AOSP SystemServerに新しいサービスを登録する実践的手法

Android Automotive OS (AAOS) は、車載インフォテインメント (IVI) システムの標準プラットフォームとして急速に普及しています。この強力なプラットフォームの心臓部には、Androidフレームワークの各種サービスを管理・実行するSystemServerプロセスが存在します。標準機能だけでは実現できない独自のハードウェア制御や、特権レベルでのデータアクセスが必要な場合、開発者は独自の「システムサービス」を作成し、このSystemServerに登録する必要があります。しかし、このプロセスはAOSP (Android Open Source Project) の深い知識を要求され、ドキュメントも断片的で多くの開発者が困難に直面します。この記事では、AOSPのソースコードを直接扱う開発者の視点から、Android Automotive OS環境で新しいシステムサービスを定義し、実装し、そしてSystemServerに安全かつモダンな方法で登録するまでの一連のプロセスを、具体的なコード例と詳細な解説と共に徹底的に掘り下げていきます。

このガイドの対象読者: AOSPのビルド経験があり、Androidフレームワークの基本的な構造を理解している中級以上のAndroidエンジニア、またはAndroid Automotiveプラットフォームでカスタム機能を開発する必要がある開発者を対象としています。基本的なJava/Kotlin、C++、およびAndroidのビルドシステム(Soong, Make)に関する知識があることを前提としています。

なぜカスタムシステムサービスが必要なのか?

アプリケーション開発者が通常利用するContext.getSystemService()で取得できるActivityManagerWindowManagerは、すべてSystemServer内で動作するシステムサービスです。では、なぜわざわざ新しいシステムサービスを作る必要があるのでしょうか。主な理由は以下の通りです。

  • 特権アクセス: 一般的なアプリからはアクセスできないハードウェア(例えば、カスタムECU、車両のCANバスデータなど)を直接制御する必要がある場合。システムサービスはsystem_serverコンテキストで動作するため、より広範な権限を持ちます。
  • 一元的な状態管理: 車両全体で共有されるべき状態(例えば、特定の走行モード、ユーザー設定など)を、単一の信頼できる情報源として管理したい場合。
  • プロセス間通信(IPC)の最適化: 複数のアプリやシステムコンポーネントが頻繁にアクセスする必要がある機能を、効率的なBinder IPCを通じて提供したい場合。
  • フレームワークレベルでの機能提供: 特定のアプリに依存せず、OSレベルで常に利用可能な機能を提供したい場合。例えば、カスタムの車両診断機能やテレマティクスデータ収集機能などが挙げられます。

このガイドでは、例として「車両の特定のカスタム設定(例:アンビエントライトの色)を管理する」という架空のサービス、MyVehicleSettingsServiceを作成するプロセスを追っていきます。これにより、理論だけでなく実践的なスキルを習得できます。

ステップ1: AIDLによるサービスインターフェースの設計

システムサービスの最初のステップは、クライアント(他のアプリやサービス)との通信規約を定義することです。Androidでは、プロセス間通信(IPC)のためにAIDL (Android Interface Definition Language) を使用するのが標準的です。AIDLは、Javaに似た構文でインターフェースを定義すると、ビルドシステムが自動的にJavaのスタブコードを生成してくれる強力なツールです。これにより、開発者は面倒なBinder通信のシリアライズ/デシリアライズ処理を意識することなく、ビジネスロジックに集中できます。

1.1 AIDLファイルの作成

まず、サービスのインターフェースを定義する.aidlファイルを作成します。AOSPソースツリー内の適切な場所(例: frameworks/base/core/java/android/car/settings/ のような既存の構造に倣うか、vendor/ ディレクトリ配下の自社プロジェクト領域)にファイルを作成します。

ここでは、IMyVehicleSettingsService.aidl という名前でファイルを作成します。


// ファイルパス: vendor/example/interfaces/automotive/myvehiclesettings/android/example/myvehiclesettings/IMyVehicleSettingsService.aidl

