AAOS起動の裏側 車両起動と同時にサービスを動かす技術

Android Automotive OS (AAOS) は、単なるスマートフォン向けAndroidの車載版ではありません。車両の心臓部と深く連携し、エンジン制御ユニット(ECU)や各種センサー、CAN (Controller Area Network) バスといったハードウェアと直接通信する能力が求められます。このため、AAOSにおけるアプリケーション開発、特にシステムの根幹に関わるサービス開発は、一般的なアプリ開発とは全く異なるアプローチが必要です。多くの開発者が直面する大きな課題の一つが、「車両の電源が入った瞬間に、バックグラウンドで特定の処理を確実に開始させたい」という要求です。例えば、車両状態を監視するロガー、カスタムのテレマティクスサービス、あるいは特定のハードウェアを初期化するプロセスなどがこれに該当します。この記事では、AOSP (Android Open Source Project) の深部にまで踏み込み、AAOSが起動するまさにその瞬間に、自作のサービスをシステムサービスとして確実に自動実行させるための包括的かつ実践的な技術を詳細に解説します。単なる BOOT_COMPLETED ブロードキャストの利用に留まらず、真のシステムインテグレーションを実現するための特権アプリの作成、プラットフォーム署名、SELinuxポリシーの設定、そしてCarServiceとの連携まで、その全貌を明らかにします。

なぜ通常のAndroidアプリ開発とAAOSではアプローチが異なるのか?

スマートフォンアプリ開発の経験が豊富な開発者ほど、AAOS開発で最初に戸惑うのは、その環境の「制約」と「特権」です。スマートフォンでは、ユーザーが主体であり、アプリはユーザーの操作に応じて動作するのが基本です。しかし、車載システムでは、ドライバーの安全と車両の正常な動作が最優先されます。この根本的な思想の違いが、開発アプローチの差となって現れます。通常のアプリがサンドボックス化された安全な環境で動作するのに対し、AAOSのシステムアプリは、より深いレベルでOSやハードウェアと対話する権限を持ちます。この違いを理解することが、安定した車載サービスを開発するための第一歩となります。

主な違いは、パーミッションモデル、ライフサイクル、そしてハードウェアへのアクセス方法に集約されます。例えば、スマートフォンのGPS情報にアクセスするのと、車両のGNSSモジュールから直接、高精度な位置情報や慣性計測情報(IMU)を取得するのでは、求められる権限レベルが全く異なります。後者は、android.permission.LOCATION_HARDWAREのような、通常のサードパーティアプリには決して許可されないsignatureまたはsystemレベルの権限を要求します。このような強力な権限を持つアプリは、OSと同じプラットフォームキーで署名され、システムの/system/priv-appのような特別なディレクトリに配置される必要があります。これは、悪意のあるアプリが車両の重要な機能を乗っ取ることを防ぐための、極めて重要なセキュリティ機構です。

以下の表は、一般的なAndroidアプリとAAOSの特権アプリ(Privileged App)の主な違いをまとめたものです。

項目 一般的なAndroidアプリ AAOS特権アプリ (システムアプリ)
実行コンテキスト サンドボックス化された、分離されたプロセス (untrusted_app) システムのUID (system) または共有UIDで動作し、高い権限を持つ (platform_app, priv_app)
パーミッション normal, dangerous レベルの権限をユーザーの許可を得て使用 signature, system, internal レベルの保護されたAPIやハードウェアアクセス権限を行使可能
インストール場所 /data/app ディレクトリ /system/app または /system/priv-app, /vendor/app など、読み取り専用パーティション
署名 開発者自身のプライベートキーで署名 OSと同じプラットフォームキーで署名が必須
ライフサイクル管理 メモリ不足時にシステムによって比較的容易にキルされる システムのコアプロセスと見なされ、通常はキルされない。永続的な動作が期待される。
ハードウェアアクセス 公開されたAndroid API (SDK) を通じて間接的にアクセス HAL (Hardware Abstraction Layer), JNI, あるいは直接デバイスファイル (/dev/...) にアクセス可能
SELinuxポリシー untrusted_app ドメインに属し、厳しく制限された操作のみ許可 独自のSELinuxドメインを定義し、必要なリソースへのアクセス権を明示的に許可する必要がある

