手机上开发Android车机应用一 预制系统apk

背景

书接上回,在我的Pixel上刷上车机系统后,准备开发一个系统预制的ROOT权限的应用。

榨干Pixel5最后的价值:编译刷写Android12L车机系统-CSDN博客

暂时设计了如下几个功能,目前每天下班回家开发一点,已经写了两个页面:

  • 设备信息
  • APP信息
  • 调试功能
  • 信号模拟
  • 文件浏览
  • 网络交互

想要设计的功能全部开发完毕,需要提升至系统权限,甚至ROOT权限,再修改系统源码配合。

这个全部走完战线比较长,所以现在先研究下如何让自己的apk获取系统权限,集成到系统里,成为不可卸载的预制应用。废话不多说,直接开始本篇主题。

制作系统platform.jks签名

应用想要系统权限,必须以系统的签名文件进行签名。我们的源码在wsl子系统里,接下来说明下如何在源码目录里找到制作系统签名的源文件。

WSL制作jks文件

目前我的app还是采取直接run到设备上进行开发的,想将其预制到系统里。我们先测试下其是否可以直接使用系统签名文件进行签名。首先需要手动制作对应aosp源码系统里的签名文件,不同版本可能不尽相同。

这一步网上教程很多,我们有源码就会更加方便,直接切换到目标目录下进行操作。

cd ~/aaos/build/target/product/security

其中应该有如下文件:

stephen@CODE01:~/aaos/build/target/product/security$ ls
Android.bp              fsverity-release.x509.der  platform.jks       shared.pk8        verity.x509.pem
Android.mk              media.pk8                  platform.p12       shared.x509.pem   verity_key
README                  media.x509.pem             platform.pem       testkey.pk8
cts_uicc_2021.pk8       networkstack.pk8           platform.pk8       testkey.x509.pem
cts_uicc_2021.x509.pem  networkstack.x509.pem      platform.x509.pem  verity.pk8

工具齐全的话,只需要输入三条指令即可生成系统platform的签名了。

第一,生成platform.pem文件

openssl pkcs8 -inform DER -nocrypt -in platform.pk8 -out platform.pem

第二,将在目录下生成platform.p12文件。其中,pass后的字段为签名密码password,name后字段为Keyalias,根据自己喜好设置。

openssl pkcs12 -export -in platform.x509.pem -out platform.p12 -inkey platform.pem -password pass:stephen -name stephen

第三,就是生成jks签名文件了。其中-deststorepass后也会用到上一步设置的password字段。

keytool -importkeystore -deststorepass stephen -destkeystore platform.jks -srckeystore platform.p12 -srcstoretype PKCS12 -srcstorepass stephen 

把 platform.jks 文件从wsl里抠出来,部署到我们应用文件夹里,并在app应用级的gradle里进行配置,这一步应用开发应该都很熟悉了。

release {
    storeFile file('../platform.jks')
    storePassword 'stephen'
    keyAlias 'stephen'
    keyPassword 'stephen'
}

之后在AndroidManifest文件里,设置android:sharedUserId="android.uid.system"和系统进程共享userid,就可以获取到系统权限了。注意app编译完成后,我们需要将系统里已经存在的app给卸载掉重装。

验证platform权限

为了知道是否成功获取系统权限,我们以一个高阶窗口的形式来测试。普通应用想要申请高阶窗口来显示应用,最高只有1999,public static final int LAST_SUB_WINDOW = 1999即应用内部各类窗口,子窗口,最高只能到1999的type层级。

从下面的第一个开始,用于表示系统窗口的开始,非应用程序创建

public static final int FIRST_SYSTEM_WINDOW = 2000;

2000以上就属于系统窗口,想要使用必须要有系统权限。

比如我们普通应用里设置一个window.type=2036,运行后就会报错:permission denied for window type 2036

下面实践开始,封装一个WindowManager类,简单地设置type,添加view:

binding?.btnHighwindow?.setOnClickListener {
    WindowManager.setType(2036)
WindowManager.addView(LayoutInflater.from(requireContext()).inflate(R.layout.layout_highwindowtest, null))
}

结果如下所示,view里添加的布局是一个蓝色色块,并且成功盖住了系统的Dock栏,说明系统权限获取成功。

APK集成

两种方式可供选择:

一种是直接将app的源码一起放到系统源码里,和系统一起集成。

一种是放置apk到系统源码,添加mk文件,再一起集成。

第二种方式比较通用,后期也能和伙伴一起玩,开发各种不同应用集成到一个系统里,就像新手机的那些提前装好的广告软件一样。

我采取的也是第二种方法,直接将编译好的apk放置到系统目录下。

打包apk 编写mk文件

我习惯采用命令行直接使用gradlew脚本的方法来打包,AS的terminal输入:

D:\AndroidStudioProjects\RedfinDemo> ./gradlew assemblerelease

build文件夹下获取apk后,将其复制到D盘根目录,再进WSL新建文件夹,将apk同步过来。

