Android13 automotive移植

2023年 9月 2日 111.5k 0

需求背景

目前Google已经释放Android 13的基线代码,其中包括了automotive的product,但芯片厂商针对automotive的Android 13基线并未释放,在释放前,下面的方案商无法得知芯片厂商的将会以哪个product来释放automotive基线,因此,需要提前预研在其他product上移植automotive功能。

相关概念

  • automotive

    翻译过来就是汽车的意思,它是基于Android平台的车载信息娱乐系统,简称IVI(In-Vehicle Infotainment),在Android 13中,automotive长这样

automotive桌面.png

  • product

    AOSP针对不同的设备,设置的不同的产品。我们在编译源码时,执行lunch命令后展示的列表就是当前基线提供的所有product

    q@jason-OptiPlex-7080:/projects/AOSP_Android13$ source build/envsetup.sh 
    q@jason-OptiPlex-7080:/projects/AOSP_Android13$ lunch
    
    You're building on Linux
    
    Lunch menu .. Here are the common combinations:
         1. aosp_arm-eng
         2. aosp_arm64-eng
         3. aosp_barbet-userdebug
         4. aosp_bluejay-userdebug
         5. aosp_bluejay_car-userdebug
         6. aosp_bramble-userdebug
         7. aosp_bramble_car-userdebug
         8. aosp_car_arm-userdebug
         9. aosp_car_arm64-userdebug
         10. aosp_car_x86-userdebug
         11. aosp_car_x86_64-userdebug
         12. aosp_cf_arm64_auto-userdebug
         13. aosp_cf_arm64_phone-userdebug
         14. aosp_cf_x86_64_foldable-userdebug
         15. aosp_cf_x86_64_pc-userdebug
         16. aosp_cf_x86_64_phone-userdebug
         17. aosp_cf_x86_64_tv-userdebug
         18. aosp_cf_x86_auto-userdebug
         19. aosp_cf_x86_phone-userdebug
         20. aosp_cf_x86_tv-userdebug
         21. aosp_cheetah-userdebug
         22. aosp_cloudripper-userdebug
         23. aosp_coral-userdebug
         24. aosp_coral_car-userdebug
         25. aosp_flame-userdebug
         26. aosp_flame_car-userdebug
         27. aosp_oriole-userdebug
         28. aosp_oriole_car-userdebug
         29. aosp_panther-userdebug
         30. aosp_raven-userdebug
         31. aosp_raven_car-userdebug
         32. aosp_ravenclaw-userdebug
         33. aosp_redfin-userdebug
         34. aosp_redfin_car-userdebug
         35. aosp_redfin_vf-userdebug
         36. aosp_rice14-userdebug
         37. aosp_slider-userdebug
         38. aosp_sunfish-userdebug
         39. aosp_sunfish_car-userdebug
         40. aosp_trout_arm64-userdebug
         41. aosp_trout_x86-userdebug
         42. aosp_whitefin-userdebug
         43. aosp_x86-eng
         44. aosp_x86_64-eng
         45. arm_krait-eng
         46. arm_v7_v8-eng
         47. armv8-eng
         48. armv8_cortex_a55-eng
         49. armv8_kryo385-eng
         50. beagle_x15-userdebug
         51. beagle_x15_auto-userdebug
         52. car_ui_portrait-userdebug
         53. car_x86_64-userdebug
         54. db845c-userdebug
         55. gsi_car_arm64-userdebug
         56. gsi_car_x86_64-userdebug
         57. hikey-userdebug
         58. hikey64_only-userdebug
         59. hikey960-userdebug
         60. hikey960_tv-userdebug
         61. hikey_tv-userdebug
         62. poplar-eng
         63. poplar-user
         64. poplar-userdebug
         65. qemu_trusty_arm64-userdebug
         66. rb5-userdebug
         67. sdk_car_arm-userdebug
         68. sdk_car_arm64-userdebug
         69. sdk_car_portrait_x86_64-userdebug
         70. sdk_car_x86-userdebug
         71. sdk_car_x86_64-userdebug
         72. sdk_pc_x86_64-userdebug
         73. silvermont-eng
         74. uml-userdebug
         75. yukawa-userdebug
         76. yukawa_sei510-userdebug
    
    Which would you like? [aosp_arm-eng]
    Pick from common choices above (e.g. 13) or specify your own (e.g. aosp_barbet-eng):
    

    不同的product打包出来的镜像略有不同,最后在设备上展示的效果也不一样,比如,手机刷的镜像跟车载刷的镜像,Launcher,Settings等不尽相同。在上面lunch的product列表中,带有car标识的都是automotive product,其他的大部分是phone product。

  • automotive移植

    就是将automotive的功能移植到非automotive的product上,即将非automotive product改造成automotive product,本篇我们将72. sdk_pc_x86_64-userdebug改造成71. sdk_car_x86_64-userdebug72. sdk_pc_x86_64-userdebug长这样

