【Video Manuscript】Development and Analysis of Car Android Application-Development System Application

Video address of this issue: https://www.bilibili.com/video/BV1NY411z7TK/

foreword

Hello, everyone, I am Lin Xu.

The development of in-vehicle applications is mainly to write various system applications in the Android system, so the previous video first introduced the download and compilation process of the Android system source code. In this video, we begin to introduce how Android system applications are developed.

System Application Introduction

When we start the mobile phone with Android system for the first time, we will find that many applications have been pre-installed in the mobile phone, such as: system settings, desktop and so on. These applications are not installed on the system through ordinary methods, but are directly embedded in the Android ROM and flashed directly to the hardware. Apps installed in this way cannot be uninstalled in the usual way. Only after obtaining root permission, delete the apk file (or flash) in the corresponding directory, otherwise these system applications cannot be removed.

In addition, we will also find that system applications have far more permissions than ordinary applications. Taking system settings as an example, it can switch the user type of the current system, set notification permissions for other applications, and even uninstall ordinary applications on the Android system. application, these functions cannot be realized by ordinary applications, because there are many unpublished APIs in the Android SDK, and these APIs are only allowed to be called by system applications.

Therefore, we can conclude that system applications have the following characteristics:

  1. You can call private APIs that are not disclosed in the Android SDK.
  2. Have higher system privileges.
  3. It is directly embedded in Android ROM, and cannot be uninstalled by ordinary methods.

System Application Prerequisites

Next we demonstrate how to write an Android system application, but before that we need to do the following preparations:

Step 1, make an API package

The characteristics of system applications determine that its development method is not exactly the same as that of ordinary Android applications. First of all, the system application can call the hidden API of the Android SDK, which requires us to introduce the jar package containing the hidden API. Of course, if you do not need to call the hidden API, this step can be skipped. In the actual project, this step will be assisted by the colleagues responsible for framework development, because the farmework layer generally has new interfaces that need to be packaged together.

1) Compile the Android framework

We can use make frameworkthe command to compile the source code of the framework, or use it mmm frameworks/baseand /framework/baseexecute it under the directory mm.

But note that the make command is followed by the module name rather than the path of the module, so it cannot be written as frameworks here.

To compile Android 11 and later versions, the compilation instructions have been adjusted, usemake framework-minus-apex

After the compilation is successful, enter /out/target/common/obj/JAVA_LIBRARIES/framework-minus-apex_intermediates/the directory, which contains classes-header.jarthe jar package we need.

classes-header.jarContains APIs that are not public in the Android SDK, for example: used to enable the RRO mechanism OverlayManager.

If you have not downloaded the AOSP source code, you can download the compiled framework.jar from the github repository of this video. The github address [https://github.com/linxu-link/CarAndroidCourse] can be viewed in the introduction of this video.

2) Import into Android Studio

After generating framework.jar, we import it into Android studio, and add the following code to build.gradle in the project directory.

allprojects{
    gradle.projectsEvaluated {
        //Arctic Fox
        tasks.withType(JavaCompile) {
            Set<File> fileSet = options.bootstrapClasspath.getFiles()
            List<File> newFileList = new ArrayList<>();
            newFileList.add(new File("./app/libs/framework_header.jar"))
            newFileList.addAll(fileSet)
            options.bootstrapClasspath = files(
                    newFileList.toArray()
            )
        }
    }
}

Introduce the jar package in the form of compileOnly in build.gradle of the App directory.

compileOnly files('libs/framework_header.jar')

Step 2, making a system signature

The Android system will identify the signature type of the application and give the application the corresponding permission level according to the signature type. An important condition for upgrading an ordinary application to a system application is that the application needs to use the system signature. So in this step, we need to make a system signature first, so that we can debug the application during development.

1) The console enters the build directory of AOSP

cd build/target/product/security

2) Make a system signature

openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out [platform.pem]0 -nocrypt