首先需要集成的应用包都在packages/apps/下面,想要新增app的话,需要mkdir创建一个新的文件夹,我的手机上就是RedfinDemo/

stephen@CODE01:~/aaos/packages/apps$ ls
BasicSmsReceiver       DevCamera              ManagedProvisioning    QuickSearchBox             TV
Bluetooth              Dialer                 Messaging              RedfinDemo                 Tag
Browser2               DocumentsUI            Music                  RemoteProvisioner          Test
Calendar               EmergencyInfo          MusicFX                SafetyRegulatoryInfo       ThemePicker
Camera2                Gallery                Nfc                    SampleLocationAttribution  TimeZoneData
Car                    Gallery2               OnDeviceAppPrediction  SecureElement              TimeZoneUpdater
CarrierConfig          HTMLViewer             OneTimeInitializer     Settings                   Traceur
CellBroadcastReceiver  ImsServiceEntitlement  PhoneCommon            SettingsIntelligence       TvSettings
CertInstaller          KeyChain               Protips                SpareParts                 UniversalMediaPlayer
Contacts               Launcher3              Provision              Stk                        WallpaperPicker
DeskClock              LegacyCamera           QuickAccessWallet      StorageManager             WallpaperPicker2

将此前准备好的apk文件,移动到这个文件夹里:

cp /mnt/d/RefginDemo.apk ~/aaos/packages/apps/RedfinDemo

然后还需要进行mk文件编写。即在RedfinDemo的同级目录下,还需要编写一个mk脚本。这个Android.mk脚本是用来告诉系统打包的工具,我们的apk名字叫啥,要放在哪里,,,,因为笔者的C++不是很熟,还未涉及过NDK开发等,所以暂时不处理so动态库的场景。

Android.mk 是Android 提供的一种makefile 文件,注意用来编译生成(exe,so,a,jar,apk)等文件。其格式一般比较固定,以我的makefile文件为例:

# 当前模块路径 my-dir就是当前文件夹,同级目录
LOCAL_PATH:= $(call my-dir)
# 清空当前环境变量
include $(CLEAR_VARS)
# 模组名
LOCAL_MODULE := RedfinDemo
# 指定模块的类型,可不用定义
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_TAGS := optional
LOCAL_BUILT_MODULE_STEM := package.apk
# 后缀,可不用定义
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
# apk签名,PRESIGNED表示其已经签过名,系统编译时不用再次对其签名
LOCAL_CERTIFICATE := PRESIGNED
# 决定安装位置,true则在priv-app下,为false则在app目录下
LOCAL_PRIVILEGED_MODULE := true
# 是否是第三方厂商应用
LOCAL_VENDOR_MODULE := false
LOCAL_SRC_FILES := RedfinDemo.apk

备注,不同安装路径的含义:

/system/framework   用于存放资源型应用(系统框架)。 
/system/app         用于存放系统应用,不能卸载。  
/system/priv-app   Android4.4+新增,系统【核心】应用存放路径。 
/vendor/app         用于存放厂商应用,可以卸载,恢复出厂时恢复。
/data/app          用于存放用户应用,可以卸载,恢复出厂不能恢复。 
/data/app-private   Android4.4+新增,受DRM保护的应用存放路径。

修改系统mk脚本,增加这个新建文件夹的引用

我们在packages/apps下新增了模块,配置了模块的mk文件,那么AOSP系统源码编译时也需要知道这个文件夹是新来的,下次编译时要带带他。我们需要找到这个系统模组的mk文件,并修改它,加上一个RedfinDemo即可:

首先切到~/aaos/build/target/product/,查看内容列表

stephen@CODE01:~/aaos/build/target/product$ ls
AndroidProducts.mk        emulator_system.mk            languages_default.mk            profile_boot_common.mk
OWNERS                    emulator_vendor.mk            languages_full.mk               runtime_libart.mk
aosp_64bitonly_x86_64.mk  full.mk                       mainline_sdk.mk                 sdk.mk
aosp_arm.mk               full_base.mk                  mainline_system.mk              sdk_arm64.mk
aosp_arm64.mk             full_base_telephony.mk        mainline_system_arm64.mk        sdk_phone_arm64.mk
aosp_base.mk              full_x86.mk                   mainline_system_x86.mk          sdk_phone_armv7.mk
aosp_base_telephony.mk    generic.mk                    mainline_system_x86_64.mk       sdk_phone_x86.mk
aosp_product.mk           generic_no_telephony.mk       mainline_system_x86_arm.mk      sdk_phone_x86_64.mk
aosp_x86.mk               generic_ramdisk.mk            media_product.mk                sdk_x86.mk
aosp_x86_64.mk            generic_system.mk             media_system.mk                 sdk_x86_64.mk
aosp_x86_arm.mk           generic_system_arm64.mk       media_system_ext.mk             security
base.mk                   generic_system_x86.mk         media_vendor.mk                 sysconfig
base_product.mk           generic_system_x86_64.mk      module_arm.mk                   telephony.mk
base_system.mk            generic_system_x86_arm.mk     module_arm64.mk                 telephony_product.mk
base_system_ext.mk        generic_x86.mk                module_common.mk                telephony_system.mk
base_vendor.mk            go_defaults.mk                module_x86.mk                   telephony_system_ext.mk
cfi-common.mk             go_defaults_512.mk            module_x86_64.mk                telephony_vendor.mk
core_64_bit.mk            go_defaults_common.mk         product_launched_with_k.mk      updatable_apex.mk
core_64_bit_only.mk       gsi                           product_launched_with_l.mk      userspace_reboot.mk
core_minimal.mk           gsi_keys.mk                   product_launched_with_l_mr1.mk  vboot.mk
default_art_config.mk     gsi_release.mk                product_launched_with_m.mk      verity.mk
developer_gsi_keys.mk     handheld_product.mk           product_launched_with_n.mk      virtual_ab_ota
empty-preloaded-classes   handheld_system.mk            product_launched_with_n_mr1.mk  virtual_ab_ota.mk
empty-profile             handheld_system_ext.mk        product_launched_with_o.mk      virtual_ab_ota_plus_non_ab.mk
emulated_storage.mk       handheld_vendor.mk            product_launched_with_o_mr1.mk  virtual_ab_ota_retrofit.mk
emulator.mk               iorap_large_memory_config.mk  product_launched_with_p.mk

