デバイス固有のコード

リカバリ システムにはデバイス固有のコードを挿入するためのフックがいくつか含まれており、ベースバンド プロセッサやラジオ プロセッサなど、Android システム以外のデバイスの一部を OTA アップデートで更新することもできます。

以下のセクションと例では、yoyodyne ベンダーが作成した tardis デバイスをカスタマイズしています。

パーティション マップ

Android 2.3 以降、eMMc フラッシュ デバイスと、これらのデバイスで動作する ext4 ファイル システムをサポートしています。また、メモリー テクノロジー デバイス(MTD)フラッシュ デバイスや古いリリースの yaffs2 ファイル システムもサポートしています。

パーティション マップ ファイルは、TARGET_RECOVERY_FSTAB によって指定されます。このファイルは、リカバリ バイナリとパッケージ ビルド ツールの両方で使用されます。BoardConfig.mk の TARGET_RECOVERY_FSTAB にマップファイルの名前を指定できます。

パーティション マップ ファイルの例を次に示します。

device/yoyodyne/tardis/recovery.fstab
# mount point       fstype  device       [device2]        [options (3.0+ only)]

/sdcard     vfat    /dev/block/mmcblk0p1 /dev/block/mmcblk0
/cache      yaffs2  cache
/misc       mtd misc
/boot       mtd boot
/recovery   emmc    /dev/block/platform/s3c-sdhci.0/by-name/recovery
/system     ext4    /dev/block/platform/s3c-sdhci.0/by-name/system length=-4096
/data       ext4    /dev/block/platform/s3c-sdhci.0/by-name/userdata

この例のマウント ポイントは、任意である /sdcard を除いてすべて定義する必要があります。デバイスがパーティションを追加する可能性もあります。次の 5 つのファイル システムがサポートされています。

yaffs2
MTD フラッシュ デバイスの上にある yaffs2 ファイル システム。「device」は MTD パーティションの名前で、/proc/mtd で指定する必要があります。
mtd
RAW MTD パーティション。boot や recovery などの起動可能なパーティションに使用されます。MTD は実際にはマウントされませんが、パーティションを見つけるためのキーとしてマウント ポイントが使用されます。「device」には /proc/mtd にある MTD パーティションの名前を指定してください。
ext4
eMMc フラッシュ デバイスの上にある ext4 ファイル システム。「device」はブロック デバイスのパスである必要があります。
emmc
RAW eMMc ブロック デバイス。boot や recovery などの起動可能なパーティションに使用されます。mtd タイプと同様、eMMc は実際にはマウントされませんが、テーブル内のデバイスを探すためにマウント ポイント文字列が使用されます。
vfat
FAT ファイル システム。通常は外部ストレージ(SD カードなど)用のブロック デバイス上にあります。device はブロック デバイスです。device2 は、パーティション テーブルでフォーマットされたかどうか不明な SD カードとの互換性を理由としてプライマリ デバイスのマウントに失敗した場合に、マウントを試みる 2 番目のブロック デバイスです。

すべてのパーティションをルート ディレクトリにマウントする必要があります。マウント ポイントの値の先頭はスラッシュにし、それ以外にはスラッシュを付けないでください。この制限は、リカバリ時のファイル システムのマウントにのみ適用されます。メインシステムはどこにでも自由にマウントできます。ディレクトリ /boot/recovery/misc は RAW 型(mtd または emmc)である必要がありますが、ディレクトリ /system/data/cache/sdcard(可能な場合)は、ファイル システム型(yaffs2、ext4、または vfat)でなければなりません。

Android 3.0 以降、recovery.fstab ファイルにはオプションのフィールドである options が追加されています。現在定義されているオプションは length のみで、パーティションの長さを明示的に指定できます。この長さは、パーティションの再フォーマット時に使用されます。たとえば、データ消去や初期化時の userdata パーティション、フル OTA パッケージのインストール時の system パーティションなどです。長さの値が負の場合、長さの値を実際のパーティション サイズに加算して、サイズを指定します。たとえば、「length=-16384」を設定すると、そのパーティションの最後の 16 KB は、パーティションが再フォーマットされたときにも上書きされません。これにより、userdata パーティションの暗号化などの機能がサポートされます。このパーティションの末尾には暗号化のメタデータが保存されており、上書きされないようにする必要があります。

注: device2options フィールドは省略可能ですが、解析時にあいまいさが生じます。行の 4 番目の項目が「/」で始まるエントリは、device2 エントリとみなされます。エントリが「/」で始まらない場合、options フィールドとみなされます。

起動アニメーション

デバイス メーカーは、Android デバイスの起動時に表示されるアニメーションをカスタマイズできます。これを行うには、起動時のアニメーション形式の仕様に従って整理され配置された .zip ファイルを作成します。

Android Things デバイスの場合、Android Things コンソールで ZIP ファイルをアップロードして、選択したサービスに画像を含めることができます。

注: これらの画像は Android のブランドの取り扱いガイドラインに適合している必要があります。

リカバリ UI

物理ボタンや LED、スクリーンなどの使用可能なさまざまなハードウェアをデバイスがサポートできるようにするため、リカバリ インターフェースをカスタマイズして、ステータスを表示すること、デバイスごとに手動で非表示の機能を利用することが可能です。