この表からも明らかなように、AAOSで真に価値のある機能を実装するためには、単なるAPKを開発するのではなく、AOSPビルドシステムの一部としてアプリケーションを構築し、OSと一体化させるという発想の転換が不可欠です。次のセクションでは、この特権的な地位を得るための具体的なプロセスと、AAOSの起動シーケンスにおけるサービスの役割について掘り下げていきます。

AAOSの起動シーケンスとサービスの役割

「車両起動時にサービスを開始する」という要件を正確に実装するためには、まずAAOSがどのように起動するのか、そのシーケンスを理解する必要があります。スマートフォンのように単純な「電源ON」とは異なり、車両の起動は複数のステージ(ACC-ON, IGN-ONなど)を経て、様々なECUが協調しながら進行します。Androidは、この複雑なプロセスの一部として起動されます。

AAOSの起動プロセスは、Linuxカーネルのブートから始まり、initプロセスが各種デーモンやサービスを立ち上げ、Zygoteがアプリプロセスの土台を作り、最終的にSystem ServerがAndroidフレームワークのコアサービス群を起動するという、標準的なAndroidの起動フローを踏襲します。しかし、AAOSには車載特有の重要なコンポーネントが追加されています。それが CarService です。

CarService (com.android.car.CarService) は、AAOSにおける心臓部とも言える存在です。Vehicle HAL (VHAL) を介して車両の各種プロパティ(車速、エンジン回転数、燃料残量など)へのアクセスを抽象化し、CarPowerManager, CarSensorManager, CarPropertyManagerといった各種車載用APIをアプリケーションに提供します。重要なのは、このCarServiceが起動プロセスの比較的早い段階で初期化され、多くの車載関連サービスのハブとして機能する点です。

一般的なAndroidアプリ開発でよく用いられるandroid.intent.action.BOOT_COMPLETEDというブロードキャストインテントは、AAOSでも利用可能です。しかし、このインテントが発行されるのは、システムの基本的な起動がすべて完了し、ホーム画面が表示される準備が整った後の、かなり遅いタイミングです。車両のCAN信号を起動直後から記録したい場合や、外部ECUとの通信を早期に確立する必要がある場合など、クリティカルなタスクにとってはBOOT_COMPLETEDでは手遅れなのです。私たちが目指すのは、CarServiceのようなシステムの中核サービスとほぼ同等のタイミングで、自作のサービスを起動させることです。これにより、ユーザーが操作可能になるよりもずっと前に、バックグラウンドで必要な処理を開始し、シームレスなユーザーエクスペリエンスとシステムの安定性を確保できます。この早期起動を実現するための具体的な方法については、後のセクションで詳しく解説します。

特権アプリ(Privileged App)の作成と署名

システムサービスとして早期に起動するためには、まずアプリケーションが「特権」を持つ存在、すなわち特権アプリ(Privileged App)としてシステムに認識される必要があります。これには、マニフェストファイルの適切な設定と、AOSPのビルドプロセスにおけるプラットフォームキーでの署名という、二つの重要なステップが不可欠です。

Step 1: AndroidManifest.xmlの核心的設定

特権アプリのマニフェストは、そのアプリのアイデンティティと要求する権限をシステムに宣言する設計図です。通常のアプリと最も大きく異なるのは、manifestタグにandroid:sharedUserId="android.uid.system"属性を追加する点です。これは、このアプリがAndroid OSのシステムプロセスと同じLinuxユーザーIDで実行されることを要求するもので、これによりシステムレベルのリソースへのアクセスが可能になります。ただし、これは非常に強力な権限であり、システムの安定性に直接影響を与えるため、慎重に使用する必要があります。