package android.example.myvehiclesettings;

import android.example.myvehiclesettings.AmbientLightColor;

/**
 * MyVehicleSettingsServiceのためのシステムサービスインターフェース。
 * {@hide}
 */
interface IMyVehicleSettingsService {
    /**
     * 現在のアンビエントライトの色を設定します。
     * @param color 設定する色情報を保持するParcelableオブジェクト。
     */
    void setAmbientLightColor(in AmbientLightColor color);

    /**
     * 現在のアンビエントライトの色を取得します。
     * @return 現在の色情報を保持するParcelableオブジェクト。
     */
    AmbientLightColor getAmbientLightColor();

    /**
     * サービスが正常に動作しているかテストするためのping。
     * @return boolean true
     */
    boolean ping();
}

上記のコードでは、setAmbientLightColorgetAmbientLightColorという2つの主要なメソッドを定義しています。引数と戻り値には、カスタムのデータ型であるAmbientLightColorを使用しています。AIDLでは、プリミティブ型(int, booleanなど)、StringListMap以外に、Parcelableを実装したカスタムオブジェクトも送受信できます。

1.2 Parcelableの定義

カスタムオブジェクトを送受信するためには、そのオブジェクトがParcelableインターフェースを実装していることを示す別のAIDLファイルが必要です。AmbientLightColor.aidlを同じディレクトリに作成します。


// ファイルパス: vendor/example/interfaces/automotive/myvehiclesettings/android/example/myvehiclesettings/AmbientLightColor.aidl

package android.example.myvehiclesettings;

/**
 * アンビエントライトの色情報を表すParcelableオブジェクト。
 * {@hide}
 */
parcelable AmbientLightColor;

この宣言により、ビルドシステムはAmbientLightColorという名前のJavaクラスがParcelableを実装していることを認識します。実際のJavaクラスはサービスの実装側で作成します。(後述)

1.3 ビルドシステムへの登録 (Android.bp)

作成したAIDLファイルをビルドシステムに認識させる必要があります。Soongビルドシステムでは、Android.bpファイルを使用します。AIDLファイルを配置したディレクトリにAndroid.bpを作成し、以下のように記述します。


// ファイルパス: vendor/example/interfaces/automotive/myvehiclesettings/Android.bp

// AIDLインターフェースライブラリの定義
aidl_interface {
    name: "my-vehicle-settings-interface",
    vendor: true, // ベンダーパーティションに配置されることを示す
    srcs: [
        "android/example/myvehiclesettings/IMyVehicleSettingsService.aidl",
        "android/example/myvehiclesettings/AmbientLightColor.aidl",
    ],
    api_dir: "api", // APIダンプを保存するディレクトリ
    stability: "vintf", // VINTF (Vendor Interface) の一部として安定性を保証
    backend: {
        java: {
            enabled: true, // Javaバックエンドを有効化
        },
    },
}

// Javaライブラリとしてラップする (オプションだが推奨)
java_library {
    name: "my-vehicle-settings-interface-java",
    vendor: true,
    srcs: [":my-vehicle-settings-interface"], // 上記aidl_interfaceをソースとして指定
}

aidl_interfaceモジュールは、AIDLファイルからJavaスタブコードを生成します。nameプロパティで指定した名前(ここではmy-vehicle-settings-interface)は、後ほどサービスの実装側で依存関係として使用します。

ステップ2: カスタムシステムサービスの実装

インターフェースが定義できたら、次はそのインターフェースの具体的な振る舞いを実装するサービスクラスを作成します。これはAOSP内の適切なモジュール(例: frameworks/opt/car/services/vendor/ 配下のサービス実装用ディレクトリ)に配置します。

2.1 Parcelableクラスの実装

まず、AIDLで宣言したAmbientLightColorのJavaクラスを実装します。


