需求:将第三方应用预置到系统,用户可以卸载,恢复出厂设置后,应用可以恢复
实现方案
将第三方应用预置到一个目录,比如/vendor/preinstall、系统第一次起来时,将这个目录下的所有apk文件拷贝到data/app下或者通过pm脚本安装目录下的所有apk。
将第三方应用预置到系统目录
有两种方式实现
1.通过编写Android.mk 将apk 文件拷贝到指定系统编译目录
2.直接将目录下的所有apk 文件拷贝到系统编译目录
方式1:属于常规操作,缺点是每新增一个第三方apk文件,都需要重新修改文件
LOCAL_PATH := $(call my-dir)
#########################
include $(CLEAR_VARS)
LOCAL_MODULE := preinstallApk
LOCAL_MODULE_TAGS := optional
LOCAL_DEX_PREOPT :=false
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_MODULE_PATH := $(TARGET_OUT)/vendor/preinstall
LOCAL_MODULE_CLASS := APPS
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
include $(BUILD_PREBUILT)
方式2:这种方式简单快捷,后续新增apk 也很方便
新增一个目录preintall/prethirdapk、并将apk文件全部放置其中、然后加入编译选项
PRODUCT_COPY_FILES += \
$(call find-copy-subdir-files,*,device/amlogic/$(PRODUCT_DIR)/preinstall/prethirdapk/,/$(TARGET_COPY_OUT_VENDOR)/preinstall) \
注意:直接拷贝apk文件,系统编译时会报错
Prebuilt apk found in PRODUCT_COPY_FILES: preinstallApk, use BUILD_PREBUILT instead!
vim build/make/core/Makefile +28
@@ -28,7 +28,6 @@ product_copy_files_ignored :=
$(foreach cf,$(unique_product_copy_files_pairs), \
$(eval _src := $(call word-colon,1,$(cf))) \
$(eval _dest := $(call word-colon,2,$(cf))) \
- $(call check-product-copy-files,$(cf),$(_dest)) \
屏蔽check-product-copy-files即可
第三方应用预装到系统
1.系统第一次起来时,将这个目录下的所有apk文件拷贝到data/app下,系统自动解析
2.系统第一次起来时,通过pm脚本安装目录下的所有apk
两种方式大同小异,略有点不同
1.将目录下的apk 直接拷贝到/data/apk,Android 9.0会报错
"Application package " + pkg.packageName
+ " not found; ignoring."
修改如下(解析data app时,去掉SCAN_REQUIRE_KNOWN 标记):
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2853,7 +2853,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!mOnlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
- scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
+ scanDirTracedLI(sAppInstallDir, 0, scanFlags, 0);
2.上面的修改略微觉得有点欠妥,修改的系统代码,破坏了原有逻辑,推荐第二种办法:
增加pm preinstall 命令
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -26,6 +26,7 @@ import android.accounts.IAccountManager;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.Application;
+import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -156,6 +157,8 @@ class PackageManagerShellCommand extends ShellCommand {
return runQueryIntentReceivers();
case "install":
return runInstall();
+ case "preinstall":
+ return preInstall();
case "install-abandon":
case "install-destroy":
return runInstallAbandon();
@@ -898,11 +901,60 @@ class PackageManagerShellCommand extends ShellCommand {
return 0;
}
+ private int preInstall() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ String path = getNextArg();
+ pw.println("preInstall path: " + path);
+ if (path == null) {
+ pw.println("Error: no package specified");
+ return 1;
+ }
+
+ File[] files = new File(path).listFiles();
+ for(File apkFilePath : files) {
+ final String inPath = apkFilePath.getPath();
+ SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+ InstallParams params = new InstallParams();
+ params.sessionParams = sessionParams;
+ pw.println("preInstall pkg: " + inPath);
+ setParamsSize(params, inPath);
+ final int sessionId = doCreateSession(params.sessionParams,
+ params.installerPackageName, params.userId);
+ boolean abandonSession = true;
+ try {
+ if (inPath == null && params.sessionParams.sizeBytes == -1) {
+ pw.println("Error: must either specify a package size or an APK file : "+inPath);
+ continue;
+ }
+ if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "",
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ pw.println("doWriteSplit Error :"+inPath);
+ continue;
+ }
+ if (doCommitSession(sessionId, false /*logSuccess*/)
+ != PackageInstaller.STATUS_SUCCESS) {
+ pw.println("doCommitSession Error :"+inPath);
+ continue;
+ }
+ abandonSession = false;
+ pw.println("preInstall : " + inPath+ " Success!");
+ } finally {
+ if (abandonSession) {
+ try {
+ doAbandonSession(sessionId, false /*logSuccess*/);
+ } catch (Exception ignore) {
+ }
+ }
+ }
+ }
+ pw.println("preInstall path: " + path + " ok");
+ return 0;
+ }
增加启动脚本,执行pm preinstall
on property:sys.boot_completed=1
start preinstall
service preinstall /vendor/bin/preinstall.sh
class main
user root
group root system
disabled
oneshot
/vendor/bin/
#!/vendor/bin/sh
if [ ! -e /data/funsys.notfirstrun ]; then
echo "do preinstall job"
pm preinstall /vendor/preinstall
#通过写入文件判断是否第一次开机,恢复出厂会把该文件删掉、用于恢复安装应用
#也可以通过它判断脚本有没有执行
touch /data/funsys.notfirstrun
echo "preinstall ok"
fi
将 添加到编译选项 :
/
PRODUCT_COPY_FILES += device/amlogic/$(PRODUCT_DIR)/preinstall/script/preinstall.sh:$(TARGET_COPY_OUT_VENDOR)/bin/preinstall.sh
编译到系统中后,将镜像重新刷到机器上,启动系统。
系统起来后,通过dmsg 查看系统消息,查看脚本是否有执行:
发现有selinux相关东西,导致脚本没有执行:
[ 197.528960] type=1400 audit(1639017983.176:728): avc: denied { write } for pid=5670 comm="" name="/" dev="mmcblk0p21" ino=2 scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=1
[ 197.529498] type=1400 audit(1639017983.176:728): avc: denied { write } for pid=5670 comm="" name="/" dev="mmcblk0p21" ino=2 scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=1
[ 197.529543] type=1400 audit(1639017983.176:729): avc: denied { add_name } for pid=5670 comm="" name="" scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=1
[ 197.529678] type=1400 audit(1639017983.176:729): avc: denied { add_name } for pid=5670 comm="" name="" scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=1
[ 197.529716] type=1400 audit(1639017983.176:730): avc: denied { create } for pid=5670 comm="" name="" scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
[ 197.529843] type=1400 audit(1639017983.176:730): avc: denied { create } for pid=5670 comm="" name="" scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
[ 197.529947] type=1400 audit(1639017983.180:731): avc: denied { write open } for pid=5670 comm="" path="/data/" dev="mmcblk0p21" ino=14 scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
[ 197.594638] audit: audit_lost=563 audit_rate_limit=5 audit_backlog_limit=64
[ 197.594644] audit: rate limit exceeded
[ 203.093582] type=1400 audit(1639017985.332:753): avc: denied { read } for pid=5691 comm="touch" path="/data/" dev="mmcblk0p21" ino=15 scontext=u:r:install_recovery:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
将以上放到 文件, 在Linux 终端执行
audit2allow -i
#============= install_recovery ==============
allow install_recovery system_data_file:dir { write add_name };
allow install_recovery system_data_file:file { write read create open };
selinux 相关就不再此展开了.后续再慢慢介绍。