openssl pkcs12 -export -in platform.x509.pem -inkey [platform.pem] -out [platform.pk12] -name [key的别名] -password pass:[key的密码]

keytool -importkeystore -deststorepass [key的密码] -destkeypass [key的密码] -destkeystore platform.jks -srckeystore platform.pk12 -srcstoretype PKCS12 -srcstorepass [key的密码] -alias [android]

After the production is completed, a platform.jks signature file will be downloaded and generated in the current directory, and it will be imported into android studio to sign the application.

3) Import into Android Studio

Place platform.jks in the App directory, and add the following code to build.gradle.

signingConfigs {
    sign {
        storeFile file('platform.jks')
        storePassword '123456'
        keyAlias 'android'
        keyPassword '123456'
    }
}

buildTypes {
    release {
        minifyEnabled false
        signingConfig signingConfigs.sign
    }
    debug {
        minifyEnabled false
        signingConfig signingConfigs.sign
    }
}

After introducing the system signature into android studio, the app project can directly call the system API in the Android emulator, and at the same time obtain higher-level permissions.

Note: The test key file based on the AOSP source code generally cannot be used in a real environment (such as a mobile phone), and the vehicle-mounted project is more complicated. Some projects will use stricter signature verification during the development stage, so the AOSP Signature files are also unusable. However, there are also projects that will change the signature in the final mass production stage, so the test key in AOSP can still be used before then.

Supplementary information about the signature document is as follows:

In the build/target/product/security/ directory of the Android source code, there are the following 5 pairs of common KEYs:

  • media.pk8 gives media.x509.pem

    A test key for the apk package included with the media/download system.

  • platform.pk8与platform.x509.pem

    Test key for the included apk package of the core platform.

  • shared.pk8与shared.x509.pem

    Test key for shared content in the Home/Contacts process.

  • testkey.pk8 and testkey.x509.pem

    Generic default key for apks that do not otherwise specify a key.

  • networkstack.pk8与networkstack.x509.pem

    Test key for apk packages contained in web systems.

Among them, the " .pk8" file is the private key, and the " .x509.pem" file is the public key. Note that test keys in this directory are for development only and should not be used to sign packages in publicly released images.

For more information about keys, you can read the official documentation: https://source.android.com/docs/core/ota/sign_builds?hl=zh-cn

And how do these keys correspond to the signed APK? Android.bpThere is a field in the file under the APK source code directory certificate, which is used to specify the KEY used for signing. If not specified, testkey is used by default. Corresponding to the system application, certificatethe following values ​​can be set.

certificate: "platform"
certificate: "shared"
certificate: "media"

For these configurations in , you need to add the following content Android.bpto the node in the AndroidManifest.xml file of the APK source code :<manifest>

android:sharedUserId="android.uid.system"
android:sharedUserId="android.uid.shared"
android:sharedUserId="android.media"

Practice System Application

Step 1, define requirements

In order to allow you to intuitively feel the difference between "system applications" and "common applications", we require "system applications" to complete the following functions:

  1. The application starts automatically after the system is turned on, that is, it starts automatically when the system is turned on
  2. Overlay a View on the screen after booting, and does not require authorization to "display on top of other applications"
  3. Automatically pull up after the application is killed, that is, the process is kept alive

These functions are requirements that are difficult to achieve in "normal applications". Let's demonstrate how to achieve them in "system applications".

Step 2, modify AndroidManifest.xml

The Android system itself has provided corresponding mechanisms to realize the two functions of booting up and process keeping alive. We only need to configure them in the manifest.xml.

  • persistent

Sets whether the application should remain in the resident state. The default value falseis set to trueenable resident mode, which is only applicable to system applications.

After the resident mode is turned on, the application will complete the startup before the Android system startup animation is played, and the application will be resident in the background, even if it is killed, it will be launched immediately.

<application
    android:persistent="true">

In addition, there are some properties that may be more commonly used in system applications that can be configured, and we will introduce them one by one.

  • android:sharedUserId