// ファイルパス: vendor/example/services/myvehiclesettingsservice/src/android/example/myvehiclesettings/AmbientLightColor.java

package android.example.myvehiclesettings;

import android.os.Parcel;
import android.os.Parcelable;

public final class AmbientLightColor implements Parcelable {
    private final int r;
    private final int g;
    private final int b;

    public AmbientLightColor(int r, int g, int b) {
        this.r = r;
        this.g = g;
        this.b = b;
    }

    private AmbientLightColor(Parcel in) {
        r = in.readInt();
        g = in.readInt();
        b = in.readInt();
    }

    public int getRed() { return r; }
    public int getGreen() { return g; }
    public int getBlue() { return b; }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(r);
        dest.writeInt(g);
        dest.writeInt(b);
    }

    public static final Parcelable.Creator<AmbientLightColor> CREATOR =
            new Parcelable.Creator<AmbientLightColor>() {
        public AmbientLightColor createFromParcel(Parcel in) {
            return new AmbientLightColor(in);
        }

        public AmbientLightColor[] newArray(int size) {
            return new AmbientLightColor[size];
        }
    };
}

Parcelableの実装は定型的なものですが、writeToParcelでフィールドを書き込み、Parcelable.CreatorでParcelからオブジェクトを復元するロジックを正確に実装する必要があります。

2.2 サービスクラス本体の実装

次に、サービスの本体であるMyVehicleSettingsService.javaを作成します。このクラスは、AIDLから生成されたIMyVehicleSettingsService.Stubを継承します。


// ファイルパス: vendor/example/services/myvehiclesettingsservice/src/com/android/server/example/MyVehicleSettingsService.java

package com.android.server.example;

import android.content.Context;
import android.content.pm.PackageManager;
import android.example.myvehiclesettings.AmbientLightColor;
import android.example.myvehiclesettings.IMyVehicleSettingsService;
import android.os.Process;
import android.util.Slog;

import com.android.server.SystemService;

public class MyVehicleSettingsService extends IMyVehicleSettingsService.Stub {
    private static final String TAG = "MyVehicleSettingsService";
    private static final String PERMISSION_MANAGE_SETTINGS = "android.permission.MANAGE_VEHICLE_SETTINGS";

    private final Context mContext;
    private AmbientLightColor mCurrentColor;

    // このコンストラクタは SystemService のライフサイクルから呼ばれる
    public MyVehicleSettingsService(Context context) {
        mContext = context;
        mCurrentColor = new AmbientLightColor(255, 0, 0); // 初期色は赤
        Slog.i(TAG, "Service instance created.");
    }
    
    // このメソッドは SystemService の onStart フェーズで呼ばれる
    public void onStart() {
        Slog.i(TAG, "Service is starting.");
        // ここでハードウェアの初期化などを行う
    }

    @Override
    public void setAmbientLightColor(AmbientLightColor color) {
        // 権限チェックはシステムサービスにおいて非常に重要
        mContext.enforceCallingOrSelfPermission(PERMISSION_MANAGE_SETTINGS, "setAmbientLightColor");
        
        Slog.d(TAG, "Setting ambient light color to: R=" + color.getRed() +
                ", G=" + color.getGreen() + ", B=" + color.getBlue());
        
        synchronized (this) {
            mCurrentColor = color;
        }
        
        // ここで実際のハードウェア制御ロジックを呼び出す
        // nativeSetHardwareColor(color.getRed(), color.getGreen(), color.getBlue());
    }

    @Override
    public AmbientLightColor getAmbientLightColor() {
        // 読み取り操作にも権限を要求することが望ましい
        mContext.enforceCallingOrSelfPermission(PERMISSION_MANAGE_SETTINGS, "getAmbientLightColor");

        synchronized (this) {
            return mCurrentColor;
        }
    }
    
    @Override
    public boolean ping() {
        return true;
    }
}

