🐸

オリジナルの自動車用Android OSを作る - AOSP開発はじめの一歩

2023/08/02に公開

はじめに

Turing株式会社UXチームエンジニアの井上(@yoinoue5212)です。

Turingは完全自動運転EVの開発を目標に、自動運転AIとEV本体の両面での開発に挑戦しています。UXチームでは、自社EVのIVI(In-Vehicle Infotainment)つまりセンターディスプレイ等に表示されるシステムのOSとして、Androidを基盤とする独自車載OSの開発を行っています。

本記事では、ソースコードの公開されているAndroid Open Source Project(AOSP)を題材に、Android OSを開発するとはどういうことか、開発のための環境構築についてお話しします。

AOSPとは

何の略?

AOSPはAndroid Open Source Projectの略で、Android OSを構成するすべての要素がオープンソースで公開されています。

Googleの開発した最新のOSは一定の非公開期間を経たのち、オープンソースとして公開されます。この公開されたOSをベースに、デバイス開発元が用途に合わせた機能追加や修正を加え、自社のスマートフォンやタブレットなど各種端末にOSを搭載します。

ライセンス

AOSPは"Apache License, Version 2.0"というライセンスが主としてつけられています。このApache2.0というライセンスは、無料で商用利用が可能で、改変・修正後の再頒布も許可されています。そのため、AOSPのソースコードをベースに作成した独自OSを商品に組み込んで販売することが出来ます。

しかしAOSPの場合、開発したOSをAndroid™という記載のうえ販売するにはライセンス料をGoogleへ支払う必要があります。これにより、Androidという巨大OSを基盤に持つ安全なOSであることを証明できます。技術的には、公式のAndroidとの互換性を損ねていないことを証明するためのCTS(Compatibility Test Suite)VTS(Vendor Test Suite)というテストを通過することが求められます。

また、ライセンス料を支払うことでGoogle社製の各種アプリケーションが利用可能になります。Google Play Store, Google Map, Chrome, GMail, “OK, Google”のボイスアシスタントなどが該当します。

構成要素

Android OSアーキテクチャ
Android OSアーキテクチャ

Androidは非常に巨大なOSで構成要素も多岐にわたります。

最下層にはLinuxカーネルが存在し、Kernel上で動作するC/C++で書かれたデーモンやライブラリがあります。

その上には、HAL(Hardware Abstarct Layer)というハードウェアとソフトウェアとの通信を請け負う層が存在します。この層を介することで、ソフトウェア側は特定のハードウェアに依存せず、実行したい内容を記述することが出来ます。

さらに上には、Android Runtime(ART)というプログラム実行のための仮想マシンが配置されています。AndroidではJavaやKotlinといった言語をサポートしており、これらの言語でコーディングされたアプリケーションは配布時に.apkという拡張子でバイトコードに変換されています。アプリのインストール時にバイトコードから端末特有のネイティブコードに事前に変換され、ART上でこのネイティブコードが実行されています。

ARTより上の要素としては、System Service, Android Framework, Appがあります。

System Serviceは、OSの動作に関連したタスクをバックグラウンドで常に処理するコンポーネントを指します。アプリケーション群の死活監視や通知管理、WiFiやBlueToothといったデバイスとのやり取りなどが行われています。アプリがこれらの情報を必要としたり、何らかの操作を行いたい場合にアクセスが必要となります。アプリ開発においてAndroidManifest.xmlの中で<service>で定義されるサービスコンポーネントとは似て非なるものであり、OSが親となり停止などの制御は基本不可能であるという特徴があります。

Android Frameworkは、Androidアプリを開発する際にUIコンポーネントやウィンドウ情報、ステータスバーに通知を行うためのAPI、連絡帳などの別のアプリの保有する情報を取得するためのAPI、…といったAndroid OSの機能を提供するための巨大なライブラリのような存在です。

最後に、Android Appは想像するようないわゆるアプリで、一覧にアイコンが並ぶYouTubeやNetflixを指します。先ほど説明したSystem ServiceやAndroid Frameworkが提供するAPIなどを活用して開発を行い、UIをデザインして公開します。特殊なシステム権限を必要としない場合、アプリ開発はOS開発とは完全に独立しており、ARTの項目でお話しした.apkファイルの形で配布・インストールすることで簡単にAndroid端末(Android OS)に追加することが出来ます。

実際の構成例

Android OSの構成要素について一通り説明しましたが、実際にこれらの要素はどう組み合って動いているのか、文章だけでは想像がつきにくいかと思います。そこで、具体例を示して説明します。

