← Blog 一覧

Casio WSD-F30 のサブLCD を root なしで取り戻す: 入口編 ― 純正専用 API を逆解析して「FUTABA」を表示するまで

Casio WSD-F30 Pro Trek Smart Wear OS Android リバースエンジニアリング AIDL APK解析 サブLCD memory LCD bindService

Casio WSD-F30 のサブLCDに「FUTABA」(上部マトリクス) + 「123456」(中央 7-seg) + 「13:33:9」(下部 7-seg) を表示している実機写真。物理ベゼルに DUAL LAYER DISPLAY の刻印が見える

このシリーズについて

Casio Pro Trek Smart WSD-F30 (Wear OS 2 / Android 9 ベース、コードネーム kingyo) のサブLCD (純正アプリ専用の monochrome memory LCD) を、root / bootloader unlock / 署名権限のいずれもなしで、第三者アプリから完全制御するためにやった解析と実機検証の記録です。

全 3 部構成:

  • Part 1 (この記事): 入口編 — Casio 製 APK 7 本の逆解析、AIDL transaction の特定、bindService 経由でサブLCD に「FUTABA」を初表示するまで
  • Part 2: dual-layer 編 — OLED watchface とサブLCD の同時可視、bit 7 LCD_PRIMARY フラグと「lock-out」状態の攻略
  • Part 3: プロトコル編 — wire-level (/dev/MultiSensors_CD_01 への 7 バイト write) と native ライブラリ ioctl、隠し機能と mode 15+ の reboot 領域

要旨

Casio WSD-F30 のサブLCD は、本来 Casio 純正の watchface / Activity 計測 / FieldTool 等からしか書けない領域。信じられていた、というより単に「やった人がいない」状態だった — WSD-F30 はマイナー機種で対象ユーザーが少なく、英語圏フォーラムを含めても手順情報がほぼ存在しない。技術的に難しいから誰もできなかったというより、母数が小さすぎて誰もそもそも試そうとしなかった、という構図に近い。

これを解明したら、

  • 必要なのは bindServicecom.casio.cwd.cwdUtilService.ICwdSegmentLcdService に繋ぐだけ
  • 追加で必要な uses-permission ゼロ(<protected-broadcast> 等の signature 系定義は存在しない)
  • 1 行 lcd.lcdCtl(9)mode 9 = MEASUREMENT モード (上部マトリクスのみアプリ自由、中央 7-seg は IC ハードコード時計、下部 7-seg はデフォルト 000000) に切り替え。用途に応じて mode 0 (BLANK = 全領域フリー) や mode 5 (BOTTOM_CLOCK) などの選択が肝
  • あとは setTopPanelText("FUTABA", 1, 1) で 23×5 ドットマトリクスに任意文字
  • アプリ終了は unbindService だけ。memory LCD なので最後のフレームが保持されて、画面オフ後も残る

という拍子抜けするほどシンプルな結論に辿り着いた。root も bootloader unlock もいらない、SET_WATCH_FACE_PRIVILEGEDsignature も要らない、普通の untrusted app から完結する

ただしそこに至るまでのドラマがあって、特に 最初は「何を送っても表示が変わらない」状態で「物理層の何かが書き込みを遮断しているのでは」という詰み仮説にまで陥った。そこから「lcdCtl(9) でモード切替が必要」を引き当てるまでの過程が Part 1 の中身です。

背景: WSD-F30 のサブLCD という独特な構造

WSD-F30 は Casio の Pro Trek Smart 第三世代 (2018-2019)、Wear OS 2 ベースのアウトドアウォッチ。最大の特徴は OLED の上にもう一枚、モノクロの memory LCD (= サブLCD) が物理的に重なっている dual-layer 構造

  • OLED: フルカラー、Wear OS の UI が出る、消費電力高い
  • サブLCD: モノクロ memory LCD、書き換え時にしか電力使わない (memory in pixel)、純正の Multi Timepiece モードや「Extend Mode (バッテリー 10 日)」ではこれだけが点灯

サブLCD には以下の領域がある:

  • 上部 23×5 ドットマトリクス — 6 文字 NARROW / 4 文字 WIDE のテキスト
  • 中央 7-segment 5 桁 — 時刻を出す領域 (IC ハードコード時計が動いている)
  • 下部・周囲のアイコン / プリセットセグメント — BPM・歩数・気圧・心拍 etc