セキュリティに関する注意: システムサービスは高い権限で動作するため、公開するメソッドには必ず適切なパーミッションチェックを実装する必要があります。Context.enforceCallingOrSelfPermission()は、呼び出し元のプロセスが必要な権限を持っていない場合にSecurityExceptionをスローし、処理を中断させます。これにより、不正なアプリからのアクセスを防ぎます。

2.3 ビルドファイル (Android.bp)

サービス実装のソースコードをコンパイルし、JARファイルとしてパッケージ化するためのAndroid.bpを作成します。


// ファイルパス: vendor/example/services/myvehiclesettingsservice/Android.bp

java_library {
    name: "MyVehicleSettingsServiceLib",
    vendor: true,
    srcs: [
        "src/**/*.java",
        // ステップ1で定義したAIDLインターフェースへのパス
        // aidlファイルへの直接参照ではなく、生成されたライブラリを参照する
    ],

    libs: [
        "services.core", // SystemServiceなどのコアクラスに依存
        "android.car",   // 車両関連のAPIに依存する場合
    ],
    
    static_libs: [
        "my-vehicle-settings-interface-java", // ステップ1で定義したインターフェースライブラリ
    ],
}

ステップ3: SystemServerへのサービスの組み込み

ここがこの記事の核心部分です。実装したサービスを、OS起動時にSystemServerプロセス内でインスタンス化し、他のコンポーネントから参照できるように登録します。これには、伝統的な方法と、よりモダンで推奨される方法の2つがあります。

3.1 モダンで推奨される方法: `SystemService` の活用

近年のAndroidバージョンでは、サービスのライフサイクル管理を容易にし、SystemServer.javaの肥大化を防ぐためにSystemServiceという抽象クラスが導入されました。このクラスを継承することで、サービスの起動、ブートフェーズごとの処理、終了処理などをきれいに記述できます。

3.1.1 `SystemService` を継承したライフサイクル管理クラスの作成

MyVehicleSettingsService.java とは別に、ライフサイクルを管理するためのクラスを作成します。これはサービス本体をラップする形になります。


// ファイルパス: vendor/example/services/myvehiclesettingsservice/src/com/android/server/example/MyVehicleSettingsServiceLifecycle.java

package com.android.server.example;

import android.content.Context;
import android.os.ServiceManager;
import android.util.Slog;

import com.android.server.SystemService;

public class MyVehicleSettingsServiceLifecycle extends SystemService {
    private static final String TAG = "MyVehicleSettingsServiceLifecycle";
    public static final String SERVICE_NAME = "my_vehicle_settings_service";

    private MyVehicleSettingsService mService;

    public MyVehicleSettingsServiceLifecycle(Context context) {
        super(context);
    }

    @Override
    public void onStart() {
        Slog.i(TAG, "Registering " + SERVICE_NAME);
        mService = new MyVehicleSettingsService(getContext());

        // ServiceManagerにサービスを公開(publish)する。これが「登録」の核心部分。
        try {
            publishBinderService(SERVICE_NAME, mService);
            Slog.i(TAG, "Service " + SERVICE_NAME + " published.");
        } catch (Exception e) {
            Slog.e(TAG, "Failed to publish " + SERVICE_NAME + ": " + e);
        }
    }

    @Override
    public void onBootPhase(int phase) {
        super.onBootPhase(phase);
        if (phase == SystemService.PHASE_BOOT_COMPLETED) {
            Slog.i(TAG, "Boot completed. Service is fully operational.");
            // OSのブートが完了した時点で行いたい処理があればここに記述
        }
    }
}

このクラスの責務は明確です。

  1. onStart()でサービス本体(MyVehicleSettingsService)をインスタンス化する。
  2. publishBinderService()メソッドを使い、サービスインスタンスをServiceManagerに登録する。引数のSERVICE_NAMEは、クライアントがサービスを取得する際に使用する一意なキーとなります。
  3. onBootPhase()を使い、特定のブートフェーズ(例: `PHASE_ACTIVITY_MANAGER_READY`や`PHASE_BOOT_COMPLETED`)に依存する初期化処理を行う。