デバイス固有の機能を提供するために、いくつかの C++ オブジェクトを持つ小さな静的ライブラリを作成します。ファイル bootable/recovery/default_device.cpp はデフォルトで使用され、使用しているデバイスにこのファイルのバージョンを書き込む際に最初にコピーします。

device/yoyodyne/tardis/recovery/recovery_ui.cpp
#include <linux/input.h>

#include "common.h"
#include "device.h"
#include "screen_ui.h"

ヘッダーと項目の関数

Device クラスには、非表示のリカバリ メニューに表示されるヘッダーと項目を返す関数が必要です。ヘッダーは、メニューを操作する方法を示し、ハイライト表示された項目を変更または選択します。

static const char* HEADERS[] = { "Volume up/down to move highlight;",
                                 "power button to select.",
                                 "",
                                 NULL };

static const char* ITEMS[] =  {"reboot system now",
                               "apply update from ADB",
                               "wipe data/factory reset",
                               "wipe cache partition",
                               NULL };

注: 長い行は改行されず、切り捨てられます。デバイス画面の幅に注意してください。

CheckKey のカスタマイズ

次に、デバイスの RecoveryUI 実装を定義します。この例では、tardis デバイスに画面があると仮定しているため、組み込みの ScreenRecoveryUIimplementation から継承できます(画面がないデバイスの指示をご覧ください)。ScreenRecoveryUI からカスタマイズできる唯一の関数は CheckKey() で、初期非同期キー処理を行います。

class TardisUI : public ScreenRecoveryUI {
  public:
    virtual KeyAction CheckKey(int key) {
        if (key == KEY_HOME) {
            return TOGGLE;
        }
        return ENQUEUE;
    }
};

KEY 定数

KEY_* 定数は、linux/input.h で定義されます。 CheckKey() はリカバリのほかの部分で行われていることにかかわらず呼び出されます(メニューの切り替え時、メニューの表示がオンになっているとき、パッケージのインストール中、ユーザーデータのワイプ中など)。次の 4 つの定数のいずれかを返すことができます。

  • TOGGLE。メニューやテキストログの表示をオンまたはオフに切り替えます
  • REBOOT。デバイスをすぐに再起動します
  • IGNORE。このキー操作を無視します
  • ENQUEUE。このキー操作をキューに登録して同期的に使用します。ディスプレイが有効な場合はリカバリ メニュー システムによって行われます)

CheckKey() はキーダウン イベントの後に同じキーのキーアップ イベントが発生するたびに呼び出されます(イベント A-down B-down B-up A-up という連続の場合、CheckKey(B) のみで呼び出されます)。CheckKey() IsKeyPressed() を呼び出し、他のキーが押されているかどうかを調べます(上記の一連のキーイベントでは、CheckKey(B) IsKeyPressed(A) を呼び出した場合、true を返します)。

CheckKey() はクラスで状態を維持できます。キーのシーケンスを検出するのに便利です。次の例は少し複雑ですが、電源ボタンを押しながら音量大を押すとディスプレイが切り替わり、電源ボタンを(途中で他のキーを押さずに)連続して 5 回押すとデバイスが再起動します。

class TardisUI : public ScreenRecoveryUI {
  private:
    int consecutive_power_keys;

  public:
    TardisUI() : consecutive_power_keys(0) {}

    virtual KeyAction CheckKey(int key) {
        if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
            return TOGGLE;
        }
        if (key == KEY_POWER) {
            ++consecutive_power_keys;
            if (consecutive_power_keys >= 5) {
                return REBOOT;
            }
        } else {
            consecutive_power_keys = 0;
        }
        return ENQUEUE;
    }
};

ScreenRecoveryUI

ScreenRecoveryUI で独自の画像(エラーアイコン、インストール アニメーション、進行状況バー)を使用する場合、animation_fps 変数を設定して、アニメーションのフレーム/秒(FPS)を調整できます。

注: 現在の interlace-frames.py スクリプトを使用すると、animation_fps 情報を画像自体に保存できます。以前のバージョンの Android では、animation_fps を自分で設定する必要がありました。

animation_fps 変数を設定するには、サブクラスで ScreenRecoveryUI::Init() 関数をオーバーライドします。値を設定して、parent Init() 関数を呼び出して初期化を完了します。デフォルト値(20 FPS)は、デフォルトのリカバリ イメージに対応します。これらの画像を使用する場合、Init() 関数を使用する必要はありません。画像の詳細については、リカバリ UI 画像をご覧ください。

Device クラス

RecoveryUI の実装を作成したら、Device クラスを定義します(組み込みの Device クラスからサブクラス化されています)。UI クラスの単一のインスタンスを作成し、次のように GetUI() 関数から返すようにします。

class TardisDevice : public Device {
  private:
    TardisUI* ui;

  public:
    TardisDevice() :
        ui(new TardisUI) {
    }

    RecoveryUI* GetUI() { return ui; }

StartRecovery

StartRecovery() メソッドは、リカバリの開始時、UI が初期化され、かつ引数が解析された後、アクションが発生する前に呼び出されます。デフォルトの実装では何もしないので、必要がない場合はサブクラスで指定する必要はありません。