通常は Casio 純正の watchface / 内蔵アプリしか触れない。サードパーティ Wear OS アプリは OLED にしか描画できない。「サブLCD で自分のアプリの何かを出したい」が普通には不可能。

動機: サブLCDで遊びたい (Multi Timepiece 運用の延長として)

そもそも OLED で表示できるスマートウォッチはいくらでもある。Apple Watch でも他の Wear OS 機でも、OLED に好きな情報を出すのは普通にできる。F30 にしかない遊び場は サブLCD という独特な省電力ディスプレイ の方。memory in-pixel で書き換え時しか電力を使わないモノクロ LCD で、ここに自分の好きな表示を出せたら面白そう、というのが本質の動機。

実運用上の文脈もある。自分は WSD-F30 を、

  • 通常時は Multi Timepiece モード (= OLED off、サブLCDのみで時計表示) で運用
  • 通知を見たり何か操作する時だけ Wear OS を起こして OLED 表示

というスタイルで使っていた。Extend Mode (10 日駆動の Stamina モード) も試したが、開始時刻 / 終了時刻 / 起動予約 / シャットダウン予約とパラメータが多く、日常的に on/off するには設定が面倒。Multi Timepiece のほうがその場で気軽に切り替えられて、結果的にこちらが日常運用の定番になっていた。

ということは、普段の自分はずっとサブLCDを見ている。だったらここに好きな文字や絵を出せたら、ウォッチ体験が一段楽しくなる。やってみたかったこと:

  • 上部マトリクスに飼い犬の名前
  • 中央 7-seg に歩数や現在気温など好きな数字
  • アニメーション (走る猫スプライト等)
  • 散歩中・自転車中に、時計じゃない情報を出す

これら全部、純正にはない (Multi Timepiece は時計表示固定、Extend Mode は決まった項目だけ)。WSD-F30 はもう後継機が出ない (Casio は 2022 年に Pro Trek Smart 系列の打ち切りを発表) ので、メーカーがやらない以上は自分でやるしかない。

なおこのプロジェクトは read-only な静的解析と adb shell 観察、および第三者アプリのインストールのみ で完結している。デバイスへの書き込み・root 化・bootloader 操作は一切やっていない。文鎮化リスクなし、保証も切らない。

既存策と限界

限界
純正 Casio watchface / Activity 計測アプリサブLCDの表示パターンが固定、ユーザー文字列を入れる隙間がない
root 化 + Magisk モジュールbootloader unlock が必要、保証切れる、Casio はファクトリイメージ未公開
カスタム ROM / Wear OS 移植版F30 向けのコミュニティ ROM はほぼ存在しない
Tasker + AutoWear 系OLED 側の通知制御止まり、サブLCDには触れない

サブLCDの「制御 API そのものが Casio 内部にだけ存在する」という構造なので、その内部 API を見つけて呼べないなら詰み、という前提でスタートした。

ハードルとブレークスルー

1. Casio 製パッケージの棚卸し (pm list packages)

最初の手がかりは adb shell pm list packages | grep -i casio:

package:com.casio.cwd.cwdUtilService
package:com.casio.cwd.wsdapps
package:com.casio.cwd.batterynotification
package:com.casio.cwd.setupreceiver
package:com.casio.cwd.gpsassistservice
package:com.casio.cwd.retail.stub

6 パッケージあり、特に com.casio.cwd.cwdUtilService (/vendor/app/CwdUtilService/、130KB) が 複数プロセス (:cwdUtil:cwdSegmentLcd:SysTimeSv 等) を別 UID で立ち上げてる のが目を引く。:cwdSegmentLcd がそれっぽい名前で、ここが本命と当たりを付けた。

加えて com.casio.cwd.wsdapps (/data/app/...-base.apk、36MB) が TOOL 系の本体で、Stamina / FieldTool / WatchFace / MultiSensor / LocationNote を全部内包している巨大パッケージ。これは「Casio 純正がサブLCD を実際にどう叩いているか」のサンプル集として価値が高い。

2. /vendor/app/ から APK を取り出す: adb exec-out cat の罠回避

/vendor/app/CwdUtilService/CwdUtilService.apk を取り出そうとして adb pull すると Permission deniedshell グループは read 権限を持つが、SELinux 的に adb-pull (= adb sync) のドメイン遷移で拒否される。