以下は、車両のプロパティにアクセスし、システム起動時にサービスを開始する特権アプリのマニフェストの例です。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myautomotiveservice"
    android:sharedUserId="android.uid.system">

    <!-- 車両のハードウェア状態にアクセスするための必須権限 -->
    <uses-permission android:name="android.car.permission.CAR_POWERTRAIN" />
    <uses-permission android:name="android.car.permission.CAR_SENSORS" />
    <!-- システムレベルのAPIをコールするために必要な場合がある -->
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />

    <application
        android:label="@string/app_name"
        android:persistent="true"> <!-- persistent=trueでプロセスが常に生存するようにする -->
        
        <!-- 起動時に開始したいメインのサービス -->
        <service
            android:name=".MyVehicleMonitoringService"
            android:exported="false" />

        <!-- BOOT_COMPLETEDで起動するためのレシーバー(代替案または補助的手段) -->
        <receiver
            android:name=".BootReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

    </application>
</manifest>

このマニフェストのポイントは以下の通りです。

  • android:sharedUserId="android.uid.system": アプリをシステムプロセスの一部として実行させるための最重要設定。
  • android:persistent="true": システムがこのアプリのプロセスを常に維持しようと試みるようにするフラグ。システムのコア機能を提供するサービスに適しています。
  • 各種パーミッション: android.car.permission.*のような車載専用のsystemレベル権限を宣言します。これらの権限は、プラットフォーム署名がなければシステムから付与されません。

Step 2: AOSPプラットフォームキーでの署名とビルド定義

マニフェストでシステム権限を要求しただけでは、アプリは特権を得られません。その要求が正当であることを証明するために、アプリはOS自体と同じ「プラットフォームキー」で署名される必要があります。これは、Android Studioで通常のアプリをビルドしてAPKを生成し、それを手動でインストールする、というフローでは実現できません。AOSPのソースコードツリー内で、Android OSと共にアプリをビルドする必要があります。

ビルドシステムに対して、このアプリが特権アプリであることを伝えるために、Android.bp (Soongビルドシステム) または Android.mk (makeベースの旧ビルドシステム) ファイルをアプリのソースディレクトリに配置します。

以下は、Android.bp を使用したビルド定義の例です。

// Android.bp
android_app {
    name: "MyAutomotiveServiceApp",
    srcs: [
        "src/**/*.java",
    ],
    // パッケージ名を指定
    package_name: "com.example.myautomotiveservice",

    // AndroidManifest.xmlの場所
    manifest: "AndroidManifest.xml",

    // ★★★ 最重要: プラットフォームキーで署名する設定 ★★★
    certificate: "platform",

    // ★★★ 最重要: /system/priv-app にインストールする設定 ★★★
    privileged: true,

    // 依存するライブラリ
    static_libs: [
        "android.car-stubs",
        "androidx.car.app.app-stubs",
    ],

    // SDKのバージョン
    sdk_version: "system_current",
    platform_apis: true,
}

このファイルで最も重要なのはcertificate: "platform"privileged: trueの二つの行です。

  • certificate: "platform": ビルドシステムに対し、AOSPのビルドプロセスで使用される共通のプラットフォーム証明書を使ってこのAPKに署名するように指示します。
  • privileged: true: このモジュール(アプリ)を、特権アプリが配置されるべき/system/priv-app/ディレクトリにインストールするように指示します。これにより、アプリは起動時にシステムから特権を付与されます。

このAndroid.bpファイルを作成し、AOSPのpackages/appsディレクトリなどに配置してmコマンドでフルビルドを実行することで、初めてシステムと一体化した特権アプリが生成されます。このプロセスを経て初めて、AutomotiveアプリのSignatureOrSystem権限の取得が完了するのです。

サービスの自動起動:真の「システムサービス」として登録する

特権アプリの準備が整ったところで、いよいよ本題である「サービスの自動起動」を実装します。前述の通り、BOOT_COMPLETEDインテントに頼る方法は、タイミングが遅すぎるという欠点があります。ここでは、より確実で早期にサービスを開始するための、プロフェッショナルな3つの方法を比較・解説します。

