Android プラットフォームには、audio config などの構成データを保存するための XML ファイルが多数含まれています。XML ファイルの多くは vendor パーティションに保存されていますが、読み込まれるのは system パーティションです。この場合、XML ファイルのスキーマは 2 つのパーティション間のインターフェースとして機能するため、明示的にスキーマを指定し、下位互換性を持たせる必要があります。
Android 10 より前のプラットフォームには、XML スキーマの指定と使用を要求するメカニズムや、互換性が失われるスキーマ変更を防ぐメカニズムはありませんでした。Android 10 には、そのようなメカニズムとして Config File Schema API が用意されています。このメカニズムは、xsdc というツールと xsd_config というビルドルールで構成されています。
xsdc ツールは、XML スキーマ ドキュメント(XSD)コンパイラです。XML ファイルのスキーマを記述した XSD ファイルを解析し、Java と C++ のコードを生成します。生成されたコードは、XSD スキーマに準拠する XML ファイルを、XML タグをモデル化したオブジェクトのツリーに解析して変換します。XML 属性は、オブジェクトのフィールドとしてモデル化されます。
xsd_config ビルドルールは、xsdc ツールをビルドシステムに統合します。
与えられた XSD 入力ファイルに対して、ビルドルールは Java と C++ のライブラリを生成します。XSD に準拠する XML ファイルが読み取られて使用されるモジュールにライブラリをリンクできます。system パーティションと vendor パーティションで使用される独自の XML ファイルにビルドルールを使用できます。
Config File Schema API のビルド
ここでは、Config File Schema API をビルドする方法について説明します。
Android.bp で xsd_config ビルドルールを構成する
xsd_config ビルドルールは、xsdc ツールでパーサーコードを生成します。xsd_config ビルドルールの package_name プロパティにより、生成される Java コードのパッケージ名が決定されます。
Android.bp の xsd_config ビルドルールの例は以下のとおりです。
xsd_config {
    name: "hal_manifest",
    srcs: ["hal_manifest.xsd"],
    package_name: "hal.manifest",
}
ディレクトリ構造の例は以下のとおりです。
├── Android.bp
├── api
│   ├── current.txt
│   ├── last_current.txt
│   ├── last_removed.txt
│   └── removed.txt
└── hal_manifest.xsd
ビルドシステムは、生成された Java コードを使用して API リストを生成し、そのリストを使って API をチェックします。この API チェックは DroidCore に追加され、m -j で実行されます。
API リストファイルの作成
API チェックには、ソースコード内に API リストファイルが必要です。
API リストファイルには次のファイルが含まれます。
current.txtとremoved.txtは、ビルド時に生成された API ファイルとの比較により、API が変更されているかどうかをチェックします。last_current.txtとlast_removed.txtは、API ファイルとの比較により、API に下位互換性があるかどうかをチェックします。
API リストファイルを作成するには:
- 空のリストファイルを作成します。
 make update-apiコマンドを実行します。
生成されたパーサーコードの使用
生成された Java コードを使用するには、Java の srcs プロパティに含まれる xsd_config モジュール名にプレフィックスとして : を追加します。生成された Java コードのパッケージは package_name プロパティと同じです。
java_library {
    name: "vintf_test_java",
    srcs: [
        "srcs/**/*.java"
        ":hal_manifest"
    ],
}
生成された C++ コードを使用するには、generated_sources プロパティと generated_headers プロパティに xsd_config モジュール名を追加します。また、生成されたパーサーコードには libxml2 が必要であるため、libxml2 を static_libs または shared_libs に追加します。生成された C++ コードの名前空間は package_name プロパティと同じです。たとえば、xsd_config モジュール名が hal.manifest の場合、名前空間は hal::manifest です。
cc_library{
    name: "vintf_test_cpp",
    srcs: ["main.cpp"],
    generated_sources: ["hal_manifest"],
    generated_headers: ["hal_manifest"],
    shared_libs: ["libxml2"],
}
パーサーの使用
Java パーサーコードを使用するには、XmlParser#read または read{class-name} メソッドを使ってルート要素のクラスを返します。この時点で解析が行われます。
import hal.manifest.*;
…
class HalInfo {
    public String name;
    public String format;
    public String optional;
    …
}
void readHalManifestFromXml(File file) {
    …
    try (InputStream str = new BufferedInputStream(new FileInputStream(file))) {
        Manifest manifest = XmlParser.read(str);
        for (Hal hal : manifest.getHal()) {
            HalInfo halinfo;
            HalInfo.name = hal.getName();
            HalInfo.format = hal.getFormat();
            HalInfo.optional = hal.getOptional();
            …
        }
    }
    …
}
C++ パーサーコードを使用するには、まずヘッダー ファイルを含めます。ヘッダー ファイルの名前は、パッケージ名のピリオド(.)をアンダースコア(_)に変換したものです。次に、read または read{class-name} メソッドを使用して、ルート要素のクラスを返します。この時点で解析が行われます。戻り値は std::optional<> です。
include "hal_manifest.h"
…
using namespace hal::manifest
struct HalInfo {
    public std::string name;
    public std::string format;
    public std::string optional;
    …
};
void readHalManifestFromXml(std::string file_name) {
    …
    Manifest manifest = *read(file_name.c_str());
    for (Hal hal : manifest.getHal()) {
        struct HalInfo halinfo;
        HalInfo.name = hal.getName();
        HalInfo.format = hal.getFormat();
        HalInfo.optional = hal.getOptional();
        …
    }
    …
}
パーサーを使用するために用意されているすべての API は api/current.txt にあります。統一を図るため、すべての要素と属性の名前はキャメルケース(ElementName など)に変換され、対応する変数、メソッド、クラス名として使用されます。解析されたルート要素のクラスは、read{class-name} 関数で取得できます。ルート要素が 1 つしかない場合、関数名は read です。解析されたサブ要素または属性の値は、get{variable-name} 関数で取得できます。
パーサーコードの生成
ほとんどの場合、xsdc を直接実行する必要はありません。代わりに xsd_config ビルドルールを使用します。これについては、Android.bp で xsd_config ビルドルールを構成するで説明されています。説明を完全なものにするため、このセクションでは xsdc コマンドライン インターフェースについて補足します。このインターフェースはデバッグに役立ちます。
xsdc ツールに対して XSD ファイルのパスとパッケージを指定する必要があります。Java コードの場合はパッケージ名、C++ コードの場合は名前空間を指定します。Java コードを生成する場合は -j オプション、C++ コードを生成する場合は -c オプションをそれぞれ使用します。-o オプションでは、出力ディレクトリのパスを指定します。
usage: xsdc path/to/xsd_file.xsd [-c] [-j] [-o <arg>] [-p]
 -c,--cpp           Generate C++ code.
 -j,--java          Generate Java code.
 -o,--outDir <arg>  Out Directory
 -p,--package       Package name of the generated java file. file name of
                    generated C++ file and header
コマンドの例は以下のとおりです。
$ xsdc audio_policy_configuration.xsd -p audio.policy -j