   void StartRecovery() {
       // ... do something tardis-specific here, if needed ....
    }

リカバリ メニューの提供と管理

ヘッダー行と項目リストの一覧を取得するため、システムは 2 つのメソッドを呼び出します。この実装では、ファイルの先頭に定義された静的配列を返します。

const char* const* GetMenuHeaders() { return HEADERS; }
const char* const* GetMenuItems() { return ITEMS; }

HandleMenuKey

次に、HandleMenuKey() 関数で、キー操作と現在のメニューの公開設定を取得し、実行する操作を決定します。

   int HandleMenuKey(int key, int visible) {
        if (visible) {
            switch (key) {
              case KEY_VOLUMEDOWN: return kHighlightDown;
              case KEY_VOLUMEUP:   return kHighlightUp;
              case KEY_POWER:      return kInvokeItem;
            }
        }
        return kNoAction;
    }

メソッドはキーコード(以前に処理済みで、UI オブジェクトの CheckKey() メソッドによってキューに加えられたコード)と、メニューまたはテキストログの現在の公開設定を取得します。戻り値は整数です。値が 0 以上の場合は、直後に起動するメニュー項目の位置となります。以下の InvokeMenuItem() メソッドをご覧ください。それ以外の場合は、以下に示す定義済みの定数のいずれかになります。

  • kHighlightUp。メニューのハイライトを前の項目に移動します
  • kHighlightDown。メニューのハイライトを次の項目に移動します
  • kInvokeItem。現在ハイライトされている項目を起動します
  • kNoAction。このキーを押しても何も行われません

visible 引数があることからわかるように、HandleMenuKey() はメニューが表示されていない場合でも呼び出されます。CheckKey() とは異なり、データのワイプやパッケージのインストールなどのリカバリ中には呼び出されません。リカバリがアイドル状態で入力待ちの場合にのみ呼び出されます。

トラックボールのメカニズム

デバイスがトラックボール型の入力メカニズムを持つ場合(EV_REL タイプで REL_Y コードの入力イベントを生成する場合)、トラックボール型入力デバイスが Y 軸方向の動きを伝えると、リカバリは KEY_UP キー入力と KEY_DOWN キー入力を合成します。必要な作業は、KEY_UP と KEY_DOWN のイベントをメニュー アクションにマッピングすることだけです。このマッピングは CheckKey() では起こらないので、トラックボールの動きを再起動や表示の切り替えのトリガーとして使用することはできません。

修飾キー

修飾キーとして押されているキーを確認するには、独自の UI オブジェクトの IsKeyPressed() メソッドを呼び出します。たとえば、一部のデバイスでは、リカバリ中に Alt+W キーを押すと、メニューが表示されたかどうかにかかわらず、データの消去が開始されます。次のように実装できます。

   int HandleMenuKey(int key, int visible) {
        if (ui->IsKeyPressed(KEY_LEFTALT) && key == KEY_W) {
            return 2;  // position of the "wipe data" item in the menu
        }
        ...
    }

注: visible が false の場合、ユーザーがハイライトを表示できないため、ハイライトの移動やハイライト項目の起動などのメニューを操作する特殊な値を返すのは意味がありません。ただし、必要に応じて値を返すこともできます。

InvokeMenuItem

次に、InvokeMenuItem() メソッドを使用して、GetMenuItems() により返された項目の配列内の整数位置をアクションにマッピングします。tardis の例にある項目の配列の場合、次のものを使用します。

   BuiltinAction InvokeMenuItem(int menu_position) {
        switch (menu_position) {
          case 0: return REBOOT;
          case 1: return APPLY_ADB_SIDELOAD;
          case 2: return WIPE_DATA;
          case 3: return WIPE_CACHE;
          default: return NO_ACTION;
        }
    }

このメソッドは、BuiltinAction 列挙型の任意のメンバーを返して、システムにアクションを要求します。システムがアクションを実行しないようにするには、NO_ACTION メンバーを返します。ここではシステムの機能に加えてリカバリの追加機能を使用できます。メニューに項目を追加したり、メニュー項目が呼び出されたときにここで実行したり、NO_ACTION を返してシステムが何もしないようにしたりします。

BuiltinAction には次の値が含まれます。

  • NO_ACTION。何もしません。
  • REBOOT。リカバリを終了し、デバイスを通常どおり再起動します。
  • APPLY_EXT、APPLY_CACHE、APPLY_ADB_SIDELOAD。さまざまな場所からアップデート パッケージをインストールします。詳しくは、サイドローディングをご覧ください。
  • WIPE_CACHE。キャッシュ パーティションのみを再フォーマットします。比較的無害であるため、確認は必要ありません。
  • WIPE_DATA。ユーザーデータ パーティションとキャッシュ パーティションを再フォーマットします。データの初期化とも呼びます。ユーザーがこの操作を続行するには確認が必要です。

最後のメソッドの WipeData() はオプションで、データワイプ操作が開始されるたびに呼び出されます。メニューからのデータの復元や、メインシステムからのデータの初期化が選択されたときに呼び出されます。このメソッドは、ユーザーデータ パーティションとキャッシュ パーティションがワイプされる前に呼び出されます。デバイスがこの 2 つのパーティション以外の場所にユーザーデータを保存している場合は、ここでデータを消去します。成功の場合は 0、失敗の場合は別の値が返されますが、現時点ではこの戻り値は無視されます。成功したか失敗したかにかかわらず、ユーザーデータ パーティションとキャッシュ パーティションはワイプされます。