3.1.2 `SystemServer` へのライフサイクルクラスの登録

次に、作成したMyVehicleSettingsServiceLifecycleクラスをSystemServerに認識させる必要があります。これは通常、frameworks/base/services/java/com/android/server/SystemServer.javaまたは、車種によってはframeworks/base/services/core/java/com/android/server/CoreServices.javaなど、デバイス固有の起動スクリプトファイルで行います。

SystemServer.javastartOtherServices()メソッド内に、一行追加します。


// ファイルパス: frameworks/base/services/java/com/android/server/SystemServer.java

...
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    ...
    t.traceBegin("StartCarServiceHelperService");
    mSystemServiceManager.startService(CarServiceHelperService.class);
    t.traceEnd();

    // ↓↓↓ この行を追加 ↓↓↓
    t.traceBegin("StartMyVehicleSettingsService");
    mSystemServiceManager.startService(com.android.server.example.MyVehicleSettingsServiceLifecycle.class);
    t.traceEnd();
    // ↑↑↑ この行を追加 ↑↑↑

    t.traceBegin("StartInputManager");
    ...
}
...

mSystemServiceManager.startService()を呼び出すだけで、SystemServiceManagerが指定されたクラスのインスタンス化とライフサイクルメソッド(onStart(), onBootPhase()など)の呼び出しを適切に管理してくれます。

3.2 伝統的な方法(非推奨)との比較

以前は、SystemServer.javaに直接サービスの実装ロジックを書き込む方法が主流でした。参考までに、その方法も示しますが、AOSPのアップストリームとのマージコンフリクトを引き起こしやすいため、現在では避けるべきです。

項目 モダンな方法 (`SystemService`) 伝統的な方法 (直接編集)
コードの場所 独立したライフサイクルクラスに分離 (例: `MyVehicleSettingsServiceLifecycle.java`) `SystemServer.java` 内に直接インスタンス化と登録ロジックを記述
モジュール性 高い。サービス関連のロジックが自己完結している。 低い。`SystemServer.java` が巨大化し、関連コードが分散する。
保守性 高い。AOSPの更新時に`SystemServer.java`の変更と競合しにくい。 低い。AOSPの更新のたびにマージ作業が必要になる可能性が高い。
ライフサイクル管理 `onStart()`, `onBootPhase()`などのフックが用意されており、体系的な管理が可能。 `startOtherServices()`内の適切な場所に手動でコードを配置する必要があり、依存関係の管理が複雑。
推奨度 強く推奨 非推奨

ステップ4: SELinux ポリシーの構成とパーミッション定義

Androidでは、SELinux (Security-Enhanced Linux) が強制アクセス制御(MAC)を提供しており、これがシステムサービス登録における最大の障壁の一つです。SELinuxは「デフォルトですべてを拒否する」原則で動作するため、新しいサービスを追加したら、そのサービスが何者で、誰がアクセスできるかを明示的に許可するポリシーを記述しなければなりません。これを怠ると、サービスは起動に失敗するか、クライアントからのアクセスがすべてブロックされます。

4.1 サービスコンテキストの定義

まず、新しいサービスがどのSELinuxコンテキストに属するかを定義します。これは通常、system/sepolicy/public/service_contexts(またはvendor/<...>/sepolicy/vendor/service_contexts)ファイルで行います。


# ファイルパス: device/<vendor>/<product>/sepolicy/vendor/service_contexts

# ... 既存の定義 ...
my_vehicle_settings_service               u:object_r:my_vehicle_settings_service:s0

これにより、"my_vehicle_settings_service"という名前のサービスはmy_vehicle_settings_serviceというSELinuxタイプを持つことになります。