回避策は adb exec-out cat <path> > local.apkexec-out はバイナリセーフな stdout で、catshell グループ権限で素直に read できる。これで全 7 APK を手元に揃えた。

3. APK をデコンパイル: apktool + jadx の合わせ技

  • apktool: AndroidManifest.xml、リソース、AIDL interface descriptor を抜き出す
  • jadx: Smali → Java 逆コンパイル (ProGuard で難読化されてるが、論理構造は読める)

com.casio.cwd.cwdUtilService の AndroidManifest を読むと:

<service android:name=".CwdSegmentLcdService" android:process=":cwdSegmentLcd">
  <intent-filter>
    <action android:name="com.casio.cwd.cwdUtilService.ICwdSegmentLcdService"/>
  </intent-filter>
</service>
  • android:exported 明示なし → API 28 (Android 9) では intent-filter ありで未指定 = exported=true がデフォルト
  • android:permission 属性なし → クライアントに追加 permission 要求なし
  • sharedUserId="android.uid.system" がアプリタグについてる → サービス本体は system UID で動作 (SELinux context は u:r:system_app:s0)

つまり第三者アプリ側は uses-permission を一切宣言せずに、ただ bindService するだけで繋がる構造。これが最初の朗報。

4. AIDL transaction code の復元

AIDL Stub クラスは ProGuard 難読化済みで、Java 側で見えるのは a(), b(), c() 系のメソッドだけ。ただし AIDL の writeInterfaceToken("com.casio.cwd.cwdUtil.ICwdSegmentLcdService") 文字列は残っている。これが取っ掛かり。

onTransact のディスパッチ部分は典型的な switch(code) で、各 case 内の引数読み出しパターンから transaction code 1〜42 が何を期待しているか復元できる:

switch (code) {
    case 1: // INTERFACE_TRANSACTION
    case 3: // lcdCtl(int mode)
        int v = data.readInt();
        this.lcdCtl(v);
        reply.writeNoException();
        return true;
    case 4: // lcdCtlBits(int mode, int b6, int b7)
        ...
    case 8: // setTopPanelText(String text, int x, int mode)
        ...
}

引数 (Int / String / byte[]) のパース順序と書き戻し方からシグネチャを推定して、42 メソッドぶん全部マップした。完成したマッピング表が後の Part 3 で公開する API リファレンス の中身。

5. クライアント側 AIDL Stub の自作

Casio 公式 AIDL は公開されていないので、descriptor 文字列さえ合えば transact できる原理を使って 自前で AIDL Stub を書く

public interface ICwdSegmentLcdService extends IInterface {
    String DESCRIPTOR = "com.casio.cwd.cwdUtil.ICwdSegmentLcdService";

    abstract class Stub extends Binder implements ICwdSegmentLcdService {
        public Stub() { this.attachInterface(this, DESCRIPTOR); }

        public static ICwdSegmentLcdService asInterface(IBinder obj) {
            if (obj == null) return null;
            IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (iin != null && iin instanceof ICwdSegmentLcdService)
                return (ICwdSegmentLcdService) iin;
            return new Proxy(obj);
        }

        public IBinder asBinder() { return this; }
    }

    static class Proxy implements ICwdSegmentLcdService {
        private IBinder mRemote;
        Proxy(IBinder remote) { mRemote = remote; }

        public void lcdCtl(int mode) throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            try {
                data.writeInterfaceToken(Stub.DESCRIPTOR);
                data.writeInt(mode);
                mRemote.transact(3, data, reply, 0);  // transaction code 3
                reply.readException();
            } finally {
                data.recycle();
                reply.recycle();
            }
        }
        // ... 他 41 メソッド分も同様
    }
}

これで Casio 側 service と AIDL レベルで会話できる土台ができた。

6. bindService + 最初の write

シンプルに以下を書いて Pixel 4 機 (Mac から adb) で WSD-F30 にインストール、起動:

Intent i = new Intent("com.casio.cwd.cwdUtilService.ICwdSegmentLcdService");
i.setPackage("com.casio.cwd.cwdUtilService");
bindService(i, new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        ICwdSegmentLcdService lcd = ICwdSegmentLcdService.Stub.asInterface(service);
        try {
            lcd.setTopPanelText("FUTABA", 1, 1);
        } catch (RemoteException e) { e.printStackTrace(); }
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {}
}, BIND_AUTO_CREATE);

結果: 何も起きないadb logcat を見ると setTopPanelText は走っているが、サブLCDは Casio 標準の watchface (時刻) のまま。