   int WipeData() {
       // ... do something tardis-specific here, if needed ....
       return 0;
    }

Device の作成

最後に、recovery_ui.cpp ファイルの最後のボイラープレートを make_device() に加え、Device クラスのインスタンスを作成して返します。

class TardisDevice : public Device {
   // ... all the above methods ...
};

Device* make_device() {
    return new TardisDevice();
}

recovery_ui.cpp ファイルを作成したら、ビルドしてからデバイスのリカバリにリンクします。Android.mk で、次の C++ ファイルのみを含む静的ライブラリを作成します。

device/yoyodyne/tardis/recovery/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES += bootable/recovery
LOCAL_SRC_FILES := recovery_ui.cpp

# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
LOCAL_MODULE := librecovery_ui_tardis

include $(BUILD_STATIC_LIBRARY)

次に、このデバイスのボード構成で、静的ライブラリを TARGET_RECOVERY_UI_LIB の値として指定します。

device/yoyodyne/tardis/BoardConfig.mk
 [...]

# device-specific extensions to the recovery UI
TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis

リカバリ UI の画像

リカバリ ユーザー インターフェースは画像で構成されます。原則として、ユーザーは UI で操作を行いません。通常のアップデートでは、スマートフォンはリカバリで起動し、インストールの進行状況バーが埋まると、ユーザーの入力がなくても新しいシステムが起動します。システム アップデートで問題が発生した場合は、カスタマーケア担当者にご連絡ください。

画像のみのインターフェースは、ローカライズの必要性がありません。ただし、Android 5.0 の場合、アップデートでは画像とともに「システム アップデートをインストールしています...」などの文字列が表示されます。詳しくは、ローカライズ済みリカバリ テキストをご覧ください。

Android 5.0 以降

Android 5.0 以降のリカバリ UI では、エラー画像とインストール アニメーションの 2 つの画像を使用します。

OTA エラー中に表示される画像

図 1. icon_error.png

OTA インストール中に表示される画像

図 2. icon_installing.png

インストール アニメーションは、異なるアニメーションのフレームが行でインターレースされた 1 つの PNG 画像で表されます。そのため、図 2 は、つぶれたように見えます。たとえば、200x200 で 7 フレームのアニメーションの場合、200x1400 の画像が 1 つ作成され、最初のフレームは行 0、7、14、21、...となり、2 番目のフレームは行 1、8、15、22、...となります。合成画像には、アニメーション フレーム数とフレーム/秒(FPS)を示すテキスト チャンクが含まれます。ツール bootable/recovery/interlace-frames.py は一連の入力フレームを取り込み、リカバリでの使用に必要な合成画像に結合します。

デフォルト画像はさまざまな密度で使用でき、bootable/recovery/res-$DENSITY/images に配置されます(例: bootable/recovery/res-hdpi/images)。インストール時に静止画像を使用するには、icon_installing.png 画像のみを指定し、アニメーションのフレーム数を 0 に設定します。エラーアイコンはアニメーションではなく常に静止画像です。

Android 4.x 以前

Android 4.x 以前のリカバリ UI では、上記のエラー画像とインストール アニメーションに加え、次のような複数のオーバーレイ画像を使用します。

OTA インストール中に表示される画像

図 3. icon_installing.png

最初のオーバーレイとして表示される画像

図 4. icon-installing_overlay01.png

7 番目のオーバーレイとして表示される画像

図 5. icon_installing_overlay07.png

インストール時には、画面上に icon_installing.png イメージを描画し、その上にオーバーレイ フレームの 1 つを適切なオフセットで描画します。以下の例では赤色の四角を重ねて、ベース画像の上にオーバーレイが配置された部分をハイライトしています。

インストールと最初のオーバーレイの合成画像

図 6. インストール アニメーションのフレーム 1(icon_installing.png + icon_installing_overlay01.png)

インストールと 7 番目のオーバーレイの合成画像

図 7. インストール アニメーションのフレーム 7(icon_installing.png + icon_installing_overlay07.png)

後続のフレームは次のオーバーレイ画像だけを現在の画像の上に描画することによって表示されます。ベース画像は再描画されません。

アニメーション内のフレーム数、速度、ベースに対するオーバーレイの x 軸と y 軸のオフセットは、ScreenRecoveryUI クラスのメンバー変数で設定します。デフォルト画像の代わりにカスタム画像を使用する場合は、サブクラス内で Init() メソッドをオーバーライドして、カスタム画像にこれらの値を変更します。詳細は、ScreenRecoveryUI をご覧ください。スクリプト bootable/recovery/make-overlay.py は、一連のイメージ フレームをリカバリで必要な「ベース画像 + オーバーレイ画像」の形に変換するのに役立ちます。この変換には必要なオフセットの計算を含みます。

デフォルト画像は bootable/recovery/res/images にあります。インストール時に静止画像を使用するには、icon_installing.png 画像のみを指定し、アニメーションのフレーム数を 0 に設定します。エラーアイコンはアニメーションではなく常に静止画像です。

ローカライズ済みリカバリ テキスト

Android 5.x では画像とともに「システムアップデートをインストールしています...」などの文字列が表示されます。メインシステムがリカバリに起動すると、ユーザーの現在のロケールがコマンドライン オプションとしてリカバリに渡されます。表示するメッセージごとに、リカバリには各ロケールのメッセージのテキスト文字列が事前にレンダリングされた 2 つ目の合成画像が含まれます。

リカバリ テキスト文字列のサンプル画像は次のとおりです。

リカバリ テキストの画像

図 8. ローカライズ済みリカバリ メッセージのテキスト

リカバリ テキストには次のメッセージが表示されます。