方法1: BOOT_COMPLETEDレシーバー (基本だが不十分な方法)

これは最も古典的で簡単な方法です。マニフェストにBroadcastReceiverを登録し、android.intent.action.BOOT_COMPLETEDインテントを受け取ったらサービスを起動します。

// BootReceiver.java
public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            Intent serviceIntent = new Intent(context, MyVehicleMonitoringService.class);
            context.startForegroundService(serviceIntent);
        }
    }
}

この方法は実装が容易である一方、前述の通り、車両のクリティカルな処理には起動タイミングが遅すぎます。また、ユーザーがアプリを「強制停止」した場合、Android 8.0 (API 26) 以降では、ユーザーが一度アプリを起動するまでBOOT_COMPLETEDが配送されないという制約もあります。特権アプリであればこの制約は緩和されますが、起動タイミングの問題は依然として残ります。

方法2: CarServiceにサービスを登録する (推奨されるプロフェッショナルな方法)

これぞAAOS開発の真骨頂です。自作のサービスを、AAOSの心臓部であるCarServiceの初期化プロセスに組み込んでしまう方法です。これにより、CarServiceが起動するのと同じ、非常に早いタイミングで自作サービスを起動させることができます。この方法は、Android Automotiveのシステムサービス登録そのものを意味します。

この実装には、AOSPのフレームワークコード (packages/services/Car/) に手を入れる必要があります。

  1. サービスの定義: サービスがICarServiceのライフサイクルイベントをフックできるように、特定のインターフェースを実装します。通常、CarServiceBaseを継承するのが簡単です。
    // MyVehicleMonitoringService.java
        import com.android.car.CarServiceBase;
        
        public class MyVehicleMonitoringService implements CarServiceBase {
            private final Context mContext;
        
            public MyVehicleMonitoringService(Context context) {
                mContext = context;
                // ここはCarServiceのコンストラクタから呼ばれる。非常に早いタイミング。
            }
        
            @Override
            public void init() {
                // CarServiceの初期化処理中に呼ばれる
                // ここでリスナー登録や初期化処理を行う
            }
        
            @Override
            public void release() {
                // CarServiceのシャットダウン時に呼ばれる
                // リソースの解放処理を行う
            }
        
            @Override
            public void dump(PrintWriter writer) {
                // dumpsys car_service で表示する情報をここに出力
            }
        }
        
  2. CarServiceへの登録: AOSP内のpackages/services/Car/service/src/com/android/car/CarService.javaファイルを直接編集し、サービスのリストに自作サービスを追加します。
    // CarService.java の一部を改変
        public class CarService extends Service {
            // ... 既存のコード ...
            private final CarServiceBase[] mAllServices;
        
            public CarService() {
                mAllServices = new CarServiceBase[]{
                        // ... 既存のシステムサービス ...
                        new CarPowerManagementService(this),
                        new CarPropertyService(this, ...),
                        // ...
        
                        // ★★★ ここに自作サービスを追加 ★★★
                        new com.example.myautomotiveservice.MyVehicleMonitoringService(this)
                };
            }
            // ... 既存のコード ...
        }
        

    注意: このようにフレームワークのコードを直接変更するアプローチは、Androidのバージョンアップ時に追従が必要になるなど、メンテナンスコストが発生します。OEMやTier1サプライヤーなど、OS全体を管理する立場で開発する場合に用いられる手法です。

この方法の最大の利点は、起動タイミングの早さと確実性です。CarServiceとライフサイクルを共にすることで、車両の電源状態と完全に同期した、安定したサービス運用が可能になります。

方法3: init.rcスクリプトを利用したネイティブサービスの起動 (低レベルアクセス向け)