WiFiの機能を使ったアプリの構成例
WiFiの機能を使ったアプリの構成例

AppsがUIを備えたアプリ本体です。AppsはAndroid Frameworkの提供するAPIであるandroid.net.wifiをJava/Kotlinコード内でimportすることで、WiFiに関する情報を取得したりWiFiの有効化・切断などの操作を行えます。APIはAndroid独自のIPC(InterProcess Communication, プロセス間通信)であるBinderを用いてSystem Serviceにアクセスします。

図中のWi-Fi SERVICEの4つはいずれもSystem Serviceで、WiFiアクセスポイントのスキャンや接続状況の監視などをバックグラウンドで常に行っています。図中では、これを実現するための2つの方法が記載されています。

1つ目が、右側のService→AIDL/HIDL→HAL→ハードウェアドライバという構成です。Serviceから出ているAIDL(Android Interface Definition Language) / HIDL(Hardware Interface Definition Language)は異なるコンポーネント間でIPCを行う際に用いられるインターフェース定義言語です。とりわけHALとのやり取りにはCに近いHIDL言語で定義されたHIDLが使われます。最後にHALを介してハードウェアドライバに到達します。

2つ目が、左側のService→Binder→wificond→nl80211→ハードウェアドライバという構成です。ServiceはBinderでwificondというWiFiハードウェアとやり取りするために生まれたプロセスと繋がっています。wificondはnl80211コマンドを利用してハードウェアドライバにアクセスします。wificondはAndroid8.0(コードネームOreo, 2017/8リリース)で追加された概念です。

以上が、AOSPに関する概要とOSの大きな理解となります。ハードウェアと接続必須なソフトウェアであるだけにやや構成要素は多いですが、非常に良くモジュール化がされており拡張性、安全性が高いことが伺えるかと思います。

では、実際に自ら開発を行うには何を用意して、どこで作業すればよいでしょうか。その答えとなる環境構築について、次にお話しします。

AOSP開発のための環境構築

本章では主に、WSL上でIntelliJ IDEAを動作させるためのセットアップ手順を説明します。

ビルド機

筆者のビルド機スペック

  • WSL2, Ubuntu20.04
  • ディスク容量2TB
  • RAM32GB

公式の要件は以下の通りです(ドキュメントページ)

  • Ubuntu18.04
  • 400GB以上 (コードチェックアウト250GB, ビルド150GB)
  • RAM16GB (推奨64GB以上)

Repo

Android OSはコンポーネントのレベルで見ると、さらに非常に多くの構成要素から実現されるシステムです。コンポーネントの結合は疎で、それぞれが独立したGitレポジトリで管理・開発がされています。これら多くのGitレポジトリをManifestファイルと呼ばれる管理ファイルをもとに管理するのがRepoというツールになります。

repoインストール & AOSPコード取得

  1. repoコマンドのインストール

    cd
    mkdir ~/bin
    PATH=~/bin:$PATH
    curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
    chmod a+x ~/bin/repo
    
  2. Git情報の登録

    git config --global user.name <ユーザ名>
    git config --global user.email <メールアドレス>
    
  3. AOSPビルド環境構築

    sudo apt update
    sudo apt-get install git gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig
    sudo apt-get install bc coreutils dosfstools e2fsprogs fdisk kpartx mtools ninja-build pkg-config python3-pip
    sudo pip3 install meson mako jinja2 ply pyyaml
    
  4. Repoの初期化

    mkdir ~/aosp
    cd ~/aosp
    repo init -u https://android.googlesource.com/platform/manifest -b <tag name>
    

    repo initコマンドの-bオプションの後にタグ名を指定してください。タグ名とAndroidバージョン/サポート対象デバイスの対応はこちらから確認できます。

  5. AOSPソースコード取得
    非常に大きなダウンロードが始まるので、デバイスのディスク容量・ネットワーク帯域に注意してください。容量は250GB以上必要です。

    repo sync -c -j8
    

IntelliJ IDEA

Android開発はAndroid Studioというアプリ開発用のGoogle公式のIDE(Integrated Development Environment, 統合開発環境)で行われるのが一般的ですが、これはアプリ開発の場合の話です。OS開発では、多くのパッケージを開発の対象とするため、複数パッケージ間でのコードジャンプなど機能が豊富なIntelliJ IDEAというツールがおすすめです。また、Android StudioはIntelliJをUI作成などアプリ開発に特化させたものという関係性があります。