7. 詰み仮説: 「物理層の何かが書き込みを遮断してるのでは」

最初に立てた誤った仮説:

WSD-F30 のディスプレイは OLED の上にサブLCD が重なる dual-layer 構造。Wear OS が起動してて OLED に通常 UI が出ている間は、サブLCD は物理的・電気的に何かしらの方法で抑制されていて、書いても反映されないだけなのでは?

具体的には「OLED ON 中はサブLCD の表示バッファが OLED 側の合成優先になっていて、書き込んでも合成段で捨てられている」みたいな方向の推測。これだと、「サブLCDを見せるためには OLED を消す = ユーザーが Wear OS に戻れない状態にする」必要がある、ということになり、要件 (普通のアプリから安全に使いたい) を満たせない。1 時間ほど詰みかけた

8. ブレークスルー: staminaservice/b.javaa(1) 呼び出し

詰みかけていた状態で、Casio 純正コード (decompiled) を眺め直して staminaservice/b.java を読んでいたら:

// stamina OFF 時
public void e() {
    this.lcd.a(1);  // ICwdSegmentLcdService.lcdCtl(1)
}
// stamina ON 時
public void d() {
    this.lcd.a(12);  // ICwdSegmentLcdService.lcdCtl(12)
}

そして watchface_f30/c.java 線 77:

// measurement active 時
this.lcd.a(9);  // ICwdSegmentLcdService.lcdCtl(9)

「あ、lcdCtl() でモードを切り替えてから書き込むのか」

つまり最初の仮説 (「物理層の何かが遮断してる」) は外れで、サブLCDコントローラ IC 自体が複数モードを持っていて、デフォルトの classic mode (mode 1) ではアプリからの write を無視するだけだった。mode 9 (MEASUREMENT) に切り替えると上部マトリクスがアプリ自由に書けるようになる、というのが真相。

9. 深夜 23:59 の「FUTABA」表示成功

その仮説で書き直した:

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    ICwdSegmentLcdService lcd = ICwdSegmentLcdService.Stub.asInterface(service);
    try {
        lcd.lcdCtl(9);                       // ★ モード切替 (これが鍵)
        lcd.setTopPanelText("FUTABA", 1, 1); // NARROW 6文字
    } catch (RemoteException e) { e.printStackTrace(); }
}

実機にインストール → 起動。サブLCD上部のドットマトリクスに「FUTABA」が表示された。深夜 23:59。

その時点までの試行錯誤を踏まえると拍子抜けする 2 行だが、ここに到達するまでに

  • Casio 製 7 APK のデコンパイル
  • AndroidManifest 41 個の確認
  • AIDL 42 transaction の復元
  • 純正アプリ (watchface_f30、staminaservice) の呼び出し順序の読解

を経ている。lcdCtl(9) を「呼ぶことを思いつく」のが本体だった という体験。

サブLCD の「モード」全体像

ここまで判明した lcdCtl(mode) の値:

mode意味根拠
0OPTION / 未使用?(Part 3 で詳細)
1CLASSIC (自律時計モード = デフォルト)staminaservice/b.java で stamina OFF 時に a(1)
5BOTTOM_CLOCK(Part 3 で詳細)
9MEASUREMENT ← Part 1 の主役 (上部マトリクス フリー / 中央 7-seg 時計 / 下部 7-seg 000000)watchface_f30/c.java:77 で measurement active 時 a(9)
10watchface style-1同上 c.java の他箇所
11watchface style-2同上
12STAMINA (Extend Mode 中)staminaservice/b.java:103 で stamina ON 時 a(12)
13OUTDOOR_SLOW (10 秒更新省電力)(Part 3 で詳細)
14(調査中)
15-31REBOOT 領域 ← 触ると watchdog で再起動(Part 3 で詳細)

Part 1 の主役は mode 9 (MEASUREMENT)。これに入ると、上部 23×5 ドットマトリクスへの書き込み (setTopPanelText / setPanelData) と各種アイコン (setSegmentIcon) が反映される。ただし中央 7-seg は IC ハードコード時計のままで setCenter7Seg は無視される、下部 7-seg もデフォルト 000000。中央 7-seg / 下部 7-seg を自分のアプリから書きたい場合は別モードを選ぶ必要がある (mode 0 BLANK = 全領域フリー、mode 5 BOTTOM_CLOCK = 中央フリーで下部が時計、など) — 詳細は Part 3 で。

