2026.09.02
USB が使えない PC から、スマホのホーム画面にウィジェットを置くまで
はじめに
Pixel のホーム画面に、自分用のウィジェットを 2 枚置いた。開発機は WSL2(Windows の中で動く Linux)で、ここからは USB でつないだスマホが見えない。それでも、ケーブルを 1 本も挿さずにビルドからホーム画面への配置まで PC 側で完結できた。
ぶつかった壁は 2 つあった。1 つは接続そのもの。もう 1 つは、Android のウィジェットは外から勝手に置けないという仕様の壁。前者はワイヤレスデバッグで、後者は requestPinAppWidget という API で抜けた。
同じことをやろうとして「ポートが違う」「なぜか繋がらない」で止まる人は多いと思うので、詰まった箇所を順番に書いておきます。
基礎となる知識
- adb(Android Debug Bridge): PC からスマホを操作するための公式ツール。アプリを入れたり、画面をタップさせたりできる
- ワイヤレスデバッグ: Android 11 以降の機能で、USB を使わず Wi-Fi 経由で adb をつなぐ仕組み。スマホの「開発者向けオプション」の中にある
- APK: Android アプリの配布ファイル。普通は Android Studio と Gradle(ビルド自動化ツール)で作る
- ウィジェット: ホーム画面に置く小さな情報表示のパネル。天気や時計のあれ
- scrcpy: スマホの画面を PC に映して、PC のマウスとキーボードで操作するツール
解説と使い方
ペア用ポートと接続用ポートは別物
最初にここで 15 分ぐらい溶かした。ワイヤレスデバッグは、pair(初回のペア設定)と connect(実際の接続)の 2 段構えになっている。
adb pair <IP>:<ペア用ポート> <6桁コード>
adb connect <IP>:<接続用ポート>
この 2 つのポート番号は違う。 ペア用ポートは「ペア設定コードによるデバイスのペア設定」を開いた時にだけ出るダイアログの中に、接続用ポートはワイヤレスデバッグ画面本体に表示されている。同じ画面に似た数字が 2 つ並ぶので、ダイアログの番号で connect して「繋がらない」となりやすい。
さらに厄介なのが、接続用ポートは端末の再起動やワイヤレスデバッグの ON/OFF のたびに変わること。スクリプトに固定で書くと翌日には動かない。うちでは既定値だけスクリプトに持たせて、外から環境変数で上書きできるようにした。
PHONE_ADDR=192.168.x.x:ポート bash scripts/phone-mirror.sh --connect
Gradle を使わずに APK を組む
ウィジェット 2 枚のために Android Studio を入れる気になれなかったので、Android SDK のコマンドだけで組んだ。外部ライブラリがゼロなら、これで足りる。
流れはこうなる。
aapt2 compileでリソース(レイアウト XML など)をコンパイルaapt2 linkでR.javaを生成して、中身が空の APK を作るjavacで Java をコンパイルd8で Android が読めるclasses.dexに変換classes.dexを APK の中に入れてzipalignで整列apksignerで署名
ビルドスクリプトは 90 行に収まった。JDK 17 と build-tools 35.0.0 があれば動く。副産物として、API のエンドポイント URL をビルド時に Config.java として生成する形にできたので、トークンを含む URL をソースに残さずに済んだ(endpoint.txt は git 管理外)。Gradle でも同じことはできるけど、シェルスクリプト 1 本のほうが何が起きているか見えていいなと思っている。
ウィジェットは外から置けない
ここが一番の壁だった。アプリのインストールは adb install -r で入る。ところが、ホーム画面にウィジェットを配置する口が adb には無い。ランチャー(ホーム画面アプリ)を長押しして、一覧から選んでドラッグする、という UI 操作でしか置けない。
抜け道は requestPinAppWidget だった。アプリ側から OS に「このウィジェットをホーム画面に追加していいですか」と確認ダイアログを出させる API で、Android 8.0 以降で使える。これを呼ぶだけの透明な画面を 1 枚作った。
AppWidgetManager mgr = getSystemService(AppWidgetManager.class);
if (mgr != null && mgr.isRequestPinAppWidgetSupported()) {
mgr.requestPinAppWidget(new ComponentName(this, target), null, null);
}
あとは adb で起動して、出てきたダイアログを adb でタップする。
adb shell am start -n jp.onbit.claudeusage/.PinActivity --es widget sites
adb shell input tap <x> <y>
タップ座標は adb shell screencap で撮ったスクリーンショットを見て決めた。この画面をアプリ一覧のアイコンからも呼べるようにしておくと、ウィジェットを間違って消した時の置き直しがスマホ側だけで済む。ここは後から効いた。
画面を見ながらやるなら scrcpy、ただし —no-audio
座標を当てずっぽうで叩くのはつらいので、途中から scrcpy で画面を PC に映した。ここでも WSL 特有の罠がある。WSL には音声デバイスが無いので --no-audio が必須で、付けないと起動した直後に落ちる。エラーメッセージが音声まわりだと気づきにくく、これも少し時間を取られた。
scrcpy -s "$DEVICE" --no-audio --max-size 1200
更新間隔は 30 分より短くできない
ウィジェットの自動更新は updatePeriodMillis で指定するけれど、30 分(1800000 ミリ秒)が Android の許す最短で、それより短い値を書いても無視される。電池を守るための制限なので、ここは諦めるところ。すぐ見たい時のために、タップしたら取り直す動きだけ足した。
やってみた結果
置いたのは 2 枚。Claude の使用量(5 時間枠・週次・モデル別の消費率)と、onbit.jp と、運用を預かっているもう 1 つのサイトの応答状況。
使用量のほうは、数字を取りに行くのを Cloudflare Workers(サーバを持たずにコードを動かせるサービス)に任せて、認証トークンを端末に持たせない構成にした。スマホを落とした時に Max プランの枠を使われるのが怖かったからで、ウィジェットは Worker が返す数字だけを読む。
サイト監視のほうは逆に、中継サーバを置かず端末から直接叩いている。監視サーバ越しだと「サーバからは見えている」という状態しか分からないけれど、端末から叩けば実際の回線からの到達性がそのまま出る。代わりにスマホが圏外だと全部 NG に見えるので、そこの区別は付かない。この割り切りは、用途によって逆になると思う。
一番の収穫は、ホーム画面という「一日に何十回も目に入る場所」に数字を出せたこと・・・というより、ワイヤレスデバッグさえ通れば PC 側からスマホの操作を一通り台本にできると分かったことだった。座標タップまで含めて手順を残せるので、同じ設定を別の端末にやり直す時が楽になる。
残った不満は、やはりポート番号が毎回変わること。今は繋がらなければスマホの画面を見に行く運用にしているけれど、ここは自動化の余地がありそうだなと思っている。
まとめ
- WSL2 から USB は見えないが、ワイヤレスデバッグで adb は通る
pairとconnectでポート番号が違う。接続用ポートは再起動のたびに変わる- ウィジェットの配置は adb から直接できない。
requestPinAppWidgetで確認ダイアログまで出して、そこを adb でタップする - 外部ライブラリが無いなら Gradle 抜きで APK は組める。ビルド時にトークンを埋め込めるので秘密の置き場所も作れる
- WSL で scrcpy を使うなら
--no-audio updatePeriodMillisは 30 分が下限
自分専用の小さい道具は、作るより「置く」「更新する」のほうが手間になりやすい。そこを台本にしておくと続くと思います。