Set up data sharing between different users. By default, Android assigns each application its unique user ID. If two or more apps set this property to the same value, then those apps will all share the same ID, provided they have the exact same signature. Applications with the same user ID can access each other's data and, if desired, run in the same process.

This property was deprecated in API level 29. Note that since existing apps cannot remove this value, such apps should add android:sharedUserMaxSdkVersion="32" to avoid using the shared user ID on new user installs.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:sharedUserId="android.uid.system"
    android:sharedUserMaxSdkVersion="32">
  • directBootAware

Direct boot mode. The direct boot mode appeared after Android 7.0. When the device has been turned on normally but has not been unlocked, the device is said to be in DirectBoot mode. By default, apps don't start in DirectBoot mode, even system apps.

If the application needs to be started in DirectBoot mode, you can set the directBootAware attribute to true in the manifest.xml.

<application android:directBootAware="true" >

Some common application use cases that require running in Direct Boot mode include:

  1. Apps that have scheduled notifications, such as alarm clock apps;
  2. Apps that provide important user notifications, such as SMS apps;
  3. Applications that provide accessibility services, such as Talkback;
  4. Key system services, such as CarService, etc.

Note that for applications, storage space is divided into the following two types

  1. Credential encrypted storage, credential encrypted storage area. The place where data is stored by default, only available after the user unlocks the phone.
  2. Device encrypted storage, device encrypted storage area. The main correspondence is the storage space used by DirectBoot. This storage space can be used both in DirectBoot mode and after the user unlocks the phone.

0- When the Android system is powered on, it first enters a DirectBoot mode. If the application needs to access local data when running in DirectBoot mode, Context.createDeviceProtectedStorageContext()a special Contextinstance can be created by calling. All storage class API calls made through this instance can access the device's encrypted storage. As follows:

    Context directBootContext = appContext.createDeviceProtectedStorageContext();
    // Access appDataFilename that lives in device encrypted storage
    FileInputStream inStream = directBootContext.openFileInput(appDataFilename);
    // Use inStream to read content...

If you need to monitor when the screen is unlocked, you can register for the following broadcast

    <receiver
      android:directBootAware="true" >
      ...
      <intent-filter>
        <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
      </intent-filter>
    </receiver>
    

Some critical system applications or services need to be launched and start running before the Android screen is unlocked. In this case, it can be configured as a direct boot mode. At this time, you must read the official documents carefully to prevent unexpected bugs. Official documents: https://developer.android.google.cn/training/articles/direct-boot?hl=zh-cn

  • uses-library

Specifies the shared library with which the app must be associated. This tag tells the system to add the library's code to the package's class loader.

In the vehicle application project, it may be used to load some framework custom shared libraries.

<uses-library
  android:name="string"
  android:required=["true" | "false"] />

android:nameThe name of the library. This name is given by the documentation of the package you use. For example, " android.test.runner" is a package that contains Android test classes.

android:requiredIndicates whether the application requires the android:namespecified library:

  • "true": The app will not function properly without this library. The system does not allow app installation on devices without this library.
  • "false": The app can use this library (if present), but specifically runs without it (if necessary). The system allows the app to be installed even if this library does not exist. "false"You need to check for this library at runtime if you use

The complete androidmanifest.xml configuration is as follows:

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

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

    <application
        android:name=".SystemApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:persistent="true"
        android:supportsRtl="true"
        android:theme="@style/Theme.SystemApp">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".SystemService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.android.systemapp.action" />
            </intent-filter>
        </service>
    </application>

</manifest>

Step 3, write logic code

There is only one Service in this application, start the Service in Application.

class SystemApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        Log.e("System", "System APP started")
        val intent = Intent()
        intent.setPackage("com.android.systemapp")
        intent.setAction("com.android.systemapp.action")
        startService(intent)
    }
}