「やりたい表示」に応じて適切なモードを選ぶのが肝。Part 1 はあくまで「上部マトリクスにテキスト or bitmap を出したい」というスコープで mode 9 を使う。

最小実装パス (まとめ)

実機検証済みの最終コード:

// 1. bindService
Intent i = new Intent("com.casio.cwd.cwdUtilService.ICwdSegmentLcdService");
i.setPackage("com.casio.cwd.cwdUtilService");
bindService(i, conn, Context.BIND_AUTO_CREATE);

// 2. onServiceConnected で AIDL を取得
ICwdSegmentLcdService lcd = ICwdSegmentLcdService.Stub.asInterface(binder);

// 3. *** モード切替 (これが Part 1 で 1 時間溶かした 1 行) ***
// mode 9 = MEASUREMENT: 上部マトリクスのみアプリ自由 / 中央 7-seg は時計 IC ハードコード / 下部 7-seg は 000000
// (中央 7-seg を書きたい場合は mode 0 や mode 5 を選ぶ — Part 3 で詳述)
lcd.lcdCtl(9);   // ← 1 引数版! bit 7/6 を立てない (これが鍵、後述)

// 4. mode 9 で反映される書き込み
lcd.setTopPanelText("FUTABA", 1, 1);   // 23×5 マトリクス NARROW 6 文字 ✓
lcd.setPanelData(bitmap5rows);         // 23×5 マトリクス 生 bitmap ✓
lcd.setSegmentIcon(idx, 1);            // アイコン点灯 ✓
// lcd.setCenter7Seg(1, 2, 3, 4, 5);   // mode 9 では無視される (IC ハードコード時計のまま)

// 5. アプリ終了時は unbindService だけ
// !!! lcdCtl(1) や notifyAppShutdown は呼ばないこと !!!
// memory LCD なので最後のフレームが保持される → 画面オフ後も表示残る
unbindService(conn);

// 6. 明示的に classic 時計に戻したい時のみ
lcd.lcdCtl(1);                  // または lcd.notifyAppShutdown()

これで第三者アプリから WSD-F30 のサブLCDに任意の表示を出せる。Wear OS スリープ後もカスタム表示が残り、電源ボタンで通常 OS にも戻れる

致命的な落とし穴 (Part 2 への接続)

ここまでが「OLED を生かしたまま、auxiliary としてサブLCDに書く」 (Pattern A) の話。

これとは別に、lcdCtlBits(9, 1, 1) (3 引数版) という亜種があり、これを呼ぶと bit 7 = LCD_PRIMARY フラグが立つ → サブLCDがプライマリ表示モードになる → OLED が物理的に suspend されて、ユーザーが Wear OS に戻れなくなる。これがいわゆる 「lock-out 状態」

lcdCtlBits(9, 1, 1) を使うと bit 7 (LCD primary flag) が立つ → サブLCD primary → OLED 抑制 → 電源ボタンを押しても WearOS UI に戻れない ✅ lcdCtl(9) (1引数版) → auxiliary mode → OLED と共存

ところがこの bit 7 を立てる経路は、OLED watchface + サブLCD 任意 content を同時に光らせる体験 (ハードウェア的には可能なのに Casio 純正アプリでも実装されていない構成) には必要。つまり、

  • Part 1 のスコープ (Wear OS 起動中は OLED、スリープ後はサブLCD) → lcdCtl(9) で完結
  • Part 2 のスコープ (OLED watchface 表示中にサブLCD にも同時表示) → lcdCtlBits 必須、ただし lock-out 状態を回避する仕掛けが必要

Part 2 では、この lock-out の正体を実機実験 (5 種類の BroadcastReceiver を exported で公開して isolate) で突き止めて、最終的に WakeLock.release() 一発で解除できることを発見するまで をやります。

教訓

  • AIDL Stub の公開無しでも、descriptor 文字列 + transaction code 復元で transact できる。ProGuard 難読化はクラス/メソッド名にしか効かない (writeInterfaceToken の文字列定数は残る)
  • /vendor/app/ 配下の APK は adb exec-out cat で取り出せるadb pull の Permission denied は SELinux のドメイン拒否なので、shell グループの read 権限で素直に抜く
  • メモリ in-pixel な memory LCD は最後のフレームが保持される。書き込み終わった後に unbindService しても、画面オフ→電源 OFF まで表示が残る。これは省電力アプリ設計に向いている
  • 「物理層で何か遮断されてるのでは」という詰み仮説に陥ったときは、純正アプリ側のコードを読み直すlcdCtl(9) のような前提呼び出しが純正コード中に必ずある
  • モード値を表で全部洗うのは早めにgrep -rn 'lcd.a(' decompiled/staminaservice watchface_f30 等の呼び出し箇所が一望できる