  • システム アップデートをインストールしています...
  • エラー
  • 消去しています...(データの消去または初期化を行う場合)
  • コマンドが指定されていません(ユーザーが手動でリカバリを起動した場合)

bootable/recovery/tools/recovery_l10n/ にある Android アプリは、メッセージのローカライズを行い、合成画像を作成します。このアプリの使用方法の詳細については、bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java にあるコメントを参照してください。

ユーザーが手動でリカバリを起動した場合、ロケールは使用できず、テキストも表示されません。リカバリ プロセスではテキスト メッセージを重要なものにしないでください。

注: ログメッセージを表示し、メニューから操作を選択できる非表示インターフェースは、英語でのみ提供されています。

進行状況バー

進行状況バーは、メインの画像またはアニメーションの下に表示されます。進行状況バーは、サイズが同じ 2 つの入力画像を結合することで作成されます。

進行状況バーが空の状態

図 9. progress_empty.png

進行状況バーが埋まった状態

図 10. progress_fill.png

fill 画像の左端は empty 画像の右端の横に表示され、進行状況バーになります。2 つの画像の境界位置が変化することで進行状況を示します。たとえば、上記の入力画像ペアでは、次のように表示されます。

進行状況バー: 1%

図 11. 進行状況バー: 1% 未満

進行状況バー: 10%

図 12. 進行状況バー: 10%

進行状況バー: 50%

図 13. 進行状況バー: 50%

こうした画像のデバイス固有のバージョンを使用する場合、(この例では)device/yoyodyne/tardis/recovery/res/images に画像を置きます。ファイル名は上記と一致させる必要があります。ビルドシステムはそのディレクトリでファイルを見つけると、対応するデフォルト イメージに優先して使用します。8 ビットの色深度の RGB または RGBA 形式の PNG のみがサポートされます。

注: Android 5.x では、リカバリがロケールを認識し、その言語が右から左に記述する言語(アラビア語、ヘブライ語など)の場合、進捗バーは右から左に進みます。

画面のないデバイス

一部の Android デバイスには画面がありません。デバイスがヘッドレス器具またはオーディオ専用インターフェースの場合は、リカバリ UI をさらに広範囲にカスタマイズする必要があります。ScreenRecoveryUI のサブクラスを作成する代わりに、親クラスの RecoveryUI を直接サブクラス化します。

RecoveryUI には、「表示を切り替える」、「進行状況バーを更新する」、「メニューを表示する」、「メニュー選択を変更する」などの、下位の UI 操作を処理するメソッドがあります。これらをオーバーライドすることでデバイスの適切なインターフェースを使用できます。デバイスによっては LED のさまざまな色や点滅パターンを使って状態を表示することや、音声を再生することが可能です。メニューも「テキスト表示」モードもサポートしないようにする場合は、これらの機能にアクセスできないように、CheckKey() および HandleMenuKey() 実装を使用して、表示をオンにしたりメニュー項目を選択することができないようにします。この場合、RecoveryUI メソッドの多くは、空のスタブのみで十分です。

どのメソッドをサポートする必要があるかを確認するには、RecoveryUI の宣言の bootable/recovery/ui.h を参照してください。RecoveryUI は抽象的で、いくつかのメソッドは純粋に仮想であり、サブクラスで提供する必要がありますが、キー入力の処理を行うコードを持っています。デバイスがキーを持っていない場合や、別の方法で処理したい場合は、オーバーライドできます。

アップデータ

アップデータ スクリプト内から呼び出すことができる独自の拡張機能を提供することで、アップデート パッケージのインストールにデバイス固有のコードを使用できます。次に、tardis デバイスの関数の例を示します。

device/yoyodyne/tardis/recovery/recovery_updater.c
#include <stdlib.h>
#include <string.h>

#include "edify/expr.h"

すべての拡張機能には同じ署名があります。引数は、関数が呼び出されたときの名前、State* Cookie、引数の数、およびその引数を表す Expr* ポインタの配列です。戻り値は、新たに割り当てられた Value* です。

Value* ReprogramTardisFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc != 2) {
        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
    }

引数は、関数が呼び出された時点では評価されていません。関数の論理で評価される引数と評価の回数が決まります。このように拡張関数を使用して独自の制御構造を実装できます。Call Evaluate()Expr* 引数を評価し、Value* を返します。Evaluate() が NULL を返した場合、保持しているリソースを開放し、すぐに NULL を返します。これにより、edify スタックまで中止が伝達されます。それ以外の場合は、返された Value 所有権を取得し、最終的に FreeValue() が呼び出されるようにします。

