Skip to content

Why locations lag behind the Find My app on an iPhone, and when a report actually arrives #229

Description

@parawanderer

If you have an iPhone or iPad as well, you will notice that Apple's Find My updates a tag that this app says nothing about for hours. That is not a fetch bug, and no amount of work on the Apple-network path will fix it.

This issue exists to answer the question once, and to point anybody who wants to close the gap at the work that would do it.

The short answer

An AirTag only broadcasts to the Find My network while it is separated from its owner's Apple devices. While it is connected to one of yours, no passing iPhone reports it, so there is nothing for this app to fetch — Apple's app is not fetching either, it is being told directly over Bluetooth by the device the tag is talking to.

So the thing that makes Apple's app look better is the thing this app does not have: an Apple device in your pocket.

When you will get a location

A location report exists only when somebody else's Apple device walks past your tag while it is separated from yours. In order:

  1. Your tag loses contact with your own iPhone, iPad or Mac.
  2. After a short while it starts broadcasting to the Find My network.
  3. Any passing Apple device — a stranger's, anybody's — hears it, encrypts a position, and uploads it to Apple.
  4. This app fetches that report and decrypts it with your keys.

From 1.1.0 there is a second path: your own Android phone hears the tag over Bluetooth and records a position itself, without Apple being involved at all. That only works within Bluetooth range, but it is the one case you control.

Which means the timing is somebody else's footsteps, not your refresh button. A tag in a bag on a city street can be reported every few minutes. A tag in a drawer in a quiet house, or in a rural area, can go many hours with nothing, and a tag somewhere genuinely deserted may never be reported at all. Pressing refresh cannot conjure a report that nobody has uploaded.

It also means a report is as fresh as the last person who walked by, not as fresh as the moment you asked. A timestamp of two hours ago is usually the network working correctly.

When you will not, and it is not a bug

  • The tag is next to your own iPhone or iPad. It is connected, not separated, so nothing reports it. This is the counter-intuitive one: owning an Apple device actively suppresses network reports for tags near it.
  • The tag is somewhere with no people. No passers-by, no reports.
  • The tag has just been separated. There is a delay before it starts broadcasting, and another before anybody hears it.

What version 1.1.0 already does about this

Several things, and between them they remove a good share of the "no reports at all" cases. If you are on 1.0.5 or older, update before concluding anything from this page.