よくある質問

root / bootloader unlock が本当に不要?

不要。本記事の手順は untrusted_app ドメイン から bindService するだけで完結する。SET_WATCH_FACE_PRIVILEGEDHOME_STEM_PRESSEDsignature 系も要らない。Wear OS の adb install で普通にアプリを入れるだけ。Casio が <protected-broadcast><permission> 定義をしていないのが大きい。

WSD-F30 以外の機種 (F20 / F21HR / F10) でも動く?

未検証。F20 (前世代) は同じ Casio Smart Outdoor Watch 系列だが、APK 構成が異なる可能性が高い。F21HR は心拍センサー搭載で AIDL に追加メソッドがある可能性。手元の F30 で確認した手順がそのまま F20/F21HR に通る保証は無いので、各機種で同じ手順 (パッケージ調査 → APK 取り出し → AIDL 復元) が必要。

ちなみに F30 は心拍センサー非搭載だが、setSegmentIcon テーブルには BPM アイコンの定義がそのまま残っている。物理 LCD は F21HR と共通パターン で、Casio が用途別に有効/無効を切り替えているだけ、と推測。

Wear OS が新世代 (Wear OS 3) になっても使える?

WSD-F30 自体は Wear OS 2 で打ち止め (Casio は Wear OS 3 アップデートを提供していない)。Wear OS 3 機 (Pixel Watch 等) には Casio のサブLCD ハードウェアが存在しないので、そもそも対象外。

adb exec-out cat/vendor/app/ を抜くのは合法?

日本の著作権法では、研究 / 解析目的での逆解析は許容範囲。配布は別問題で、APK バイナリの再配布や、/vendor/app/ から取り出したファイルを公開リポジトリに置く行為はライセンス違反になりうる。本シリーズの記事中でも、Casio 製コードの直接引用は最小限のメソッドシグネチャレベルに留めている (snippet として lcd.a(1) 等を例示)。読者が自分で同じ手順を踏めば、自機の APK は自分で抜ける。

lcdCtl(9) を呼んだまま放置するとバッテリーは?

サブLCD 自体は memory in-pixel なので、書き換え時にしか電力を使わない。lcdCtl(9) (auxiliary mode、bit 7 立てない) の状態は OLED と共存しているので、追加で電力を食う要素は無い。Wear OS 通常使用と同じ駆動時間

アプリ終了時に何もしないと、サブLCD はずっと「FUTABA」表示のまま?

そうなる。memory LCD の特性で、電源を切るまで最後のフレームが保持される。「アプリ終了 = サブLCD を classic 時計に戻す」を望むなら、onDestroylcd.notifyAppShutdown() を呼ぶ

文字以外 (絵 / アニメ) も出せる?

出せる。setPanelData(int[5]) で 23×5 マトリクスの生 bitmap (各 int の下位 23 bit が 1 行) を渡せる。9×5 のスプライト 2 枚を時間差で書き込めば「走る猫」アニメも作れる (Part 2 の dual-layer 編で自作アプリのデモとして登場)。

次回: dual-layer 編

Part 1 では Wear OS スリープ後にサブLCDだけで表示を保持するパターンを扱った。Part 2 では OLED watchface とサブLCD を同時に光らせる dual-layer 表示 を実現するために必要な bit 7 LCD_PRIMARY フラグの攻略と、それに伴って何度も陥った lock-out 状態を WakeLock.release() で解除できると突き止めた実機実験の話に進みます。

制作プロセスについて

Casio 製 APK の逆解析、AIDL transaction の特定、純正アプリ (staminaservice watchface_f30) のコールパス読解、lcdCtl(9) の発見と「物理層で遮断されてる説」の棄却、自作アプリ実装と実機検証、などの調査と試行錯誤は Anthropic の Claude Code を活用した。実機 (WSD-F30) への adb 接続・apk install・サブLCDの目視確認、文鎮化リスクを抱えた書き込み判断は人間担当。

この記事をシェア