さらに低レベルな、Androidフレームワークが完全に起動するよりも前に処理を開始したい場合があります。例えば、特定のハードウェアドライバの初期化や、Linuxカーネルレベルでの設定などです。この場合、Androidのinitプロセスを利用して、ネイティブコード(C/C++で記述された実行ファイル)をサービスとして起動します。

これには、.rcという拡張子を持つ設定ファイルを作成し、デバイスの/vendor/etc/init/などに配置します。

// my_native_service.rc
// /vendor/bin/my_native_executable をサービスとして登録
service my_native_service /vendor/bin/my_native_executable
    class main
    user system
    group system
    oneshot // 一度だけ実行する場合
    // critical // 停止した場合にシステムを再起動させる場合

このmy_native_serviceは、Linuxカーネルの起動後、initプロセスによって直接起動されます。Java/Kotlinで書かれたAndroidサービスよりも遥かに早く起動しますが、AndroidフレームワークのAPI(Car APIなど)はまだ利用できません。このネイティブサービスからJava層のサービスと連携するには、ソケット通信やBinder IPCなどのプロセス間通信メカニズムを別途実装する必要があります。これは、ハードウェア制御の深い知識を要求される、最も高度なアプローチです。

各起動方法の比較

方法 起動タイミング 信頼性 実装の複雑さ 主なユースケース
BOOT_COMPLETED 遅い (UI表示後) 中 (ユーザー操作で無効化される可能性) 緊急性の低いデータ同期、UI関連の初期化など。
CarService登録 非常に速い (フレームワーク初期化時) 高 (システムと一体) 高 (AOSPフレームワークの改変が必要) 車両データロガー、センサー監視、ECU通信など、システムのコア機能。
init.rc ネイティブサービス 最速 (Linux起動直後) 非常に高 (OSの基本プロセス) 非常に高 (C/C++, IPC, Linuxの知識が必要) ハードウェア初期化、ファームウェア更新、低レベルなセキュリティ監視など。

結論として、ほとんどの車載向けバックグラウンドサービスでは、「方法2: CarServiceへの登録」が最もバランスの取れた、理想的な解決策と言えるでしょう。AAOS起動時にサービスを自動実行する方法として、このアプローチをマスターすることが、高品質な車載システム開発への鍵となります。

SELinuxポリシー:避けては通れない壁

特権アプリとしてビルドし、CarServiceに登録して早期起動を実現したとしても、まだ大きな壁が残っています。それがSELinux (Security-Enhanced Linux) です。Androidは、Linuxカーネルの強制アクセス制御(MAC)メカニズムであるSELinuxを導入しており、たとえrootsystemユーザーであっても、許可されていないリソースへのアクセスをブロックします。これは、システムの堅牢性を高めるための非常に重要なセキュリティ機能です。

例えば、自作のサービスがCANバスにアクセスするためにデバイスファイル /dev/can0 を開こうとしたり、特定のシステムプロパティを読み書きしようとしたりすると、SELinuxポリシーで明示的に許可されていない限り、必ずアクセス拒否(Permission Denied)エラーが発生します。このエラーは、logcatdmesgに "avc: denied" という特徴的なログとして記録されます。

# logcat -b all | grep "avc: denied"
avc: denied { read write } for pid=1234 comm="my_service" name="can0" dev="tmpfs" ino=5678 scontext=u:r:my_service:s0 tcontext=u:object_r:can_device:s0 tclass=chr_file permissive=0

このログは、「my_serviceというセキュリティコンテキスト(scontext)で実行されているプロセスが、can_deviceというコンテキスト(tcontext)を持つキャラクターファイル(chr_file)に対して、readwrite操作を試みたが、ポリシーに違反したため拒否された」ということを示しています。

この問題を解決するには、自作のサービス(アプリ)のために、独自のSELinuxポリシーを定義し、ビルドに組み込む必要があります。これは、.teという拡張子を持つポリシーファイル(Type Enforcementファイル)を作成することで行います。これは、車載Androidアプリの権限設定ガイドの中でも最も専門性が高い部分です。

以下は、上記の拒否ログに対応するためのポリシーファイルの例です。