pc桌面.png

​ 现在我们要将它改造成上面automotive的样子

product编译配置

每个product的编译离不开四个文件:

  • AndroidProducts.mk:定义product的名字,以及对应的Makefile文件
  • product.mk:对应product的Makefile文件,比如sdk_pc_x86_64.mk,里面包含了product所有的软件配置
  • BoardConfig.mk:对应product的硬件配置信息
  • device.mk:芯片厂商自定义的硬件配置信息

​ 看一下我们要改造的72. sdk_pc_x86_64-userdebug这四个文件的内容

#AndroidProducts.mk
PRODUCT_MAKEFILES := 
    $(LOCAL_DIR)/sdk_pc_x86_64.mk 

COMMON_LUNCH_CHOICES := 
    sdk_pc_x86_64-userdebug 

COMMON_LUNCH_CHOICES定义了lunch combo名称为sdk_pc_x86_64-userdebug,该名称包括产品名+构建模式,userdebug表示调试模式,此外还有user模式和eng模式,关于这三个模式的区别,这里不做过多介绍,可自行查阅相关资料。PRODUCT_MAKEFILES指定了product的makefile文件,$(LOCAL_DIR)表示当前目录。

#sdk_pc_x86_64.mk
PRODUCT_COPY_FILES += device/generic/goldfish/pc/config.ini.pc:config.ini

$(call inherit-product, $(SRC_TARGET_DIR)/product/sdk_x86_64.mk)

PRODUCT_NAME := sdk_pc_x86_64
PRODUCT_DEVICE := emulator64_x86_64
PRODUCT_BRAND := Android
PRODUCT_MODEL := PC on x86_64 emulator
PRODUCT_PACKAGE_OVERLAYS := device/generic/goldfish/pc/overlay
PRODUCT_COPY_FILES += 
    device/generic/goldfish/pc/data/etc/pc.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/pc.xml
PRODUCT_SDK_ADDON_SYS_IMG_SOURCE_PROP := device/generic/goldfish/pc/images_source.prop_template

sdk_pc_x86_64.mk文件里定义了产品的name、device硬件配置、brand、model等信息,其中PRODUCT_DEVICE指向BoardConfig.mkdevice.mk的父目录

BoardConfig目录.png

$(call inherit-product, $(SRC_TARGET_DIR)/product/sdk_x86_64.mk)表示该文件继承自sdk_x86_64.mk$(SRC_TARGET_DIR)指向build/target目录。

PRODUCT_COPY_FILES表示将源码中的文件复制到镜像分区中

#BoardConfig.mk
# x86_64 emulator specific definitions
TARGET_CPU_ABI := x86_64
TARGET_ARCH := x86_64
TARGET_ARCH_VARIANT := x86_64
TARGET_2ND_ARCH_VARIANT := x86_64

BOARD_DO_NOT_STRIP_VENDOR_MODULES := true

TARGET_PRELINK_MODULE := false
include build/make/target/board/BoardConfigGsiCommon.mk
include build/make/target/board/BoardConfigEmuCommon.mk

BOARD_USERDATAIMAGE_PARTITION_SIZE := 576716800

BOARD_SEPOLICY_DIRS += device/generic/goldfish/sepolicy/x86

# Wifi.
BOARD_WLAN_DEVICE           := emulator
BOARD_HOSTAPD_DRIVER        := NL80211
BOARD_WPA_SUPPLICANT_DRIVER := NL80211
BOARD_HOSTAPD_PRIVATE_LIB   := lib_driver_cmd_simulated
BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_simulated
WPA_SUPPLICANT_VERSION      := VER_0_8_X
WIFI_DRIVER_FW_PATH_PARAM   := "/dev/null"
WIFI_DRIVER_FW_PATH_STA     := "/dev/null"
WIFI_DRIVER_FW_PATH_AP      := "/dev/null"