可以看到有一个base_system.mk,这里就是我们需要的文件。直接使用vi工具修改它,vi工具的使用这里不多说,属于linux基础技能。

打开后,第一个项目就是,这里就是参与编译的各个模组了,我们在这个条目的最后追加新建的模组。

stephen@CODE01:~/aaos/build/target/product$ cat base_system.mk
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Base modules and settings for the system partition.
PRODUCT_PACKAGES += \
    abx \
    adbd_system_api \
    am \
    android.hidl.allocator@1.0-service \
    android.hidl.base-V1.0-java \
    android.hidl.manager-V1.0-java \
    android.hidl.memory@1.0-impl \
    android.hidl.memory@1.0-impl.vendor \
···
    RedfinDemo \

按 I 进入编辑模式,编辑完成,按下esc,输入:wq,保存即可

再次lunch起编

到aaos根目录,依然是上次的那几条编译指令:

. build/envsetup.sh
lunch
# 出现待选列表,找到redfin-car,我的是第31项,输入数字31
m

Google采取了增量编译的方案,这个小改动,在我的电脑上只需要一分钟就编完了,将新的编译产物刷写到Pixel上。然后remount成功后推送vendor下的文件,等待设备重启。

刷写完进不去系统解决

这次信心满满的配置编译之后,却发现系统虽可以执行adb root,却无法remount,一直黑屏,adb remount提示:

Cannot use remount when a checkpoint is in progress.

可以通过AS看日志,了解到系统其实已经起来了,看我们RedfinDemo的logcat,循环显示

 java.lang.IllegalStateException: Signature|privileged permissions not in privapp-permissions allowlist

改报错说明应用反复崩溃,系统权限检查一直无法完成。所以无法remount完成,也无法sync过去vendor的内容,我们也就无法进入系统玩耍了。

这就是为什么,系统工程师在集成新apk时,或者有新增权限时,都会要求应用开发者要将自己所有的权限都提前告知,他们才能配置,否则系统编出来就是废的。

需要解决这个问题,我们需要给RedfinDemo单独配置其所需的权限,相关的文件就在如下目录,可以打开后直接按照其他应用的格式进行配置。

cd ~/aaos/frameworks/base/data/etc
# cat查看文件内容进行确认
cat privapp-permissions-platform.xml

由于我想自己的应用已经拿到系统权限了,所以应该不用单独在manifest里声明权限,但是这个猜测还没求证。故为了当晚可以顺利集成我的apk,我是直接把应用manifest里声明的权限都删除了,重新打了一个apk,放到apps目录,在make起编,最后刷写的。后面我再去单独验证这个权限文件的配置是否有效。

再次刷写后效果

进入shell,查看系统应用目录,可以找到我们的RedfinDemo了,launcher上也有图标,表明已经成功集成。

redfin:/ # cd system/priv-app/RedfinDemo/
redfin:/system/priv-app/RedfinDemo # ls
RedfinDemo.apk
redfin:/system/priv-app/RedfinDemo #

launcher图标入口

第三方安装的应用,在系统自带的设置里,会有一个卸载按钮,如下某app所示:

而我们的RedfinDemo,是不可卸载的:

至此,我们成功地将一款外部应用打包进了系统目录下,有编制了。

最后汇报应用开发的进度,仍然是开始的那一些内容,在好友的建议下升级了app列表的UI,果真比之前好看太多了!

首页

app列表

下面的计划:

  • 研究ROOT权限的获取,权限到手后,就是应用的开发居多了。
  • 肯定也需要修改framework里的若干代码,来达到我的一些特殊需求。

另外,还会把一些很好用的工具按照这个方法,都打包到系统里。比如Android Terminal、Chrome、Auto.JS等,刷机前一起编进系统,就不用每次都单独来install了。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值