関数が文字列値 key と blob 値 image の 2 つの引数を必要とするとします。次のようにして引数を読み取ることができます。

   Value* key = EvaluateValue(state, argv[0]);
    if (key == NULL) {
        return NULL;
    }
    if (key->type != VAL_STRING) {
        ErrorAbort(state, "first arg to %s() must be string", name);
        FreeValue(key);
        return NULL;
    }
    Value* image = EvaluateValue(state, argv[1]);
    if (image == NULL) {
        FreeValue(key);    // must always free Value objects
        return NULL;
    }
    if (image->type != VAL_BLOB) {
        ErrorAbort(state, "second arg to %s() must be blob", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

NULL をチェックし、以前に評価された引数を解放する作業は、引数が複数ある場合には面倒です。ReadValueArgs() 関数を使用するとこの作業を簡単にできます。上記のコードの代わりに、次のように記述します。

   Value* key;
    Value* image;
    if (ReadValueArgs(state, argv, 2, &key, &image) != 0) {
        return NULL;     // ReadValueArgs() will have set the error message
    }
    if (key->type != VAL_STRING || image->type != VAL_BLOB) {
        ErrorAbort(state, "arguments to %s() have wrong type", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

ReadValueArgs() は型チェックを行わないため、ここでチェックする必要があります。この作業は 1 つの if ステートメントで行う方が便利です。ただし、失敗した場合に表示されるエラー メッセージはやや具体性に欠けることになります。一方、ReadValueArgs() は各引数を評価し、いずれかの評価が失敗した場合には、以前に評価したすべての引数を解放する処理を行います(有用なエラー メッセージの設定も行います)。一定でない数の引数を評価する場合は、 ReadValueVarArgs() 簡易関数を使用できます(Value* の配列が返されます)。

引数を評価したら、次の関数を実行します。

   // key->data is a NUL-terminated string
    // image->data and image->size define a block of binary data
    //
    // ... some device-specific magic here to
    // reprogram the tardis using those two values ...

戻り値は Value* オブジェクトである必要があります。このオブジェクトの所有権は呼び出し元に渡されます。呼び出し元は、この Value* で指定されたデータの所有権を取得します。具体的には datamember です。

この例では、成功を示すために true または false の値を返します。空の文字列は false で、他のすべての文字列は true となります。malloc の定数文字列と一緒に Value オブジェクトを malloc して返す必要があります。呼び出し元は両方に対し free() を行うためです。引数を評価して取得したオブジェクトに対して必ず FreeValue() を呼び出してください。

   FreeValue(key);
    FreeValue(image);

    Value* result = malloc(sizeof(Value));
    result->type = VAL_STRING;
    result->data = strdup(successful ? "t" : "");
    result->size = strlen(result->data);
    return result;
}

簡易関数 StringValue() により文字列を新しい Value オブジェクトにラップします。次のように上記のコードをより簡潔に記述します。

   FreeValue(key);
    FreeValue(image);

    return StringValue(strdup(successful ? "t" : ""));
}

関数を edify インタープリタにフックするには、関数 Register_foo を使用します。ここで、foo はこのコードがある静的ライブラリの名前です。RegisterFunction() を呼び出し、各拡張関数を登録します。通常は、将来追加される組み込み関数と競合しないように、デバイス固有の関数 device.whatever には名前を付けます。

void Register_librecovery_updater_tardis() {
    RegisterFunction("tardis.reprogram", ReprogramTardisFn);
}

コードで静的ライブラリをビルドするように makefile を構成できるようになりました。これは、前のセクションでリカバリ UI をカスタマイズするのに使用された makefile と同じです。デバイスにはここで定義されている両方の静的ライブラリが存在する場合があります。

device/yoyodyne/tardis/recovery/Android.mk
include $(CLEAR_VARS)
LOCAL_SRC_FILES := recovery_updater.c
LOCAL_C_INCLUDES += bootable/recovery

静的ライブラリの名前は、その中に含まれる Register_libname 関数の名前と一致している必要があります。

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

最後に、リカバリのビルドを構成してライブラリを pull します。TARGET_RECOVERY_UPDATER_LIBS にライブラリを追加します(複数のライブラリを追加した場合はそのすべてが登録されます)。コードが、それ自体が edify 拡張ではない(Register_libname 関数がない)他の静的ライブラリに依存している場合は、TARGET_RECOVERY_UPDATER_EXTRA_LIBS 内の静的ライブラリをリストすると、(存在しない)登録関数を呼び出さずにアップデータにリンクできます。たとえば、デバイス固有のコードで zlib を使用してデータを解凍する場合、libz をここに含めます。

device/yoyodyne/tardis/BoardConfig.mk
 [...]

# add device-specific extensions to the updater binary
TARGET_RECOVERY_UPDATER_LIBS += librecovery_updater_tardis
TARGET_RECOVERY_UPDATER_EXTRA_LIBS +=

これで、OTA パッケージのアップデータ スクリプトは、他の関数と同じように目的の関数を呼び出すことができるようになります。tardis デバイスを再プログラミングするには、アップデート スクリプトに tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) を追加します。これは組み込み関数 package_extract_file() の単一引数バージョンを使用します。これにより、アップデート パッケージから blob として抽出されたファイルの内容を返し、新しい拡張関数に対する 2 番目の引数を生成します。

OTA パッケージの生成

最後に、OTA パッケージ生成ツールを使用してデバイス固有のデータを取得し、拡張関数の呼び出しを含むアップデータ スクリプトを生成します。

まず、ビルドシステムを取得して、デバイス固有の blob データを取得します。データファイルが device/yoyodyne/tardis/tardis.dat にあると仮定した場合、デバイスの AndroidBoard.mk で次のように宣言します。

device/yoyodyne/tardis/AndroidBoard.mk
  [...]

$(call add-radio-file,tardis.dat)

代わりに Android.mk に配置することもできますが、ツリー内のすべての Android.mk ファイルは作成中のデバイスに関係なく読み込まれるため、デバイス チェックで保護する必要があります(ツリーに複数のデバイスが含まれている場合、tardis デバイスの作成時に追加する必要があるのは tardis.dat ファイルのみです)。

device/yoyodyne/tardis/Android.mk
  [...]

# an alternative to specifying it in AndroidBoard.mk
ifeq (($TARGET_DEVICE),tardis)
  $(call add-radio-file,tardis.dat)
endif

これらは、歴史的な理由からラジオファイルと呼ばれます。デバイスにラジオがあったとしても、それとは関係はありません。これは、OTA 生成ツールで使用される target-files.zip にビルドシステムがコピーするデータの、不透明な blob です。ビルドを実行すると、tardis.dat は target-files.zip に RADIO/tardis.dat として格納されます。add-radio-file を複数回呼び出し、必要な数のファイルを追加します。

Python モジュール

リリースツールを拡張するには、ツールが呼び出し可能な Python モジュール(名前を releasetools.py とします)を書き込む必要があります。例:

device/yoyodyne/tardis/releasetools.py
import common

def FullOTA_InstallEnd(info):
  # copy the data into the package.
  tardis_dat = info.input_zip.read("RADIO/tardis.dat")
  common.ZipWriteStr(info.output_zip, "tardis.dat", tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

増分 OTA パッケージの生成は、別の関数で処理します。この例では、tardis.dat ファイルが 2 つのビルドの間で変更された場合にのみ、tardis を再プログラムする必要があるものとします。

def IncrementalOTA_InstallEnd(info):
  # copy the data into the package.
  source_tardis_dat = info.source_zip.read("RADIO/tardis.dat")
  target_tardis_dat = info.target_zip.read("RADIO/tardis.dat")

  if source_tardis_dat == target_tardis_dat:
      # tardis.dat is unchanged from previous build; no
      # need to reprogram it
      return

  # include the new tardis.dat in the OTA package
  common.ZipWriteStr(info.output_zip, "tardis.dat", target_tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

モジュール関数

モジュールでは、次の関数を使用できます。必要な関数のみを実装してください。

FullOTA_Assertions()
フル OTA の生成開始時に呼び出されます。これは、デバイスの現在の状態に関するアサーションを生成するのに適しています。デバイスに変更を加えるスクリプト コマンドは送信しないでください。
FullOTA_InstallBegin()
デバイスの状態に関するすべてのアサーションが成功した後、変更が行われる前に呼び出されます。デバイス固有のアップデート コマンドを発行できますが、デバイス上のその他の変更が行われる前に実行する必要があります。
FullOTA_InstallEnd()
スクリプト生成の終了時に、boot および system パーティションを更新するスクリプト コマンドが発行された後に呼び出されます。また、デバイス固有のアップデートのために追加のコマンドを発行することもできます。
IncrementalOTA_Assertions()
FullOTA_Assertions() と同様に、増分のアップデート パッケージを生成するときに呼び出されます。
IncrementalOTA_VerifyBegin()
デバイスの状態に関するすべてのアサーションが成功した後、変更が行われる前に呼び出されます。デバイス固有のアップデート コマンドを発行できますが、デバイス上のその他の変更が行われる前に実行する必要があります。
IncrementalOTA_VerifyEnd()
検証フェーズの最後に、スクリプトがタッチするファイルが想定どおりのコンテンツを開始するかどうかの確認を完了したときに呼び出されます。この時点では、デバイス上で変更されたものはありません。また、デバイス固有の検証を行うためのコードを発行することもできます。
IncrementalOTA_InstallBegin()
パッチを適用するファイルが想定どおり before 状態であるかを検証された後、変更が行われる前に呼び出されます。デバイス固有のアップデート コマンドを発行できますが、デバイス上のその他の変更が行われる前に実行する必要があります。
IncrementalOTA_InstallEnd()
フル OTA パッケージの同等の関数と同様に、ブートおよびシステム パーティションを更新するためのスクリプト コマンドが発行された後、スクリプト生成の最後に呼び出されます。また、デバイス固有のアップデートのために追加のコマンドを発行することもできます。

注: デバイスの電源が切れた場合、OTA のインストールは最初からやり直しになる場合があります。これらのコマンドがすでに部分的にでも実行されているデバイスについては注意が必要です。

関数を info オブジェクトに渡す

さまざまな便利な項目がある単一の info オブジェクトに関数を渡します。

  • info.input_zip。(フル OTA のみ)入力 target-files .zip の zipfile.ZipFile オブジェクト。
  • info.source_zip。(増分 OTA のみ)ソースとなる target-files .zip の zipfile.ZipFile オブジェクト。増分パッケージのインストール中に、すでにデバイスにビルドされています。
  • info.target_zip。(増分 OTA のみ)ターゲットとなる target-files .zip の zipfile.ZipFile オブジェクト。増分パッケージによりデバイスにビルドされます。
  • info.output_zip。作成中のパッケージ。書き込み用にオープンされた zipfile.ZipFile オブジェクトです。パッケージにファイルを追加するには、common.ZipWriteStr(info.output_zip、filenamedata)を使用します。
  • info.script。コマンドを追加できる Script オブジェクトです。info.script.AppendExtra(script_text) を呼び出してテキストをスクリプトに出力します。出力テキストの末尾にはセミコロンを入力し、その後に発行されるコマンドと一緒にならないようにしてください。

info オブジェクトの詳細は、Python ソフトウェア財団の ZIP アーカイブ用ドキュメントをご覧ください。

モジュールの場所を指定する

BoardConfig.mk ファイル内で、デバイスの releasetools.py スクリプトの場所を指定します。

device/yoyodyne/tardis/BoardConfig.mk
 [...]

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

TARGET_RELEASETOOLS_EXTENSIONS が設定されていない場合、デフォルトは $(TARGET_DEVICE_DIR)/../common ディレクトリです(この例では device/yoyodyne/common )。releasetools.py スクリプトの場所は、明示的に定義することをおすすめします。tardis デバイスをビルドする際、releasetools.py スクリプトは target-files .zip ファイル(META/releasetools.py )に含まれます。

リリースツール(img_from_target_files または ota_from_target_files)を実行すると、target-files .zip 内に releasetools.py スクリプトがある場合は Android ソースツリーよりも優先されます。-s(または --device_specific)オプションを使用してデバイス固有の拡張機能へのパスを明示的に指定することもできます。この指定は最優先されます。これにより、releasetools 拡張機能でエラーを修正し、変更を加え、その変更を古いターゲット ファイルに適用できます。

これで、ota_from_target_files を実行した際に、target_files .zip ファイルからデバイス固有のモジュールを自動的に取得し、それを OTA パッケージの生成時に使用します。

./build/make/tools/releasetools/ota_from_target_files \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

別の方法として、ota_from_target_files の実行時にデバイス固有の拡張機能を指定することもできます。

./build/make/tools/releasetools/ota_from_target_files \
    -s device/yoyodyne/tardis \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

注: 全オプションの一覧については build/make/tools/releasetools/ota_from_target_filesota_from_target_files コメントをご覧ください。

サイドローディング

リカバリ パッケージには、メインシステムからの無線でのダウンロードを行わずに手動でアップデート パッケージをインストールするサイドローディング メカニズムがあります。サイドローディングは、メインシステムを起動できないデバイスでデバッグや変更を行う場合に便利です。

以前は、デバイスの SD カードからパッケージを読み込むことでサイドローディングが行われていました。起動していないデバイスの場合は、別のパソコンを使用してパッケージを SD カードに入れ、その SD カードをデバイスに挿入していました。リムーバブル外部ストレージを持たない Android デバイスに対応するため、リカバリでは、キャッシュ パーティションからのパッケージの読み込みと、adb を使用した USB 経由の読み込みの 2 つのメカニズムが追加でサポートされました。

サイドロードの各メカニズムを呼び出すため、デバイスの Device::InvokeMenuItem() メソッドは BuiltinAction の次の値を返します。

  • APPLY_EXT。外部ストレージ( /sdcard ディレクトリ)からアップデート パッケージをサイドローディングします。recovery.fstab には /sdcard のマウント ポイントを定義する必要があります。/data へのシンボリック リンク(または同様のメカニズム)で SD カードをエミュレートするデバイスでは使用できません。/data は暗号化されている可能性があるため、通常はリカバリには使用できません。リカバリ UI には、/sdcard にある .zip ファイルのメニューが表示され、ユーザーはその中の 1 つを選択できます。
  • APPLY_CACHE/sdcard からのパッケージの読み込みとほぼ同様です。ただし、/cache ディレクトリ(リカバリには常に使用可能)が代わりに使用されています。通常のシステムから /cache へは特権ユーザーのみが書き込み可能で、デバイスが起動できない場合は /cache ディレクトリへの書き込みはまったくできません。このためこのメカニズムの有用性は制限されます。
  • APPLY_ADB_SIDELOAD。USB ケーブルと adb 開発ツールを使用してデバイスにパッケージを送信できます。このメカニズムが呼び出されると、リカバリは adbd デーモンの mini バージョンを起動し、接続されたホスト コンピュータ上の adb が通信できるようにします。この mini バージョンでサポートされるコマンドは adb sideload filename 1 つのみです。指定されたファイルは、ホストマシンからデバイスに送信されます。デバイスでは、ローカル ストレージにあった場合と同様にこのファイルが検証され、インストールされます。

次のような点にご注意ください。

  • USB 転送のみがサポートされています。
  • リカバリで adbd が正常に実行された場合(userdebug および eng ビルドでは通常そのようになります)、デバイスは adb サイドロード モードでシャットダウンされ、adb サイドロードがパッケージの受信を終了すると再起動します。adb サイドロード モードの場合、sideload コマンドだけが有効です。それ以外の adb コマンド(logcatrebootpushpullshell など)はすべて失敗します。
  • デバイスで adb サイドロード モードを終了することはできません。中止するには、/dev/null などの無効なパッケージをパッケージとして送信すると、デバイスが確認に失敗してインストール処理を停止します。RecoveryUI 実装の CheckKey() メソッドがキー入力のために呼び出されるので、デバイスを再起動し adb サイドロード モードで動作するキーシーケンスを提供できます。