In Service, we draw a View through WindowManager. Before the system animation is played, the View cannot be drawn and displayed. In other words, when the View can be drawn, the system animation has finished playing and the SystemUI has been displayed.

// 创建用于 window 显示的context
val dm = getSystemService(DisplayManager::class.java)
val defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY)
val defaultDisplayContext = createDisplayContext(defaultDisplay)
val ctx = defaultDisplayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);

// 在屏幕上绘制一个像素的view,用于监控开机动画是否播放完毕
val mWindowManager = ctx.getSystemService(WindowManager::class.java)
val bounds = mWindowManager.getCurrentWindowMetrics().getBounds();

val windowSizeTest: View = object : View(ctx) {
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        Log.e(TAG, "system launch")
    }
}

The complete code of Service is as follows:

class SystemService : Service() {

    private val TAG = SystemService::class.java.simpleName;

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        // 创建用于 window 显示的context
        val dm = getSystemService(DisplayManager::class.java)
        val defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY)
        val defaultDisplayContext = createDisplayContext(defaultDisplay)
        val ctx = defaultDisplayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);

        // 在屏幕上绘制一个像素的view,用于监控开机动画是否播放完毕
        val mWindowManager = ctx.getSystemService(WindowManager::class.java)
        val bounds = mWindowManager.getCurrentWindowMetrics().getBounds();

        val windowSizeTest: View = object : View(ctx) {
            override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
                // 暂停5秒后,移除该View
                Thread{
                    sleep(5_000)
                    mWindowManager.removeView(this)
                }.start()
            }
        }
        val testParams: WindowManager.LayoutParams = WindowManager.LayoutParams(
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    and WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
        )
        testParams.width = bounds.width() / 2
        testParams.height = bounds.height()/2
        testParams.gravity = Gravity.CENTER
        testParams.title = TAG
        mWindowManager.addView(windowSizeTest, testParams)
    }
}

Step 4, Verify

In this step, we use the emulator in Android Studio to verify that the system application is running as we expect.

Push the written system application to System/app/, but because the System partition of the emulator is not open to write permission, we need to obtain the write permission of the System partition first.

1) Modify the write permission of the simulator

First enter the directory of the Android SDK emulator and execute the following command, and the message remount succeeded appears on the console, which means that the modification of the write permission is successful.

./emulator -list-avds
./emulator -writable-system -avd [10.1_WXGA_Tablet_API_31] -no-snapshot-load -qemu // 修改分区写入权限吧
adb root
adb remount
adb reboot // 重启模拟器
// 等待模拟器重启后
adb root
adb remount

2) Push the application apk to the system/app/xxx directory

Create a new one (with any name) under the system/app directory SystemApp, and then push the apk to this directory.

3) Restart the emulator to see the effect

After the emulator is restarted, the SystemApp process will automatically start and cover a black View on the screen. During the whole process, the SystemApp does not pop up a permission application window.

If we use adb kill [进程号]kill SystemApp, the system will immediately pull up the SystemApp process. The process keeping alive that is difficult to achieve in ordinary applications can be easily achieved in "system applications", and after entering the system settings to view SystemApp, it is found that SystemApp cannot actually be uninstalled.

Summarize

In this video, we introduce the development method of Android system application. In the final analysis, the development of vehicle Android application is the development of system application. Understanding the development method of system application is the most basic technical requirement for us to get started with the development of vehicle Android application.

Well, that's all for this video. The text content of this video is published on my personal WeChat public account - "Car Android" and my personal blog. The PPT files and source code used in the video are published on my Github[https://github.com/linxu-link /CarAndroidCourse], you can find the corresponding address in the introduction of this video.

Thank you for watching, see you in the next video, bye.

References

https://developer.android.google.cn/guide/topics/manifest/application-element?hl=zh-cn#persistent

https://developer.android.google.cn/guide/topics/manifest/manifest-element?hl=zh-cn#uid

Guess you like

Origin blog.csdn.net/linkwj/article/details/129499289