[RK3568][Android12.0]— The system comes with preset third-party APK method

Platform: RK3568
OS: Android 12.0
Kernel: 4.19

Rockchip provides a mechanism to preset third-party APKs by default. The method is very simple:
1. Create the preinstall directory in device/rockchip/rk3568 (if you want it to be uninstallable, create the preinstall_del directory)
2. Put the APK you want to pre-install into this directory

preinstall cannot be uninstalled

preinstall_del can be uninstalled and restored to factory settings.

preinstall_del_forever can be uninstalled and cannot be restored by factory reset.

Let’s take a look at the implementation principle process:

Device/rockchip/common/device.mk contains:

# Prebuild apps
$(call inherit-product, device/rockchip/common/modules/preinstall.mk)

device\rockchip\common\modules\preinstall.mk

# Include this makefile to support prebuild apps
ifneq ($(strip $(TARGET_PRODUCT)), )
    $(shell python device/rockchip/common/auto_generator.py $(TARGET_DEVICE_DIR) preinstall bundled_persist-app $(TARGET_ARCH))
    $(shell python device/rockchip/common/auto_generator.py $(TARGET_DEVICE_DIR) preinstall_del bundled_uninstall_back-app $(TARGET_ARCH))
    $(shell python device/rockchip/common/auto_generator.py $(TARGET_DEVICE_DIR) preinstall_del_forever bundled_uninstall_gone-app $(TARGET_ARCH))
    -include $(TARGET_DEVICE_DIR)/preinstall/preinstall.mk
    -include $(TARGET_DEVICE_DIR)/preinstall_del/preinstall.mk
    -include $(TARGET_DEVICE_DIR)/preinstall_del_forever/preinstall.mk
endif

auto_generator.py is a python script used to generate Android.mk and preinstall.mk files.

def main(argv):
preinstall_dir = os.path.join(argv[1] + ‘/’ + argv[2])
if os.path.exists(preinstall_dir):
#Use to define modules for install
makefile_path = preinstall_dir + ‘/Android.mk’
#Use to include modules
include_path = preinstall_dir + ‘/preinstall.mk’

if os.path.exists(makefile_path):
os.remove(makefile_path)
if os.path.exists(include_path):
os.remove(include_path)

makefile = file(makefile_path, ‘w’)
includefile = file(include_path, ‘w’)

makefile.write(“LOCAL_PATH := $(my-dir)\
\
“)
for root, dirs, files in os.walk(preinstall_dir):
for file_name in files:
p = re.compile(r’\S*(?=.apk\b)’)
found = p.search(file_name)
if found:
makefile.write(templet %(found.group(), argv[2]))
includefile.write(‘PRODUCT_PACKAGES + = %s\
‘ %found.group())
makefile.close()
includefile.close()

Android.mk is used to formulate compilation rules. For example, if I put an AVSourceTester.apk in the preinstall directory, the content of the generated file is