// device/<oem>/<product>/sepolicy/my_service.te

// 1. my_service という新しいタイプ(ドメイン)を定義する
type my_service, domain;
type my_service_exec, exec_type, file_type, vendor_file_type;

// 2. このドメインをシステムアプリのドメイン(platform_app)から遷移可能にする
init_daemon_domain(my_service)

// 3. 許可ルールを追加する
// my_service ドメインが can_device タイプのキャラクターファイルに対して
// read, write, open, ioctl 操作を許可する
allow my_service can_device:chr_file { read write open ioctl };

// Binder通信を許可するルール(他のシステムサービスと通信する場合に必要)
binder_use(my_service)
binder_call(my_service, platform_app)
add_service(my_service, my_binder_service)

この.teファイルを作成し、AOSPのデバイス固有のsepolicyディレクトリに配置してビルドすると、新しいポリシーがコンパイルされ、システムのポリシーファイルに統合されます。SELinuxのポリシー記述は非常に複雑で、システムの安定性に直接影響するため、最小権限の原則に従い、本当に必要な権限のみを許可することが重要です。拒否ログを解析し、audit2allowのようなツールを使って必要なルールを生成し、それを慎重に適用していく、という地道な作業が求められます。

実装例:車両CANデータを読み取るシステムサービスの構築

これまでに解説したすべての要素を統合し、車両のCANバスからデータを読み取り、ログに出力するという具体的なシステムサービスを構築してみましょう。この例は、AOSPのビルドツリー内に新しいアプリケーションを作成するプロセスを示します。

1. プロジェクトのディレクトリ構造

AOSPのpackages/services/Car/ディレクトリ内に、以下のような構造でプロジェクトを作成します。

packages/services/Car/MyCanLogger/
├── Android.bp
├── AndroidManifest.xml
└── src/
    └── com/
        └── example/
            └── mycanlogger/
                ├── CanLoggerService.java
                └── CanReader.java (JNI or Socket to native)

2. AndroidManifest.xml

sharedUserIdを設定し、永続的なサービスを宣言します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mycanlogger"
    android:sharedUserId="android.uid.system">

    <application
        android:label="My CAN Logger Service"
        android:persistent="true">
        
        <service
            android:name=".CanLoggerService"
            android:exported="false" />
            
        <uses-library android:name="android.car" />
    </application>
</manifest>

3. CanLoggerService.java (CarService登録方式)

CarServiceBaseを実装し、CarServiceのライフサイクルに連動させます。

package com.example.mycanlogger;

import android.content.Context;
import android.util.Log;
import com.android.car.CarServiceBase;
import java.io.PrintWriter;

public class CanLoggerService implements CarServiceBase {
    private static final String TAG = "CanLoggerService";
    private final Context mContext;
    private CanReader mCanReader;
    private boolean mIsRunning = false;

    public CanLoggerService(Context context) {
        mContext = context;
        Log.d(TAG, "CanLoggerService created.");
    }

    @Override
    public void init() {
        Log.d(TAG, "Initializing CanLoggerService...");
        mCanReader = new CanReader(); // ネイティブ層との接続を想定
        mCanReader.startReading();
        mIsRunning = true;
        Log.d(TAG, "CanLoggerService initialized and started.");
    }

    @Override
    public void release() {
        Log.d(TAG, "Releasing CanLoggerService...");
        if (mCanReader != null) {
            mCanReader.stopReading();
        }
        mIsRunning = false;
        Log.d(TAG, "CanLoggerService released.");
    }

    @Override
    public void dump(PrintWriter writer) {
        writer.println("CanLoggerService Status:");
        writer.println("  Running: " + mIsRunning);
        if (mCanReader != null) {
            mCanReader.dump(writer);
        }
    }
}

4. Android.bp

プラットフォーム署名と特権アプリとしてのビルド設定を記述します。

