Rebounder Tech Blog

運用している当事者が書く、本番システムの記録。

dpm set-device-ownerはアプリを起動する。先にprefsを書かないとdevice_idを横取りされる

公開 読了時間 約5分執筆: Rebounder 開発チーム(当該システムの運用当事者)

※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。

結論

dpm set-device-ownerを実行した瞬間にアプリが起動し、空のprefsを見て自分でdevice_idを生成・永続化してしまうため、Device Owner化は設定を書き終えた後でなければならない。

結論

dpm set-device-owner は権限を渡すだけのコマンドではない。実行した瞬間に対象アプリを起動させる。 この起動が「設定ファイル(prefs)に正しいdevice_idを書く」より前に起きると、アプリは自分用の空の設定しか見えないため、自分で新しいIDを生成して保存してしまう。 さらに悪いことに、Device Owner化されたあとはアプリをforce-stopで止めるという普段の対処も効かなくなる。

症状

工場出荷状態のGoogle TVをキオスク端末として量産するためのプロビジョニングスクリプトは、次の順序で動いていた。

  1. キオスクアプリをインストール
  2. dpm set-device-owner でDevice Owner化
  3. am force-stop でアプリを止める
  4. 正しいdevice_id・配信URLなどを書いたprefsファイルを書き込む
  5. 書き込み直後にrun-asでprefsを読み戻し、値が入っていることを確認

手順5の読み戻しは常に成功していた。書いた直後に読めば、書いた値がそのまま出てくるのは当然だからだ。ところが2026-10-03の実機検証で、プロビジョニングを完了させて少し時間を置いてから確認すると、device_idが指定したものとは違う値に化けている端末があることが分かった。

原因

dpm set-device-ownerは、Androidの仕様として、権限付与が成立した端末上でそのアプリを起動させる。プロビジョニングスクリプトの手順2がこれに当たり、手順4で正しいprefsを書き込むより前に、アプリは一度起動してしまっていた。

このときprefsはまだ空(または前の端末の値が残った状態)なので、アプリ側は「device_idが無ければ自分で生成して保存する」という、通常の初回起動時と同じ分岐に入る。ここで自己生成されたIDがメモリ上に乗った状態で、プロセスは起動済みのまま残る。

手順3のam force-stopは、この「起動済みで自己生成IDを持ったプロセス」を止めるために置かれていた。ところが、Device Owner化されたアプリはforce-stopを受けてもプロセスが完全には終了しない。 ファイル上のprefsを手順4で正しい値に書き換えても、生きている方のプロセスは自分がメモリに持つ自己生成IDを使い続け、タイミングによってはそのIDで再度prefsを上書きする。結果として、スクリプトの手順5(書き込み直後の読み戻し)は正しい値を見せるのに、少し後に確認すると化けているという、検証のタイミングによって結果が変わる事故になっていた。

直す

直し方は実装を増やすことではなく、実行順序を入れ替えることだった。

 echo "== APK 導入"
 a install -r "$APK"

-echo "== Device Owner"
-a shell dpm set-device-owner "$PKG/.TvDeviceAdminReceiver"
-a shell dpm list-owners || true
-
 echo "== 設定(prefs)"
 (... device_id・配信URLなどを書いたXMLを作る ...)
 a shell am force-stop $PKG
 a shell "run-as $PKG sh -c '... shared_prefs/tv_ble_bridge.xml に書き込み ...'"
 a shell "run-as $PKG cat shared_prefs/tv_ble_bridge.xml" | grep -E "device_id|device_label"
+# ⚠ DO 化後は force-stop が効かない(プロセスが残る)。書き直しが要る時は prefs を書いて adb reboot する
+
+# DO 化は prefs を書いた後(DO 化でアプリが起動し、空 prefs だと自前の device_id を発行して上書きする)
+echo "== Device Owner"
+a shell dpm set-device-owner "$PKG/.TvDeviceAdminReceiver"

dpm set-device-ownerをprefs書き込みの後ろに移すと、Device Owner化がアプリを起動させた瞬間に、prefsにはすでに正しいdevice_idが入っている。アプリは自分でIDを生成する分岐に入らず、用意された値を読むだけで済む。

あわせて、スクリプトの末尾に遅延検証を追加した。

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 が書き換わった → prefs を書き直して adb reboot"

直後の読み戻しだけでは「化ける」事故を検出できなかったため、起動シーケンス(手順の最後にam startでアプリを前面化する処理)を一通り終えた後に、もう一度device_idを確認するようにしている。もしここで化けていたら、adb rebootで端末ごと再起動する。Device Owner化されたプロセスはforce-stopでは止まらないが、再起動すれば確実に終了し、ディスク上の正しいprefsを読み直して起動する。

一般化できる形

この事故の芯は「権限を与える操作が、その権限を使った副作用(ここではアプリの自動起動)を即座に引き起こす」ことと、「副作用が依存する前提(ここでは設定ファイルの中身)をまだ整えていなかった」ことの組み合わせだ。

  • 権限昇格・Device Owner化・インストール時フック(BOOT_COMPLETEDやレシーバの初回起動)のように、「与えた瞬間にコードが動く」操作は、設定ファイルやシークレットの配置が終わっているかを先に確認する
  • 昇格後は、昇格前に効いていた普段の対処(force-stop、通常権限での書き込みなど)が同じコマンドのまま効かなくなることがある。 「さっき効いた手段だから今も効くはず」という前提は、権限が変わった後では検証し直す
  • 書き込み直後の読み戻しだけでは「後から化ける」事故は見えない。時間を置いた2回目の検証を入れることで、タイミング依存の不具合を検出できるようにした

よくある質問

Q1なぜ順序を入れ替えるだけで直るのですか?

dpm set-device-ownerがアプリを起動させてしまうのはDevice Owner化そのものの挙動で、避けられません。先にprefsへ正しいdevice_idを書いておけば、起動したアプリはその値を読み込むだけで済み、自分で新しいIDを生成する分岐に入らなくなります。

Q2Device Owner化した後にforce-stopで止めて書き直せば良いのでは?

それが元の実装でしたが効きませんでした。Device Owner化されたアプリはforce-stopを受けてもプロセスが完全には終了せず、起動済みのプロセスが自己生成したIDをメモリ上に保持し続けるため、ファイルだけ書き換えても反映されませんでした。

Q3この問題にはどうやって気づいたのですか?

プロビジョニングスクリプトの最後にdevice_idの検証手順を追加し、書き込んだはずの値がprefsに残っているかをgrepで確認するようにしたところ、2026-10-03の実機検証で別の値に化けていることが分かりました。

Q4起動済みのプロセスを確実に止める方法はないのですか?

根拠のスクリプトでは、prefsを正しく書き直した後にadb rebootで端末ごと再起動する対処を案内しています。再起動すればプロセスは確実に終了するため、書き直した値がそのまま次回起動時に読み込まれます。

確認した環境

  • Android TV / compileSdk 34, targetSdk 34, minSdk 26(adb経由のプロビジョニングスクリプト)
  • 2026-10-03 に実機プロビジョニングで発生・同日修正

この記事の根拠

  • Shellファイル 1〜84行目コミット ab5826f

本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。