BoardConfig.mk中定义了cpu的参数,wifi配置,并且通过include引用了BoardConfigGsiCommon.mkBoardConfigEmuCommon.mk文件里的配置,前者用于通用系统映像的配置,后者用于模拟器的配置。

device.mk是芯片厂商自定义的配置,这里我们看的是原生代码,分析此文件没啥意义。

上述是72. sdk_pc_x86_64-userdebug的编译配置文件,同理可析71. sdk_car_x86_64-userdebug

product编译流程

当我们执行source build/envsetup.sh命令时,会将envsetup.sh文件中定义的函数加载到当前shell环境,建立shell命令,比如lunchmakecroot等。

当执行lunch命令时,会执行envsetup.sh中的lunch()函数

function lunch()
{
	# 定义局部变量`answer`来存放用户通过`lunch`命令传进来的参数(即`lunch-combo`,可以是字符串或数字)
    local answer

    if [[ $# -gt 1 ]]; then
        echo "usage: lunch [target]" >&2
        return 1
    fi

    local used_lunch_menu=0

    if [ "$1" ]; then
        answer=$1
    else
    	# 如果用户没有没有传进来任何值,则打印出`lunch-combo`列表,提示用户输入
        print_lunch_menu
        echo "Which would you like? [aosp_arm-eng]"
        echo -n "Pick from common choices above (e.g. 13) or specify your own (e.g. aosp_barbet-eng): "
        read answer
        used_lunch_menu=1
    fi

    local selection=

    if [ -z "$answer" ]
    then
    	#如果用户没有选择,则默认选择为aosp_arm-eng
        selection=aosp_arm-eng
    elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
    then
        local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
        if [ $answer -le ${#choices[@]} ]
        then
            # array in zsh starts from 1 instead of 0.
            if [ -n "$ZSH_VERSION" ]
            then
                selection=${choices[$(($answer))]}
            else
                selection=${choices[$(($answer-1))]}
            fi
        fi
    else
        selection=$answer
    fi

    #省略部分代码

    TARGET_PRODUCT=$product 
    TARGET_BUILD_VARIANT=$variant 
    TARGET_PLATFORM_VERSION=$version 
    #build_build_var_cache()最终会执行到soong_ui.bash
    build_build_var_cache
    if [ $? -ne 0 ]
    then
        if [[ "$product" =~ .*_(eng|user|userdebug) ]]
        then
            echo "Did you mean -${product/*_/}? (dash instead of underscore)"
        fi
        return 1
    fi
    #get_build_var()最终会执行到soong_ui.bash
    export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
    export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
    if [ -n "$version" ]; then
      export TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)
    else
      unset TARGET_PLATFORM_VERSION
    fi
    
    #省略部分代码
}

# Get the exact value of a build variable.
function get_build_var()
{
    if [ "$BUILD_VAR_CACHE_READY" = "true" ]
    then
        eval "echo "${var_cache_$1}""
        return 0
    fi

    local T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP." >&2
        return 1
    fi
    #执行soong_ui.bash
    (cd $T; build/soong/soong_ui.bash --dumpvar-mode $1)
}

lunch()函数最终会执行build/soong/soong_ui.bash脚本

#记录了开始构建时的精确时间戳,以毫秒为单位
export TRACE_BEGIN_SOONG=$(date +%s%N)

# Function to find top of the source tree (if $TOP isn't set) by walking up the
# tree.
#定义gettop函数
function gettop
{
	#找到源代码树的根目录,如果环境变量 TOP 已经设置并且对应的路径存在 build/soong/root.bp 文件,那么函数将返回该路径;否则,它将从当前目录向上查找,直到找到名为 build/soong/root.bp 的文件。
    local TOPFILE=build/soong/root.bp
    if [ -n "${TOP-}" -a -f "${TOP-}/${TOPFILE}" ] ; then
        # The following circumlocution ensures we remove symlinks from TOP.
        (cd $TOP; PWD= /bin/pwd)
    else
        if [ -f $TOPFILE ] ; then
            # The following circumlocution (repeated below as well) ensures
            # that we record the true directory name and not one that is
            # faked up with symlink names.
            PWD= /bin/pwd
        else
            local HERE=$PWD
            T=
            while [ ( ! ( -f $TOPFILE ) ) -a ( $PWD != "/" ) ]; do
                cd ..
                T=`PWD= /bin/pwd -P`
            done
            cd $HERE
            if [ -f "$T/$TOPFILE" ]; then
                echo $T
            fi
        fi
    fi
}

#将当前工作目录(PWD)保存到环境变量 ORIGINAL_PWD 中
# Save the current PWD for use in soong_ui
export ORIGINAL_PWD=${PWD}
#调用 gettop 函数,并将返回的结果保存到环境变量 TOP 中
export TOP=$(gettop)
#加载 microfactory.bash 脚本,该脚本应该位于 TOP 环境变量所指示的路径中
source ${TOP}/build/soong/scripts/microfactory.bash

#调用 Android 构建系统的soong_ui,具体构建和运行某些组件
soong_build_go soong_ui android/soong/cmd/soong_ui
soong_build_go mk2rbc android/soong/mk2rbc/cmd
soong_build_go rbcrun rbcrun/cmd

#将当前目录切换到 TOP 环境变量所指示的路径
cd ${TOP}
#执行 soong_ui 程序,其路径为 getoutdir 环境变量的值,参数为 $@(表示所有传入的命令行参数)
exec "$(getoutdir)/soong_ui" "$@"

上面在执行soong_build_go soong_ui android/soong/cmd/soong_ui时,会执行/build/soong/cmd/soong_ui/main.go中的main()函数

func main() {
    //省略部分代码,省略的代码为构造build.Findsources()函数所需要的参数
	// Create a source finder.
	f := build.NewSourceFinder(buildCtx, config)
	defer f.Shutdown()
    //寻找资源文件
	build.FindSources(buildCtx, config, f)
	
	c.run(buildCtx, config, args, logsDir)
}

/build/soong/cmd/soong_ui/main.go中的main()函数中,核心主要是通过build.FindSources()去寻找所有的makefile文件和bp文件

// FindSources searches for source files known to  and writes them to the filesystem for
// use later.
func FindSources(ctx Context, config Config, f *finder.Finder) {
	// note that dumpDir in FindSources may be different than dumpDir in NewSourceFinder
	// if a caller such as multiproduct_kati wants to share one Finder among several builds
	dumpDir := config.FileListDir()
	os.MkdirAll(dumpDir, 0777)

	// Stop searching a subdirectory recursively after finding an Android.mk.
    //寻找所有的Android.mk文件,并添加到Android.mk.list中
	androidMks := f.FindFirstNamedAt(".", "Android.mk")
	err := dumpListToFile(ctx, config, androidMks, filepath.Join(dumpDir, "Android.mk.list"))
	if err != nil {
		ctx.Fatalf("Could not export module list: %v", err)
	}

	// Gate collecting/reporting mk metrics on builds that specifically request
	// it, as identifying the total number of mk files adds 4-5ms onto null
	// builds.
	if config.reportMkMetrics {
		androidMksTotal := f.FindNamedAt(".", "Android.mk")

		ctx.Metrics.SetToplevelMakefiles(len(androidMks))
		ctx.Metrics.SetTotalMakefiles(len(androidMksTotal))
		ctx.Metrics.DumpMkMetrics(config.MkMetrics())
	}

	// Stop searching a subdirectory recursively after finding a CleanSpec.mk.
    //寻找所有的CleanSpec.mk文件,并添加到CleanSpec.mk.list中
	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
	if err != nil {
		ctx.Fatalf("Could not export module list: %v", err)
	}

	// Only consider AndroidProducts.mk in device/, vendor/ and product/, recursively in these directories.
    //寻找所有的AndroidProducts.mk文件,并添加到AndroidProducts.mk.list中
	androidProductsMks := f.FindNamedAt("device", "AndroidProducts.mk")
	androidProductsMks = append(androidProductsMks, f.FindNamedAt("vendor", "AndroidProducts.mk")...)
	androidProductsMks = append(androidProductsMks, f.FindNamedAt("product", "AndroidProducts.mk")...)
	err = dumpListToFile(ctx, config, androidProductsMks, filepath.Join(dumpDir, "AndroidProducts.mk.list"))
	if err != nil {
		ctx.Fatalf("Could not export product list: %v", err)
	}

	// Recursively look for all Bazel related files.
	bazelFiles := f.FindMatching(".", findBazelFiles)
	err = dumpListToFile(ctx, config, bazelFiles, filepath.Join(dumpDir, "bazel.list"))
	if err != nil {
		ctx.Fatalf("Could not export bazel BUILD list: %v", err)
	}

	// Recursively look for all OWNERS files.
	owners := f.FindNamedAt(".", "OWNERS")
	err = dumpListToFile(ctx, config, owners, filepath.Join(dumpDir, "OWNERS.list"))
	if err != nil {
		ctx.Fatalf("Could not find OWNERS: %v", err)
	}

	// Recursively look for all TEST_MAPPING files.
	testMappings := f.FindNamedAt(".", "TEST_MAPPING")
	err = dumpListToFile(ctx, config, testMappings, filepath.Join(dumpDir, "TEST_MAPPING.list"))
	if err != nil {
		ctx.Fatalf("Could not find TEST_MAPPING: %v", err)
	}

	// Recursively look for all Android.bp files
    //寻找所有的Android.bp文件,并添加到Android.bp.list中
	androidBps := f.FindNamedAt(".", "Android.bp")
	if len(androidBps) == 0 {
		ctx.Fatalf("No Android.bp found")
	}
	err = dumpListToFile(ctx, config, androidBps, filepath.Join(dumpDir, "Android.bp.list"))
	if err != nil {
		ctx.Fatalf("Could not find modules: %v", err)
	}

	// Recursively look for all product/board config files.
	configurationFiles := f.FindMatching(".", findProductAndBoardConfigFiles)
	err = dumpListToFile(ctx, config, configurationFiles, filepath.Join(dumpDir, "configuration.list"))
	if err != nil {
		ctx.Fatalf("Could not export product/board configuration list: %v", err)
	}

	if config.Dist() {
		f.WaitForDbDump()
		// Dist the files.db plain text database.
		distFile(ctx, config, f.DbPath, "module_paths")
	}
}

执行完build.FindSources()后,所有的AndroidProduct.mk文件都将被发现,这时就能展示所有的lunch combo项,然后根据用户选择的combo,选择对应的AndroidProduct.mk

最后执行make -j16命令后,调用build/envsetup.sh里面的make()函数,最终会根据对应AndroidProduct.mk的规则,选择对应的product.mk进行编译。

移植思路

了解了product编译流程后,自然而然我们能想到的移植思路是,在72. sdk_pc_x86_64-userdebugAndroidProduct.mk文件里配置71. sdk_car_x86_64-userdebug的product.mk,也就是将72. sdk_pc_x86_64-userdebug的编译流程改向71. sdk_car_x86_64-userdebug的编译流程,这样我们的目标product就会按照车载product的配置进行编译了。查看车载product的编译配置,即打开sdk_car_x86_64.mk文件

PRODUCT_PACKAGE_OVERLAYS := device/generic/car/common/overlay

$(call inherit-product, device/generic/car/emulator/aosp_car_emulator.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/sdk_x86_64.mk)

EMULATOR_VENDOR_NO_SOUND := true
PRODUCT_NAME := sdk_car_x86_64
PRODUCT_DEVICE := emulator_car_x86_64
PRODUCT_BRAND := Android
PRODUCT_MODEL := Car on x86_64 emulator

可以看到,这里只是继承了device/generic/car/emulator/aosp_car_emulator.mk$(SRC_TARGET_DIR)/product/sdk_x86_64.mk这两个makefile文件,以及添加了PRODUCT_PACKAGE_OVERLAYSEMULATOR_VENDOR_NO_SOUND两项配置,我们将这些配置全部复制到sdk_pc_x86_64.mk

移植patch.png

当然,PRODUCT_NAME是不能变的,并且PRODUCT_DEVICE选择车载的硬件配置。

最后执行编译

source build/envsetup.sh
lunch sdk_pc_x86_64-userdebug
make -j16

等待编译完成,启动模拟器

emulator

完美进入automotive,并且Model显示我们自定义的名称

移植后的桌面.png

移植成功.png

参考资料

blog.csdn.net/daoshuti/ar…

写给应用开发的 Android Framework 教程——玩转 AOSP 篇之添加 Product

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论