DEV Community

cometa-kaito
cometa-kaito

Posted on Originally published at tech.rebounder.jp

dpm set-device-owner Launches the App Before prefs Is Set

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:

  1. Install the kiosk app
  2. Grant device ownership with dpm set-device-owner
  3. am force-stop the app
  4. Write a prefs file containing the correct device_id, delivery URL, and so on
  5. Read prefs back with run-as right 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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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_COMPLETED or 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.

Originally published at tech.rebounder.jp.

Top comments (0)