4.2 ポリシーファイル (.te) の作成

次に、新しいSELinuxタイプを定義し、そのタイプに関するルールを記述する.te (Type Enforcement) ファイルを作成します。例えば、device/<vendor>/<product>/sepolicy/vendor/my_vehicle_settings_service.te というファイルを作成します。


# my_vehicle_settings_service.te

# 新しいサービスタイプを定義
type my_vehicle_settings_service, service_manager_type;

# system_serverがこのサービスをServiceManagerに追加(add)し、
# サービスを検索(find)できるようにするルール
allow system_server self:service_manager { add find };

# system_serverがこのサービスに対してbinder callを行えるようにする
# これにより、サービスの実体を生成・管理できる
allow system_server my_vehicle_settings_service:service_manager add;

# 例えば、特権アプリ (platform_app) がこのサービスを利用できるようにするルール
# platform_app が my_vehicle_settings_service を検索(find)できるようにする
allow platform_app my_vehicle_settings_service:service_manager find;
# platform_app が my_vehicle_settings_service とbinder通信できるようにする
binder_call(platform_app, system_server);
SELinuxルールのポイント:
  • type my_vehicle_settings_service, service_manager_type;: 新しいタイプを定義し、それがservice_managerによって管理されるサービスであることを示します。
  • allow system_server ...: サービスを登録する主体であるsystem_serverプロセスに対して、サービスをadd(追加)およびfind(検索)する権限を与えます。
  • allow <client_domain> ...: サービスを利用するクライアント(例: platform_app, system_app, あるいは独自のドメイン)に対して、サービスをfindする権限を与えます。
  • binder_call(<client_domain>, <service_domain>): クライアントがサービスに対してBinder IPCコールを行うことを許可するマクロです。

4.3 file_contexts と genfs_contexts

ポリシーファイル(.te)をビルドシステムに認識させるため、device/<vendor>/<product>/sepolicy/vendor/file_contextsにエントリを追加する必要がある場合があります(ビルド構造による)。

これらのSELinuxの変更は非常に複雑で、間違えるとデバイスが起動しなくなる(ブートループに陥る)原因となります。変更後は、logcat -b all | grep avc を実行し、avc: denied というメッセージが出力されていないか常に確認することが重要です。

ステップ5: AOSPビルドシステムへの統合とビルド

これまでのステップで作成・変更したすべてのファイルを、AOSPのビルドプロセスに組み込みます。

5.1 プロダクト定義ファイルへの追加

作成したサービスライブラリ(MyVehicleSettingsServiceLib)がシステムイメージに含まれるように、デバイスのプロダクト定義ファイル(例: device/<vendor>/<product>/device.mk)を編集します。


# device/<vendor>/<product>/device.mk

# ... 他のパッケージ ...

PRODUCT_PACKAGES += \
    MyVehicleSettingsServiceLib

# Android 11以降、Soongは自動的に依存関係を解決することが多いが、
# 明示的に`PRODUCT_SYSTEM_SERVER_JARS`に追加する必要がある場合もある
PRODUCT_SYSTEM_SERVER_JARS += MyVehicleSettingsServiceLib

PRODUCT_PACKAGES にライブラリ名を追加することで、ビルド時にこのモジュールがコンパイルされ、最終的なイメージに含められます。Androidのバージョンやビルド構成によっては、`PRODUCT_SYSTEM_SERVER_JARS`への追加が必要になることもあります。

5.2 ビルドの実行

すべての変更が完了したら、AOSPのルートディレクトリでビルドを実行します。


# 環境設定スクリプトの実行
source build/envsetup.sh

# ターゲットデバイスの選択
lunch <your_target_device>

# フルビルドの実行(-jオプションで並列実行数を指定)
m -j16

ビルドが正常に完了したら、生成されたイメージ(out/target/product/<your_target_device>/配下)を物理デバイスまたはエミュレータに書き込み(flash)ます。