// packages/services/Car/MyCanLogger/Android.bp
java_library {
    name: "my-can-logger-service-lib",
    srcs: ["src/**/*.java"],
    libs: [
        "car-framework-service-stubs", // CarServiceBase を使うために必要
    ],
    sdk_version: "system_current",
    platform_apis: true,
}

そして、このライブラリをCarService自体に組み込むため、packages/services/Car/service/Android.bpを修正します。

// packages/services/Car/service/Android.bp の一部を修正
...
    static_libs: [
        ...
        "my-can-logger-service-lib", // ★★★ 自作ライブラリを依存関係に追加
    ],
...

最後に、前述の通りCarService.javaを修正してCanLoggerServiceのインスタンスを生成・登録します。

5. SELinuxポリシーの追加

デバイスのsepolicyディレクトリ (例: device/generic/car/common/sepolicy/) に、can_logger_service.teファイルを作成します。

// can_logger_service.te
type can_logger_service, domain;

// このサービスは system_server (CarServiceを含む) のコンテキストで直接実行されるため、
// 独立したドメインは不要で、system_server ドメインに権限を追加するだけで良い場合が多い。
// 以下は system_server が CAN デバイスにアクセスするための許可ルール
allow system_server can_device:chr_file { read write open ioctl };

// もし独立したプロセスとして起動する場合は、以下のようなルールが必要
// type can_logger_service_exec, exec_type, file_type, vendor_file_type;
// init_daemon_domain(can_logger_service)
// allow can_logger_service can_device:chr_file { read write open ioctl };

これらのファイルをすべて配置し、AOSPのフルビルドを実行して生成されたイメージを実機またはエミュレータに書き込むことで、車両の起動と同時にCANデータのロギングを開始する、堅牢なシステムサービスが完成します。

デバッグとトラブルシューティング

  • サービスが起動しない: logcatCarServiceや自作サービスのタグをフィルタリングし、初期化中のクラッシュやエラーがないか確認します。ビルド定義やCarService.javaへの登録が正しく行われているか再確認してください。
  • Permission Denied (Java層): logcatで "Permission Denial" を検索します。マニフェストのuses-permissionと、Android.bpprivileged: true設定が正しいか確認します。
  • Permission Denied (SELinux): logcat -b all | grep "avc: denied" を実行し、どのリソースへのアクセスが拒否されているか特定します。audit2allowツールを使って、拒否ログから必要なSELinuxルールを生成し、.teファイルに追加します。

まとめと今後の展望

AAOS環境で、車両の起動と同時にサービスを確実に自動実行させることは、単にBOOT_COMPLETEDを待つだけでは達成できない、高度なシステムインテグレーション技術です。本記事では、その核心的な手法を詳細に解説しました。

重要なポイントを再確認します。

  1. 特権アプリが必須: システムレベルの操作には、プラットフォームキーで署名され、/system/priv-appに配置される特権アプリが必要です。
  2. 起動タイミングが鍵: BOOT_COMPLETEDは遅すぎることが多く、CarServiceへの登録やinit.rcを利用することで、より早期かつ確実な起動を実現できます。
  3. SELinuxは避けて通れない: システムリソースやハードウェアにアクセスするためには、適切なSELinuxポリシーの定義が不可欠です。
  4. AOSPビルドへの統合: これらの実装はすべて、Android Studioではなく、AOSPのソースコード内で行う必要があります。

今後のAAOSの進化に伴い、VHALの機能が拡充され、より多くのハードウェア機能が標準化されたAPIを通じてアクセス可能になるでしょう。しかし、カスタムハードウェアの統合や、OEM独自のコア機能を実装する際には、本記事で解説したような低レベルなシステムサービスの開発技術が依然として重要であり続けることは間違いありません。

AAOS開発は、単なるアプリ開発の延長線上にはなく、Androidフレームワーク、Linuxカーネル、そして車両システム全体への深い理解が求められる、挑戦的でやりがいのある領域です。この記事が、その深淵への第一歩を踏み出すための一助となれば幸いです。

Post a Comment