dpm set-device-owner Launches the App Before prefs Is Set
This article may contain affiliate links. Its content is not affected by advertising.
In short
The moment dpm set-device-owner succeeds it launches the app, and with empty prefs the app mints its own device_id — so device-owner provisioning has to come after the config is written, not before.
Bottom line
dpm set-device-owner is not a command that merely grants a privilege. The instant it succeeds, it launches the target app. If that launch happens before the app’s config file (prefs) holds the real device_id, the app sees nothing but an empty config and mints and saves a new id for itself. Worse, once the app is device owner, the usual fix — force-stop — no longer reliably kills it.
Symptom
The provisioning script that turns a factory-reset Google TV into a kiosk unit ran in this order:
- Install the kiosk app
- Grant device ownership with
dpm set-device-owner am force-stopthe app- Write a prefs file containing the correct
device_id, delivery URL, and so on - Read prefs back with
run-asright after writing, to confirm the value landed
Step 5’s read-back always succeeded — reading a value right after writing it is supposed to show what you just wrote. But testing against real hardware on 2026-10-03 found that checking again a little while after provisioning finished, some devices reported a device_id different from the one that had been set.
Cause
As a matter of Android’s own behavior, dpm set-device-owner launches the app on the device once ownership is granted. Step 2 in the script did exactly that — before step 4 ever wrote the correct prefs.
At that point prefs is still empty (or holds a previous device’s leftover value), so the app takes the same branch it would on any fresh first launch: “no device_id? generate one and save it.” That self-generated id ends up in memory, and the process stays running.
Step 3’s am force-stop existed precisely to kill this “already running with a self-generated id” process. But once an app is device owner, force-stop no longer fully terminates it. Even though step 4 overwrites the file on disk with the correct value, the still-living process keeps using the id it already has in memory, and depending on timing can write that id back over the file again. The result was a bug whose outcome depended on when you checked: step 5’s immediate read-back showed the right value, while checking again moments later showed something else.
Fix
The fix wasn’t new code — it was reordering the steps.
echo "== install APK"
a install -r "$APK"
-echo "== Device Owner"
-a shell dpm set-device-owner "$PKG/.TvDeviceAdminReceiver"
-a shell dpm list-owners || true
-
echo "== config (prefs)"
(... build an XML with device_id, delivery URL, etc ...)
a shell am force-stop $PKG
a shell "run-as $PKG sh -c '... write to shared_prefs/tv_ble_bridge.xml ...'"
a shell "run-as $PKG cat shared_prefs/tv_ble_bridge.xml" | grep -E "device_id|device_label"
+# force-stop no longer works once device-owner is set; to rewrite, write prefs then adb reboot
+
+# device-owner has to come after prefs is written (it launches the app, and with
+# empty prefs the app mints and overwrites its own device_id)
+echo "== Device Owner"
+a shell dpm set-device-owner "$PKG/.TvDeviceAdminReceiver"
Moving dpm set-device-owner to after the prefs write means that by the moment device-owner provisioning launches the app, prefs already holds the real device_id. The app never takes the branch that generates its own; it just reads the value that’s already there.
A delayed verification step was also added at the end of the script.
got=$(a shell "run-as $PKG cat shared_prefs/tv_ble_bridge.xml" | grep -o "$DEV" || true)
[ -n "$got" ] && echo "device_id OK" || echo "!! device_id was overwritten -> rewrite prefs and adb reboot"
An immediate read-back couldn’t catch the “overwritten later” failure, so the script now re-checks device_id once more after the launch sequence (the part that brings the app to the foreground with am start) has fully run. If it’s wrong at that point, adb reboot restarts the device. Force-stop can’t kill a device-owner process, but a reboot guarantees it, and the device comes back up reading the correct prefs from disk.
The general shape of this
At its core, this bug is the combination of “an operation that grants a privilege immediately triggers a side effect that uses it” (here, the app auto-launching) with “the precondition that side effect depends on wasn’t in place yet” (here, the config file’s contents).
- For operations where the moment you grant something, code runs — privilege escalation, device-owner provisioning, install-time hooks like
BOOT_COMPLETEDor a receiver’s first launch — confirm config files and secrets are already in place beforehand. - After escalation, the usual remedies that worked before escalation (force-stop, writing with ordinary permissions) can stop working with the exact same command. “It worked a moment ago, so it should still work” is a premise worth re-checking once privilege has changed.
- An immediate read-back right after writing can’t see a failure that only shows up later. Adding a second check after some time has passed is what actually caught this timing-dependent bug.
Frequently asked questions
Q1Why does reordering the steps fix it?
The app launching on device-owner grant is inherent to Android and can't be avoided. Writing the real device_id into prefs first means the app that gets launched just reads that value instead of generating a new one.
Q2Couldn't the script force-stop the app after device-owner and rewrite prefs?
That was the original design and it failed. Once an app is device owner, force-stop no longer fully kills it. The running process keeps its self-generated id in memory, so overwriting the file alone has no effect.
Q3How was this caught in the first place?
A check added to the end of the script reads prefs back and greps for the expected device_id. Running it on real hardware on 2026-10-03 showed some devices held a different value than the one written.
Q4Is there a reliable way to kill the running process?
The script's answer is adb reboot. A reboot guarantees the process ends, so after rewriting correct prefs, rebooting makes the device read that value back on its next launch.
Environment verified
- Android TV / compileSdk 34, targetSdk 34, minSdk 26 (adb-driven provisioning script)
- Hit during real-device provisioning on 2026-10-03, fixed same day
What this article is based on
- Shell file lines 1-84commit ab5826f
Every claim in this article comes from the records above. The repositories we operate are private so we cannot link to them, but which file, which lines, and at which commit we read them is recorded for every article. Nothing here is written from guesswork.