NOTE 2021/01/08: As of Android 10 (API level 29), there's no way for non-system and non-carrier apps to get the device's serial number, not even by calling the new Build.getSerial() method with the
READ_PHONE_STATE
permission, since per the docs it'll always either returnBuild.UNKNOWN
(API < 29) or throw aSecurityException
(API >= 29). This means that the code below might not work on some devices running Android 10+, due to changes in the Android OS itself and AFAIK there's no work around. See this official guide to migrate from using the device's serial number ; the Settings.Secure.ANDROID_ID might also be a good replacement. Also read the comments to see how other people are dealing with this problem, maybe someone has found a solution that will help you.
This code snippet has been successfully tested on the following devices and Android versions :
- Archos 133 Oxygen : 6.0.1
- Google Nexus 5 : 6.0.1
- Hannspree HANNSPAD 13.3" TITAN 2 (HSG1351) : 5.1.1
- Honor 5C (NEM-L51) : 7.0
- Honor 5X (KIW-L21) : 6.0.1
- Honor 9 Lite (LLD-L31) : 8.0
- Huawei M2 (M2-801w) : 5.1.1
- Samsung Galaxy S5 (SM-G900F) : 6.0.1
- Samsung Galaxy S6 (SM-G920F) : 7.0
- Samsung Galaxy Tab 4 (SM-T530) : 5.0.2
- Xiaomi Mi 8 (M1803E1A) : 8.1.0
We still have a lot of different devices running OS 10 that continue to retrieve a valid id using this code - however, most are defaulting to:
deviceID= Build.SERIAL;
It seems that this starts to return 'UNKNOWN' once the Build.ID value is updated - our Galaxy Tab S5e continued to retrieve a valid ID while on OS 10, until recently the Build.ID was updated to: QP1A.190711.020
To handle the situations where the ID will change, or return 'UNKNOWN', we tiered our getID method to 1st try:
deviceID = Build.SERIAL;
If it returned empty or 'unknown', then we try:
deviceID= Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
If that returns empty or 'unknown', then we default to using MediaDrm to get an ID.
In our case, we are able to handle if the deviceID changes by always sending the current ID, and the 'last_used' ID to our validation check - if the new ID is not found, we then look for a record containing the old ID. If neither is found, no record exists...
In the end, we probably could have just resorted to creating a GUID initially, and use the same validation checking, since it handles changes, but I'm always concerned if ever these generated GUID's might duplicate somewhere... keeps it fun, right?