インストール方法

  1. WSLgが使えるか確認

    GUI表示の確認用にx11-appsをインストールする

    sudo apt-get install x11-apps
    

    xeyesというコマンドをうって下の目玉が表示されたらOKです。

    xeyes
    マウスを動かすとキョロキョロします

  2. IntelliJ IDEAのLinux版(.tar.gz)をダウンロード

    IntelliJ IDEAには、2つのエディションが存在します。エディション間の違いはこちらのページをご覧ください。

    ダウンロードコマンドは以下の通りです。なお、記事執筆時点(2023/7/31)時点での最新版は2023.2です。

    • Community Edition

      wget https://download.jetbrains.com/idea/ideaIC-2023.2.tar.gz
      
    • Ultimate Edition

      wget https://download.jetbrains.com/idea/ideaIU-2023.2.tar.gz
      

    手順5,6,7はUltimate Editionを使う場合のみ必要な手順です。

    最新版を公式サイトで確認してダウンロードするようにして下さい。

  3. インストール

    • Community Edition

      sudo tar -xzf ideaIC-2023.2.tar.gz -C /opt
      
    • Ultimate Edition

      sudo tar -xzf ideaIU-2023.2.tar.gz -C /opt
      
  4. ideaコマンドで起動できるようにする

    • Community Edition

      sudo ln -s /opt/idea-IC-232.8660.185/bin/idea.sh /usr/local/bin/idea
      
    • Ultimate

      sudo ln -s /opt/idea-IU-232.8680.185/bin/idea.sh /usr/local/bin/idea
      
  5. ライセンスコードを発行する
    JetBrainのサイトに会員登録をして、ライセンスコードをトライアル取得または購入してください。
    LicenseID: XXXXXXXのように表示されます。

  6. ライセンスコードを登録する


    登録画面

    ideaコマンド入力後、確認事項に答えると上のような画面が出現します。

    ログイン用にWSLにChromeをインストールしておきます。

    cd /tmp
    sudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
    sudo dpkg -i google-chrome-stable_current_amd64.deb
    

    dpkgコマンドでエラーが発生した場合、以下のコマンドを実行した後、再度試してみてください。

    sudo apt update
    sudo apt install --fix-broken -y
    

    google-chromeと打ってブラウザが表示されれば問題ないです。IntelliJ IDEAに戻ります。
    Log in to JetBrains Accoun…をクリックするとブラウザが立ち上がりログインするよう要求されます。

    ログイン後にlocalhostエラーが発生した場合

    Troubles?をクリック、文中のcopy the linkをクリックしてブラウザからアクセス、ログインしてください。少し待つとトークンが表示されるのでIntelliJへ入力してください。

    ライセンスコードの選択肢が現れるのでいずれかを選択してください。

    以上でIntelliJ IDEAを使えるようになっているかと思います。

AIDEGen

IntelliJ IDEAは複数のパッケージ間でのコードジャンプが出来るため、AOSPのような巨大なソースコードの中身の確認やコード改変が容易になるという利点をお話ししました。しかし、ジャンプが出来るとはいえ、ソースコードは膨大なためジャンプ回数が多くなったり、自分がいまどの階層を確認しているのか見失いがちだったり、開発の全体像が掴みにくかったりします。

Android10以降では、AIDEGenという特定のパッケージに関連する部分のソースコードのみをAOSP全体から抽出して、IntelliJを始めとした各種IDEに出力してくれるツールがあります。これを使うことで先ほどの問題点を解決し、より効率的に開発を進めることが出来ます。

なお、AIDEGenはAOSPに既に含まれているので、追加のインストールなどは必要ありません。

使い方

  1. ビルドターゲットを指定する

    source build/envsetup.sh && lunch <TARGET>
    

    ビルドターゲットとは、AndroidOSをデプロイする先のデバイス名とビルドタイプを”-”(ハイフン)でつないだものです。詳しい説明と一覧は公式ドキュメントを参照してください。lunchとだけコマンドを打つことでも確認できます。

  2. パッケージを指定して依存関係にある部分を抽出する

    • IntelliJ IDEA Community Editionを使っている場合

      aidegen <package name> -s -p /opt/idea-IC-232.8660.185/bin/idea.sh
      
    • IntelliJ IDEA Ultimate Editionを使っている場合

      aidegen <package name> -s -p /opt/idea-IU-232.8660.185/bin/idea.sh
      
  3. IntelliJ IDEAで開発をする
    2のコマンドを入力すると少し待った後に自動でIntelliJ IDEAが起動し、依存関係にある部分だけを表示してくれます。Ctrlキーを押しながらジャンプしたいシンボルをクリックすると移動することができます。

車載OS開発をしてみる