LOCAL_PATH := $(my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := AVSourceTester
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_PATH := $(TARGET_OUT)/preinstall
LOCAL_SRC_FILES := $(LOCAL_MODULE)$(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_DEX_PREOPT := false
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
include $(BUILD_PREBUILT)

The content of preinstall.mk is as follows:
PRODUCT_PACKAGES + = AVSourceTester

After compiling the system, the generation path is
out/target/product/rk3568/system/preinstall/AVSourceTester/AVSourceTester.apk

After the system is powered on, it will be called

frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java

 preinstallThirdPartyAPK(packageParser,executorService,scanFlags);
private void preinstallThirdPartyAPK(PackageParser2 packageParser, ExecutorService executorService,int scanFlags){
        preinstallPrebundledpersist(packageParser,executorService,scanFlags);
        preinstallPrebundledUninstallBack(packageParser,executorService,scanFlags);
        preinstallPrebundledUninstallGone(packageParser,executorService,scanFlags);
    }
private void preinstallPrebundledpersist(PackageParser2 packageParser, ExecutorService executorService,int scanFlags){
        scanDirTracedLI(new File(BUNDLED_PERSIST_DIR),
                    mDefParseFlags | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR
                    | ParsingPackageUtils.PARSE_IS_PREINSTALL,
                    scanFlags | SCAN_AS_PREINSTALL
                    | SCAN_AS_SYSTEM,
                    0,packageParser, executorService);
    }

    private void preinstallPrebundledUninstallBack(PackageParser2 packageParser, ExecutorService executorService,int scanFlags){
        scanDirTracedLI(Environment.getPrebundledUninstallBackDirectory(),
                    mDefParseFlags | ParsingPackageUtils.PARSE_IS_PREBUNDLED_DIR,
                    scanFlags | SCAN_AS_PREBUNDLED_DIR,
                    0,packageParser, executorService);
    }

    private void preinstallPrebundledUninstallGone(PackageParser2 packageParser, ExecutorService executorService,int scanFlags){
        scanDirTracedLI(Environment.getPrebundledUninstallGoneDirectory(),
                    mDefParseFlags | ParsingPackageUtils.PARSE_IS_PREBUNDLED_DIR,
                    scanFlags | SCAN_AS_PREBUNDLED_DIR,
                    0,packageParser, executorService);
    }
 private static final String BUNDLED_PERSIST_DIR = "/odm/bundled_persist-app";

    private static final String BUNDLED_UNINSTALL_GONE_DIR = "/odm/bundled_uninstall_gone-app";
 private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
            long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
        try {
            scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
            PackageParser2 packageParser, ExecutorService executorService) {
        final File[] files = scanDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            Log.d(TAG, "No files in app dir " + scanDir);
            return;
        }

        if (DEBUG_PACKAGE_SCANNING) {
            Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
                     + " flags=0x" + Integer.toHexString(parseFlags));
        }

        ArrayList<String> list = new ArrayList<String>();
        boolean isPrebundled = (parseFlags & amp; ParsingPackageUtils.PARSE_IS_PREBUNDLED_DIR) != 0;
        if (isPrebundled) {
            synchronized (mPackages) {
                mSettings.readPrebundledPackagesLPr();
            }
        }

        if (scanDir.getAbsolutePath().contains(BUNDLED_UNINSTALL_GONE_DIR)) {
            if (!readDeleteFile(list)) {
                Log.e(TAG, "read data failed");
                return;
            }
        }

        ParallelPackageParser parallelPackageParser =
                new ParallelPackageParser(packageParser, executorService);

        // Submit files for parsing in parallel
        int fileCount = 0;
        for (File file : files) {
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                     & amp; & amp; !PackageInstallerService.isStageName(file.getName());
            if (!isPackage) {
                // Ignore entries which are not packages
                continue;
            }
            if (file.getAbsolutePath().contains(BUNDLED_UNINSTALL_GONE_DIR)) {
                if (list != null & amp; & amp; list.size() > 0) {
                    final boolean isdeleteApk = isDeleteApk(file,parseFlags,list);
                    if (isdeleteApk) {
                        // Ignore deleted bundled apps
                        continue;
                    }
               }
            }
            parallelPackageParser.submit(file, parseFlags);
            fileCount + + ;
        }

        // Process results one by one
        for (; fileCount > 0; fileCount--) {
            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
            Throwable throwable = parseResult.throwable;
            int errorCode = PackageManager.INSTALL_SUCCEEDED;
            String errorMsg = null;

            if (throwable == null) {
                // TODO(toddke): move lower in the scan chain
                // Static shared libraries have synthetic package names
                if (parseResult.parsedPackage.isStaticSharedLibrary()) {
                    renameStaticSharedLibraryPackage(parseResult.parsedPackage);
                }
                try {
                    addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                            currentTime, null);
                    if (isPrebundled) {
                        final PackageParser.Package pkg;
                        try {
                            pkg = new PackageParser().parsePackage(parseResult.scanFile, parseFlags);
                        } catch (PackageParserException e) {
                            throw PackageManagerException.from(e);
                        }
                        synchronized (mPackages) {
                            mSettings.markPrebundledPackageInstalledLPr(pkg.packageName);
                        }
                    }
                } catch (PackageManagerException e) {
                    errorCode = e.error;
                    errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
                    Slog.w(TAG, errorMsg);
                }
            } else if (throwable instanceof PackageParserException) {
                PackageParserException e = (PackageParserException)
                        throwable;
                errorCode = e.error;
                errorMsg = "Failed to parse " + parseResult.scanFile + ": " + e.getMessage();
                Slog.w(TAG, errorMsg);
            } else {
                throw new IllegalStateException("Unexpected exception occurred while parsing "
                         + parseResult.scanFile, throwable);
            }

            if ((scanFlags & amp; SCAN_AS_APK_IN_APEX) != 0 & amp; & amp; errorCode != INSTALL_SUCCEEDED) {
                mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg);
            }

            // Delete invalid userdata apps
            if ((scanFlags & amp; SCAN_AS_SYSTEM) == 0
                     & amp; & amp; errorCode != PackageManager.INSTALL_SUCCEEDED) {
                logCriticalInfo(Log.WARN,
                        "Deleting invalid package at " + parseResult.scanFile);
                removeCodePathLI(parseResult.scanFile);
            }
        }

        if (isPrebundled) {
            synchronized (mPackages) {
                mSettings.writePrebundledPackagesLPr();
            }
        }
    }

The key function is copyPackagesToAppInstallDir(), which copies the installation files in the preinstall directory to the installation directory.
The installation is successful.

The difference between installing preinstall and preinstall_del is that the latter will delete the apk in the system directory after installation, so if you restore factory settings or uninstall, it cannot be restored.

The deletion function is deletePreinstallDir(), which is implemented through the ctl command in init.

private void deletePreinstallDir(File dir) {
String[] files = dir.list();
if (files != null) {
Slog.d(TAG, “Ready to cleanup preinstall”);
SystemProperties.set(“ctl.start”, “preinst_clr”);
}
}

However, the preinst_clr service is not found in the source code. You can add it yourself in init.rc.
The reference is Nu3001/device_rockchip_rksdk

service preinst_clr /system/bin/preinstall_cleanup.sh
disabled
oneshot

The preinstall_cleanup.sh file exists by default, and its essence is to delete the apk directly.

#!/system/bin/sh
log -t PackageManager “Start to clean up /system/preinstall_del/”
mount -o rw,remount -t ext4 /system
rm system/preinstall_del/*.*
mount -o ro,remount -t ext4 /system