1. The app listens for tags over Bluetooth itself (#139, by @ubrt). When a tag is within Bluetooth range of your Android phone, the app sees it directly and says so on its card — "Nearby", with a signal meter and a battery reading — whatever the Find My network does or does not know. That covers the commonest complaint: the tag is in your house, Apple has nothing, and the app used to have nothing to say either.

2. Those sightings become location history, at the two moments that carry information. Your phone hearing a tag continuously does not mean the tag is moving — it means you are — so a position is not written for every sighting. One is written when a tag turns up and when it stops being heard, which is where the information actually is. Your own phone is doing for your tags what a passing stranger's iPhone does, in the places you go.

3. Signing in works again, which was silently the cause of some "no reports". A sign-in that fails, or an account whose session has quietly gone stale, produces a map that stops updating for no visible reason — the same symptom as no network coverage, from a completely different cause. 1.1.0 generates its Anisette data on the device instead of depending on a third-party server, and fixes two Apple-side refusals that broke sign-in outright (#181, #206).

4. History is pulled for the whole window Apple keeps. Apple's network retains roughly seven days of reports and no more. Asking for less than that window means having fewer reports to draw than exist, which reads as a sparse map rather than as a short query.

What none of it does is give you a position for a nearby tag beyond "in range of this phone", or help at all once the tag is out of Bluetooth range and nobody walks past it.

Looking around iCloud yourself

This project reads only the parts of the keychain it understood well enough to use. There is considerably more in there. If you want to answer the bond-key question — or any other "is this even available?" question — the way to do it is to enumerate a keychain view and decrypt everything in it, rather than the one item the app goes after.

The fork is parawanderer/FindMy.py, and AsyncKeychainSession.service_keys is the whole path in one method: fetch the shares, unwrap the view's keys, enumerate the zone, then pick out one item by its tag. Replacing that last step with a loop is your index.

from findmy.keychain.items import fetch_view, decrypt_item

# `session` is an open AsyncKeychainSession and `peer` came back from session.recover(...)
shares = await session.key_shares(peer)

# 1. Which views does this account actually yield keys for? Not only the two we read.
print(sorted({s.service for s in shares if s.view_keys}))

# 2. Enumerate one view's zone and decrypt every item in it, rather than one.
view = "Manatee"                       # where Find My's keys live
keyring = await session._view_keyring(peer, view, shares)
contents = await fetch_view(session._securityd, view)

for identifier, record in contents.items.items():
    try:
        print(identifier, decrypt_item(record, keyring))
    except Exception as problem:       # noqa: BLE001 - you are exploring
        print(identifier, "could not decrypt:", problem)

# 3. And the pointers, which say which item each well-known tag names.
print(contents.pointers)

Four things worth knowing before you start:

  • NEEDED_VIEWS is Manatee and ProtectedCloudStorage, and those are just the two this project needs. Step 1 above prints every view your account yielded keys for. If something is not in Manatee, that list is where to look next.
  • ViewContents already throws information away. split_view_records keeps item and currentitem records and ignores every other type, because those were the two that mattered here. If you are hunting for something this project does not model, read the raw records before that split rather than after it.
  • _view_keyring and _securityd are private. This is a recipe for looking around, not an API to build on. If it turns up something worth having, the right outcome is a pull request that gives it a public path.
  • All of it is read-only, and needs no passcode beyond the one recover() already spent. Nothing here writes to your account.

The format specifications are in docs/findmy-export/ — Stage 3 covers the keychain trust circle and Stage 5 the accessory records.

Nobody has run the snippet above as written; it is assembled from the code paths service_keys already uses. Expect to fix an import.

Where the remaining work is

The technical reference is docs/owner-connected-tags.md — the states a tag moves through, what it broadcasts in each, the two BLE control points and what each is gated on, and which other projects have done parts of this. Read that before starting on any of the below.

Issue What closing it would buy
#48 The original tracking issue for local scanning. Partly delivered by #139; what remains is written there.
#201 Ringing a tag that is near its owner — the owner-connected control point, which is a different protocol from the unauthorised one
#193 Why Play Sound silently does nothing on a real third-party accessory, with a capture from @LiamJ74
#204 Showing the raw Bluetooth exchange when a tag is rung, so the above can be debugged at all
#131 Locating your own iPhones, iPads and Macs — a different mechanism again, and the other half of "this shows less than Apple's"
#166 Whether an AirTag can be registered without an iPhone at all
#167 Contributing reports back to the network instead of only consuming it

Is parity actually reachable?

Yes in principle, and that is worth stating plainly, because "an Android phone cannot be the owner" is the wrong conclusion to draw from any of the above.

What an iPhone does is connect to the tag over Bluetooth, encrypt the link with the owner's bond key, and use the owner control point (4F860002). Nothing in that is Apple hardware doing something only Apple hardware can do. The vendor Find My SDK is explicit that the bond key belongs to the Apple ID rather than to one phone — different phones on the same account can encrypt successfully — so "the connected-mode owner" is a role defined by holding a key, not by being an iPhone. A device that can present that key is the owner as far as the accessory is concerned.

Two things stand between that and a working feature, and both are unknowns or engineering, not impossibilities:

  1. Nobody has established where the bond key lives, or whether anything this app already exports contains it. That is the first question to answer, and answering it needs no Android code.

  2. Android cannot encrypt the link with a key you hand it, and this is the hard part. The encryption in question is at the link layer — the iPhone sends HCI_LE_Start_Encryption immediately after connecting, with the same LTK every session and EDIV and Rand both zero, which is an existing LE Secure Connections bond being resumed. It is not something that can be done above the Bluetooth stack, so no amount of GATT-level cleverness substitutes for it. Android's public API offers createBond(), which negotiates a new key, and nothing that accepts an existing one.

    A rooted route — BlueZ on the internal controller with the key loaded via btmgmt — was tried in #193 and failed at btattach because that kernel lacks CONFIG_BT_HCIUART. That says the attempt failed on one device, not that the approach is wrong, but it does mean any working version of this is likely to need root, a custom kernel, or an external Bluetooth adapter. That is a different kind of feature from anything else in this app, and worth knowing before starting.

So the accurate summary is: the separated case is largely solved, the connected case is unsolved rather than impossible, and the next useful step is the key question rather than more Bluetooth plumbing. seemoo-lab/airtag demonstrates the authorised path working on a paired AirTag, which is the closest thing to an existence proof — at the cost of a jailbroken iPhone.

A caveat on all of it: the capture this rests on is from a third-party accessory (a RAVEMEN bike finder), and an AirTag is Apple's own hardware that predates the specification. It may not behave identically.

Interactively co-authored by Claude Code and @parawanderer

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    @appIssues regarding the OpenTagViewer Android app@hardwareTalking to tags and accessories directly over Bluetooth: scanning, ringing, protocol capturesdocumentationImprovements or additions to documentationhelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions