Zygote
Privacyintermediate

Identifiers

Every identifier Android has ever offered has been narrowed, scoped or removed. The pattern is consistent: an identifier that survives uninstall and crosses apps is a tracking mechanism, and tracking mechanisms get regulated.

11 minAPI 1–37 · 17interactivequizverified 2026-10-27

An identifier is the difference between "someone used the app" and "this specific person used the app, on this device, and can be found again". Android spent a decade deciding which of those an app is allowed to know.

TL;DR#

  • Identifiers are classified by three properties: resettable, scoped, and survives uninstall.
  • IMEI, serial and MAC are hardware identifiers. Apps lost access to all three between Android 9 and 10.
  • ANDROID_ID was device-wide; since Android 8.0 it is scoped to the app-signing key.
  • The Advertising ID is the sanctioned cross-app identifier: resettable by the user, and zeroed when the user opts out.
  • App-set IDs (a UUID you generate and store) are the recommended approach for your own analytics and account linking.
  • There is no identifier that is both stable across installs and available to every app. That is the point.

Mental model#

Think of identity documents.

A passport number is a hardware identifier: it identifies the device, it does not change, and it is the same for everyone who looks at it. Handing it out is a serious decision.

A library card is a scoped identifier: it identifies you to one institution, it is useless elsewhere, and it can be cancelled and reissued.

A loyalty number is resettable: you can ask for a new one, and the old one stops working.

The Android trajectory has been to move every identifier from passport toward library card — and to delete the ones that could not be reissued.

Where the analogy breaks#

A passport is issued by a state that knows who you are. Android's scoping is cryptographic and per-installation, and the "authority" is a signing key rather than a person. Also, an app can generate its own loyalty number and store it, which no real loyalty scheme allows — the constraint is not on creating identifiers, it is on sharing them.

The big picture#

Loading diagram…
Text description of this diagram

A comparison of five identifier families and what a normal app can do with each today. IMEI and serial number required only READ_PHONE_STATE historically and are now privileged-only, returning null or throwing for ordinary apps since Android 10. MAC addresses were readable and stable; since Android 10 they are randomised per network, including in scan results. ANDROID_ID was a device-wide value identical for every app; since Android 8.0 it is scoped to the app-signing key and the user. The Advertising ID is the sanctioned cross-app identifier, resettable and zeroed on opt-out, restricted by Play policy to advertising and analytics. An app-set ID is a UUID you generate and store, scoped to the installation, lost on uninstall, not shared with other apps, and the recommended default for your own backend. An account ID remains the only correct answer for cross-device identity.

The column that matters is "survives uninstall". An identifier that disappears when the app is uninstalled cannot be used to re-identify a user who deliberately started over.

Deep dive#

The taxonomy#

Android's own guidance classifies identifiers along three axes, and the classification is the most useful thing in the document:1

PropertyMeaningExample
ResettableThe user can ask for a new valueAdvertising ID
ScopedThe value differs per app or per signerANDROID_ID since 8.0
Survives uninstallThe value is the same after reinstallHardware identifiers (historically)

A good identifier for analytics is resettable and scoped. A hardware identifier is neither, which is why it was the first thing removed.

Hardware identifiers#

IMEI (the modem's identity), serial number and MAC address were all readable by ordinary apps in early Android. All three are now restricted:

  • Android 9: background sensor and network access tightened; non-SDK interfaces restricted.
  • Android 10: READ_PRIVILEGED_PHONE_STATE is required for IMEI and serial; ordinary apps get null or a SecurityException. MAC randomisation becomes standard for Wi-Fi scanning.2

The Android 10 change is worth reading precisely, because the migration advice is the interesting part: use a resettable identifier, or an app-scoped ID, or — best — nothing at all.

ANDROID_ID#

Settings.Secure.ANDROID_ID is a 64-bit value generated when the device is first set up. For years it was device-wide and stable, which made it the unofficial tracking identifier of choice.

Since Android 8.0, it is scoped to the app-signing key and the user. Two apps signed by different keys see different values on the same device; the same app reinstalled with the same key sees the same value.3

That scoping is why ANDROID_ID is still usable for fraud detection and licence checks within one app, and useless for cross-app correlation.

// Scoped to the app-signing key and the user. Not a device identifier.
val androidId: String = Settings.Secure.getString(
    contentResolver,
    Settings.Secure.ANDROID_ID,
)

The Advertising ID#

The Advertising ID is the sanctioned exception: a resettable, user-controllable identifier intended for advertising and analytics. It has two important properties:

  • The user can reset it in Settings, which breaks the link to past activity.
  • The user can delete it, which zeroes the value. Apps must handle an all-zeros ID.

On devices where the user has opted out of ad personalisation, the Advertising ID returns a zeroed value.

unverified(the Advertising ID's availability and the exact opt-out behaviour have changed with Privacy Sandbox work; confirm against the current Play services documentation)

The Play policy around it is as important as the API: using the Advertising ID for anything other than advertising or analytics, or linking it to a persistent identifier, is a policy violation.

For your own backend, the correct answer is an identifier you generate and store:

object InstallationId {
    private const val PREFS = "installation"
    private const val KEY = "id"
 
    fun get(context: Context): String {
        val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
        prefs.getString(KEY, null)?.let { return it }
 
        val id = UUID.randomUUID().toString()
        prefs.edit().putString(KEY, id).apply()
        return id
    }
}

Three properties make this the right default:

  • It is yours, so no platform change can revoke it.
  • It is scoped to the installation, so it disappears on uninstall.
  • It is not shared with other apps, so it is not a tracking vector.

The trade-off is that it is lost on reinstall and not shared across devices — which is exactly the property that makes it acceptable.

Why identifiers were locked down#

The regulatory backdrop is the GDPR's concept of personal data and the ePrivacy rules on device storage, but the immediate cause was simpler: an identifier that survives uninstall and crosses apps is a tracking mechanism, and once one exists, every analytics SDK uses it.

The Android 10 change and the Play policy that followed did not invent a new principle. They enforced one the platform had been inconsistent about for a decade.

What to do instead#

GoalWrongRight
Count installsIMEIPlay Console install metrics, or an app-set ID
Recognise a returning userANDROID_IDYour own account ID, or a stored app-set ID
Attribute an adIMEI or MACAdvertising ID, within policy
Detect fraudDevice serialPlay Integrity API verdicts, plus your own signals
Sync across devicesAny device IDThe user's account

The last row is the one teams resist. Cross-device sync is an account problem, and solving it with a device identifier works only until the user changes device — at which point it silently fails.

The questions to ask before using any identifier#

Four questions, in order. If any answer is uncomfortable, the identifier is wrong for the job.

Does it survive uninstall? If yes, it links a user across a decision they made to start over. That is the property regulators care about most.

Is it visible to other apps? If yes, it is a join key, and any SDK that reads it can correlate your app's data with another's.

Can the user reset it? If yes, a reset must actually work — the old value must stop appearing, which means you cannot cache it indefinitely.

What happens if it changes? Every identifier eventually changes: the user resets it, the device is replaced, the app is reinstalled. Your backend must treat a changed identifier as a new installation, not as an error.

The last question is the one teams miss. A login flow that keys an account to a device identifier breaks when the user upgrades their phone, and the failure looks like data loss.

The Play policy layer#

The platform APIs are only half the constraint. Google Play policy separately restricts how the Advertising ID may be used, requires disclosure of the identifiers an app collects, and limits which apps may request USE_EXACT_ALARM or the QUERY_ALL_PACKAGES permission.

That means a technically permitted call can still be a policy violation. The pattern is the same as with permissions: the platform decides what is possible, and the store decides what is allowed to be distributed. An app that reads the Advertising ID for analytics without declaring it is within the API and outside the policy.

How it evolved#

2008–2017Hardware IDsIMEI, serial and MAC readable by any app
2017–2019ScopingANDROID_ID per app and signer; non-SDK restrictions
2019–nowRemovalIMEI and serial need privileged permissions; MAC randomised
Privacy changes8 releases
APIVersionWhat changed
184.3 Jelly Bean
  • App Ops (hidden) introduced for permission control
236.0 Marshmallow
  • requestPermissions() and the runtime permission flow
289 Pie
  • Foreground service permission
2910
  • Non-resettable identifiers restricted
3011
  • One-time permissions
3112
  • Bluetooth scan/connect permissions
3313 Tiramisu
  • POST_NOTIFICATIONS runtime permission
3717 Cinnamon Bun
  • ACCESS_LOCAL_NETWORK runtime permission for local network access

Under the hood#

Curiosity corner#

  • ANDROID_ID is not random per app. It is derived deterministically from the signing key and the user, so two apps with the same signing key see the same value.
  • A zeroed Advertising ID is a valid response, not an error. Treating it as "missing" and generating a fallback is a policy violation.
  • MAC randomisation applies to scan results too, not just the device's own interface, which is why Wi-Fi-based proximity tracking got much harder.

Try it#

Read ANDROID_ID

adb shell settings get secure android_id

Example output
a1b2c3d4e5f60718
Check the phone-state permission

adb shell dumpsys package com.example.app | grep READ_PHONE_STATE

Example output
android.permission.READ_PHONE_STATE: granted=true
Reset the advertising ID

adb shell am start -a android.settings.ADVANCED_PRIVACY_SETTINGS

Example output
Settings → Privacy → Ads → Reset advertising ID

Build it#

Generate an app-set ID and store it. Then reinstall the app and observe that it changed. That observation — that the identifier is scoped to the installation — is the design, not a bug.

Check yourself#

Check yourself

1 / 12

Put these privacy changes in the order they arrived.

Drag, or use the arrows to reorder.

  1. 1Selected photos access
  2. 2Privacy Dashboard, mic and camera indicators, approximate location
  3. 3One-time permissions and permission auto-reset
  4. 4Runtime permissions
  5. 5Scoped storage and background location as a separate grant
0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsUnique identifiers best practicesdeveloper.android.comaccessed 2026-10-27↩
  2. docsBehavior changes: Apps targeting Android 10developer.android.comaccessed 2026-10-27↩
  3. docsPerubahan Perilaku Android 8.0developer.android.comaccessed 2026-10-27↩
  4. docsPerubahan perilaku: aplikasi yang menargetkan API level 28+developer.android.comaccessed 2026-10-27↩
  5. docsManifest.permission | API referencedeveloper.android.comaccessed 2026-10-27↩
  6. docsPrivacy Sandbox on Androiddeveloper.android.comaccessed 2026-10-27↩
  7. docsPermissions on Android | Privacydeveloper.android.comaccessed 2026-10-27↩