Android OSの仕組み、開発環境の構築とお話してきたので、これらを総合して実際の開発がどのように行われるのか簡単な例を示そうと思います。

TuringではAndroid OSを車載向けにカスタムしたAAOS(Android Automotive OS)という派生OSをベースに開発しています。AAOSについては、弊社の過去記事を参照してください。なお、実機確認はこちらの記事同様に行いますので、ソースコード取得やビルド、デプロイは適宜参照をお願いします。

記事にならいAndroid13.0 revision 35を指定している想定です。

ここでは、デフォルトのホーム画面(これをLauncherと呼びます)を少しだけいじってみようと思います。


デフォルトのLauncher

AOSPにはAndroid端末のエミュレータを動かす仕組みもあります。RaspberryPi 4Bが手元にないという方は、こちらの記事を参考にしてみてください。

  1. repoでAOSPソースコードを取得

  2. (追加部分) 関係する部分だけを抽出

    まずはAIDEGenを使ってLauncherの改造に必要な部分だけでIntelliJ IDEAのプロジェクトを作成します。
    今回はRaspberryPi 4Bで動作確認をするので、デバイス名はaosp_rpi4、ビルドタイプはuserdebugとします。

    source build/envsetup.sh && lunch aosp_rpi4-userdebug
    
    aidegen packages/apps/Car/Launcher -s -p /opt/idea-IC-232.8660.185/bin/idea.sh (Community Editionの場合)
    aidegen packages/apps/Car/Launcher -s -p /opt/idea-IU-232.8660.185/bin/idea.sh (Ultimate Editionの場合)
    

    これでLauncherに関連する部分だけが表示されたIntelliJの画面が出てきたと思います。


    Launcherと関連コードのみのIntelliJ IDEAプロジェクト画面

  3. (追加部分) マップを表示させる

    デフォルトの画面右側には大きな緑色のスペースに”No maps application installed. Please contact your car manufacturer.”と表示されており、マップが表示できていません。

    現状、この警告文を表示しているモックアプリの実装部分を見てみると、

    packages/services/Car/car-map-placeholder/AndroidManifest.xml
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.APP_MAPS"/>
    </intent-filter>
    

    といったように、category.DEFAULT, category.APP_MAPSなるインテントが設定されています。この設定がアプリの起動に必要であると考えられます。インテントとはコンポーネント間で通信に使われるもので、アクティビティやサービスの起動、データの送受信に使われることが主です。
    Android地図アプリの例としては、Mapboxというマップ開発プラットフォームを使ったアプリ開発が挙げられます。こちらの記事が詳細にまとまっているので参考にしてください。

    記事末尾で公開して頂いているソースコードを見ると、category.LAUNCHERというインテントが設定されています。今回の目的であるLauncherで表示をするためには、category.DEFAULT,category.APP_MAPSというインテントを追加する必要があります。

    AndroidManifest.xml
       <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
    +    <category android:name="android.intent.category.DEFAULT"/>
    +    <category android:name="android.intent.category.APP_MAPS"/>
       </intent-filter>
    
  4. ビルド

  5. イメージの書き込みとデプロイ

  6. Launcherの変更を確認
    インテントの追加を行ったアプリをインストール→reboot後、以下のように選択画面が表示されます。

    モックアプリとMapboxアプリのどちらを表示するか選択できる
    MapboxDemoを選択すると、無事Launcherにマップが表示されるようになりました。

    右側にMapboxの地図が表示されている

(小話) ビルドシステム

AOSPのビルドシステムというやや特殊な開発の小話をして終わりとしたいと思います。

Android OSのビルドシステムは歴史とともに様々なツールを使うように変更がなされてきました。Android7.0(Nougat)以前はMakeというビルドツールが使われていましたが、NougatからはSoongninjaというツールを使うようになりました。現在はBazelというさらに別のビルドツールへの移行が進められています。


AOSPのビルドフロー

上の画像はMake時代の.mkをSoongの.bpにしたり(androidmk)、.bpを.ninjaファイルにしてninjaに渡したり(soong)、はたまた.mkをいきなり.ninjaにしたり(kati)…となんとかビルドしている図です。

さらに話は複雑化しますが、AndroidのOS開発にはSoongが使われていますが、Androidのアプリ開発にはGradleというビルドツールが使われています。これら2つのビルドツールの違いとしては、Gradleはアプリに必要なライブラリなどの依存関係を管理し、自動でダウンロードして解決できるが、Soongにはそのような機能はない、という違いがあります。