ステップ6: アプリケーションからのサービス利用

サービスが正常にSystemServerに登録され、動作していることを確認するために、クライアントアプリケーションからサービスにアクセスしてみましょう。ここでも、モダンなアプローチであるSystemServiceRegistryを使用することが強く推奨されます。

6.1 マネージャクラスの作成

アプリケーション開発者が直接ServiceManagerやBinderを扱うのは煩雑でエラーの元です。そこで、システムサービスへのアクセスをカプセル化し、使いやすいAPIを提供する「マネージャクラス」を作成するのがベストプラクティスです。これはAlarmManagerAudioManagerと同じ設計パターンです。


// ファイルパス: vendor/example/framework/java/android/example/myvehiclesettings/MyVehicleSettingsManager.java

package android.example.myvehiclesettings;

import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;

public class MyVehicleSettingsManager {
    private static final String TAG = "MyVehicleSettingsManager";
    public static final String SERVICE_NAME = "my_vehicle_settings_service";

    private final Context mContext;
    private final IMyVehicleSettingsService mService;

    public MyVehicleSettingsManager(Context context, IMyVehicleSettingsService service) {
        mContext = context;
        mService = service;
    }

    public void setAmbientLightColor(AmbientLightColor color) {
        try {
            mService.setAmbientLightColor(color);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    public AmbientLightColor getAmbientLightColor() {
        try {
            return mService.getAmbientLightColor();
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
            return null; // unreachable
        }
    }
}

このマネージャクラスは、RemoteExceptionのハンドリングなど、Binder通信の定型的な処理を内部に隠蔽します。

6.2 `SystemServiceRegistry` への登録

このマネージャクラスを、アプリケーションがContext.getSystemService(Context.MY_VEHICLE_SETTINGS_SERVICE)のように簡単に取得できるように、SystemServiceRegistryに登録します。この変更はframeworks/base/core/java/android/app/SystemServiceRegistry.javaで行います。


// ファイルパス: frameworks/base/core/java/android/app/SystemServiceRegistry.java

...
// 1. Context にサービス名を追加
// frameworks/base/core/java/android/content/Context.java
// public static final String MY_VEHICLE_SETTINGS_SERVICE = "my_vehicle_settings_service";

// 2. SystemServiceRegistry に登録ロジックを追加
...
        // MY_VEHICLE_SETTINGS_SERVICE の登録
        registerService(Context.MY_VEHICLE_SETTINGS_SERVICE, MyVehicleSettingsManager.class,
                new CachedServiceFetcher<MyVehicleSettingsManager>() {
                    @Override
                    public MyVehicleSettingsManager createService(ContextImpl ctx) {
                        IBinder binder = ServiceManager.getService(Context.MY_VEHICLE_SETTINGS_SERVICE);
                        if (binder == null) {
                            Slog.e("MyVehicleSettingsManager", "Service not available");
                            return null;
                        }
                        IMyVehicleSettingsService service = IMyVehicleSettingsService.Stub.asInterface(binder);
                        return new MyVehicleSettingsManager(ctx, service);
                    }
                });
...

この登録により、フレームワーク全体が新しいサービスを認識できるようになります。アプリがgetSystemService()を呼び出すと、SystemServiceRegistryがこのcreateServiceメソッドを実行し、マネージャのインスタンスを生成して返却します。

6.3 アプリケーションからの呼び出し

これで、特権を持つアプリケーションから非常にシンプルにサービスを呼び出すことができます。


// アプリケーションコード内

// 1. マネージャの取得
MyVehicleSettingsManager manager = (MyVehicleSettingsManager) getSystemService(Context.MY_VEHICLE_SETTINGS_SERVICE);

if (manager != null) {
    // 2. サービスメソッドの呼び出し
    AmbientLightColor currentColor = manager.getAmbientLightColor();
    Log.d("MyApp", "Current color: R=" + currentColor.getRed());

    // 新しい色を設定
    AmbientLightColor newColor = new AmbientLightColor(0, 255, 0); // 緑
    manager.setAmbientLightColor(newColor);
} else {
    Log.e("MyApp", "MyVehicleSettingsManager is not available.");
}

アプリのAndroidManifest.xmlには、サービスへのアクセスに必要なパーミッション(この例ではandroid.permission.MANAGE_VEHICLE_SETTINGS)を忘れずに記述する必要があります。

ステップ7: デバッグとトラブルシューティング

システムサービスの開発は、問題が発生した際の切り分けが難しいことがあります。ここでは、よくある問題とその解決に役立つコマンドを紹介します。

問題 確認事項とデバッグコマンド 主な原因
デバイスがブートループする adb logcat -b all で起動シーケンスを確認。特にSystemServerのクラッシュログを探す。 `SystemServer.java`の編集ミス、サービスのコンストラクタや`onStart`での例外発生、SELinuxポリシーの致命的なエラー。
サービスが登録されていない (`service list`に表示されない) adb shell service list | grep my_vehicle_settings_service を実行。
adb logcat | grep MyVehicleSettingsService で起動ログを確認。
`SystemServer`への登録コードが抜けている、ビルドでライブラリが含まれていない(`device.mk`の確認)、SELinuxの`add`権限不足。
アプリからサービスを取得できない (`getSystemService`がnullを返す) adb logcat | grep MyVehicleSettingsManagerでエラーを確認。
adb shell dumpsys meminfo --package systemで`SystemServer`が生きているか確認。
サービス名(キー)の不一致、SELinuxの`find`権限不足、`SystemServiceRegistry`への登録漏れ。
サービスメソッドを呼ぶとSecurityExceptionが発生する adb logcatで例外の詳細を確認。呼び出し元アプリのPIDと権限を確認。 アプリの`AndroidManifest.xml`に`uses-permission`が抜けている、SELinuxの`binder_call`ルールが不足している。
SELinux拒否ログが出る (`avc: denied`) adb logcat -b all | grep avc を実行し、scontext (ソース), tcontext (ターゲット), tclass (クラス), perm (権限) を確認する。 必要なSELinuxルールが`.te`ファイルに記述されていない。`audit2allow`ツールを使って必要なルールを生成することもできる。

特にdumpsysコマンドは強力なツールです。サービス側にdump()メソッドを実装しておけば、adb shell dumpsys my_vehicle_settings_serviceを実行することで、サービス内部の状態(現在の色設定など)をリアルタイムで確認できます。

まとめ

Android Automotive OSのSystemServerにカスタムサービスを登録するプロセスは、複数のコンポーネントが複雑に絡み合う、挑戦的なタスクです。本稿では、そのプロセスを以下のステップに分解し、詳細に解説しました。

  1. AIDLによるインターフェース設計: プロセス間の通信規約を明確に定義する。
  2. サービスの実装: AIDLインターフェースを実装し、ビジネスロジックと権限チェックを組み込む。
  3. SystemServerへの組み込み: SystemServiceを活用したモダンで保守性の高い方法でサービスを登録する。
  4. SELinuxポリシーの構成: サービスの存在とアクセス権をSELinuxに正しく通知する。
  5. AOSPビルドシステムへの統合: 作成したコンポーネントをシステムイメージに含める。
  6. クライアントからの利用: SystemServiceRegistryとマネージャクラスを介して、アプリケーションから安全かつ簡単にサービスを利用する。
  7. デバッグ: 発生しうる問題を特定し、解決するためのツールと手法。

この一連の流れを理解し、実践することで、単なるアプリケーション開発に留まらず、Android Automotive OSプラットフォームそのものを拡張し、車両に深く統合された独自の機能を開発する道が開かれます。AOSPの広大な世界を探求する上で、このガイドが一助となれば幸いです。

Post a Comment