通常のアプリであれば、OSとは全く別に開発を行い.apkファイルにした状態でインストールすればビルドツールの違いは問題になりません。しかし、システム権限といった強力な権限が必要なアプリは、OSが秘匿するAPIへのアクセスが必要であるため、OSビルド時に一緒に組み込む必要があります。このような場合、Android OSのSoongの中でGradleを使うという必要が出てきます。

  1. Android Studioでアプリを作成
    一般的なAndroidアプリ開発の話になるので説明は省きます。
    package名はcom.example.testappとして以降の説明を行います。

  2. build.gradle(project)を編集
    以下の内容を追記することで、アプリビルド時にAndroid Frameworkに存在するシステムAPIにアプリからアクセスできるようにビルドするようになります。

    build.gradle(project)
    allprojects {
        ext {
            headerJarPath = rootDir.parentFile.parentFile.parentFile.parent + '/out/target/common/obj/JAVA_LIBRARIES'
            frameworkHeaderJarPath = headerJarPath + '/framework_intermediates/classes-header.jar'
        }
        tasks.withType(JavaCompile) {
            // Compile with prebuilt jar.
            options.compilerArgs.add('-Xbootclasspath/p:' + frameworkHeaderJarPath)
        }
    }
    
  3. build.gradle(app)を編集

    dependencies{}の中に1行追記します。アプリとFrameworkAPIとの接続を意味します。

    build.gradle(app)
    + compileOnly files(project.frameworkHeaderJarPath)
    

    compileOnlyとは、コンパイル時には必要ですがランタイム時には不要な依存関係を記載するもので、一般に使われるimplementationはランタイム時にも依存関係が含まれるため、その点で異なります。

  4. Android.bpを追加

    Android OSのビルド時にアプリのapkファイルをビルドへ含め、プリインストールアプリ化するために必要です。

    Android.bp
    android_app_import {
        name: "com.example.testapp",
        certificate: "platform",
        privileged: true,
        apk: "app/build/outputs/apk/debug/app-debug.apk",
    }
    

    なお、apkのファイルパスはdebugビルドのパスを指定していますが、必要に応じて適宜変更が必要な箇所となります。

    apk: "app/build/outputs/apk/debug/app-debug.apk",
    
  5. Android OSソースコード内にアプリのソースコードを配置

    cd ~/aosp
    mkdir -p vendor/<Your Name>/apps
    cp -r <Androidアプリのディレクトリパス> vendor/<Your Name>/apps/
    

    配置する場所はvendor/<Your Name>/apps配下としていますが、任意の場所で問題ありません。変更する場合、手順2で設定したbuild.gradle(project)内におけるheaderJarPathの階層を適切に設定し直して下さい。

    build.gradle(project)
    headerJarPath = rootDir.parentFile.parentFile.parentFile.parent + '/out/target/common/obj/JAVA_LIBRARIES'
    
  6. 任意のmkファイルのPRODUCT_PACKAGESにパッケージ名を追加

    PRODUCT_PACKAGES += \
        com.example.testapp
    
  7. Android Studioでビルド

    Android StudioでFile>Open File or ProjectからOS内にコピーしたアプリを開きます。

    Build>Build Bundle(s)/APK(s)>Build APK(s)からapkファイルをAndroidOS内にビルドします。

  8. Android OS全体をビルド

このようにして、Android OSのFrameworkAPIを利用したアプリを通常のアプリ同様にGradleでビルドしながら、プリインストールすることができます。(こちらのブログを参考にしています。)

また、上記の方法では、アプリのビルドで.apkファイルを生成し、それを前提としたOSビルドを行っています。OSビルド時にアプリのgradleによるビルドも呼び出すことで、一連のビルドパイプラインが構成されることになります。

さいごに

UXチームでは、OS開発、アプリ開発のみならず、3D描画用にUnityを組み込んだり、音声操作用に機械学習モデルをデプロイしたり、幅広い領域で開発を進めています。開発された技術は2024~2025年販売の自社EVに搭載され、顧客の方々の手元へと届けられます。

通常のAndroidOSだけでなく車載用のAAOSにも興味を持って頂けた方は、UXチームの過去記事もぜひ読んでみてください。

Turing UXチームでは、特定領域にとらわれず、Turingが提供する新たな車両でのユーザ体験をより良いものとするため、横断的に開発に取り組むことのできるエンジニアを募集しています。

ご興味のある方は、Turing公式サイト採用情報をご覧ください。また、UXチームに限らず、Turingについてお話しできる場として「Turingカジュアル面談の部屋」もあります。こちらもお気軽にご連絡ください。

Tech Blog - Turing

Discussion