3 分钟阅读 次阅读

本文将通过修改 Android 系统源代码和配置文件,实现应用在设备开机后以前台形式自启的功能(即用户可直接看到应用界面)。具体实现涉及多个文件的修改和联动,以下内容包括详细代码块、代码解释,以及最终实现该功能的总结。

关联文章《使用fake launcher设置应用前台开机自启后,如何返回桌面》。

1 开机后台自启的基础实现

如果仅需要开机后台自启(用户看不到界面),可以通过编辑 init.rc 脚本实现:

修改文件:device/rockchip/rk356x/rk3568_HW/init.rk356x.rc

on property:sys.boot_completed=1 节点下添加启动命令:

on property:sys.boot_completed=1
    在此处添加

1.1 解释:

  • on property:sys.boot_completed=1:此处监听系统属性 sys.boot_completed,该属性在系统启动完成后被设置为 1
  • 在该节点下添加脚本、服务或应用启动命令,即可在系统完成启动后触发对应服务或应用的后台运行。
  • 限制:从 Android 9 开始,Google 引入了更严格的安全限制。应用如果未通过用户交互触发启动,其 Activity 将无法显示在前台。此时,应用即使后台自启,也可能因为未在前台运行而被系统杀掉。

2 前台自启的解决方案:使用 Fake Launcher

要实现前台自启,需要修改 Android 系统源码,通过一个伪造的 Launcher (Fake Launcher) 来绕过系统限制。以下是修改的具体步骤和代码解释。

特点:

  1. 替换机制:
    • homeIntent 被替换为应用的 launchIntent,而不是系统默认的桌面 launcher
  2. 返回桌面的限制:
    • 在应用启动后,用户尝试返回到系统桌面将无法实现。因为此时的“桌面”已经由该应用所取代。
  3. 退出应用的表现:
    • 当用户试图退出这个被替换的“桌面”时,Android 系统会在“桌面”退出后自动重新启动这个“桌面”。这导致用户无法真正退出该应用的界面。
  4. 如何设置以退回真正的桌面:
    • 参考另一篇文章《使用fake launcher设置应用前台开机自启后,如何返回桌面》。
    • 做法就是修改fake launcher的设置代码,在完成fake launcher启动后,重新设置 homeIntent 为真正的系统桌面 launcher

2.1 修改 ActivityRecord.java

修改文件:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

新增代码:

diff --git a/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java b/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
index 08ea33af80..e19cecda23 100755
--- a/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5409,6 +5409,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
         if (task != null) {
             task.setHasBeenVisible(true);
         }
+        int startFakeLauncher = Settings.System.getInt(mAtmService.mContext.getContentResolver(), "start_fake_launcher", 0);
+        Slog.i(TAG, "nodka onWindowsDrawn shortComponentName = " + shortComponentName + " , start_fake_launcher = " + startFakeLauncher);
+        if ((isHomeIntent(intent) && shortComponentName != null && !shortComponentName.contains("FallbackHome")) ||
+            ((1==startFakeLauncher) && shortComponentName != null && !shortComponentName.contains("FallbackHome"))) {
+            SystemProperties.set("service.bootanim.exit", "1");
+            try {
+                IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
+                if (surfaceFlinger != null) {
+                    Slog.i(TAG_WM, "nodka ******* TELLING SURFACE FLINGER WE ARE BOOTED!");
+                    Parcel data = Parcel.obtain();
+                    data.writeInterfaceToken("android.ui.ISurfaceComposer");
+                    surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
+                        data, null, 0);
+                    data.recycle();
+                }
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "nodka Boot completed: SurfaceFlinger is dead!");
+            }
+            Settings.System.putInt(mAtmService.mContext.getContentResolver(), "start_fake_launcher", 0);
+        }
     }

代码解释:

  1. 读取 start_fake_launcher 标志位:

    int startFakeLauncher = Settings.System.getInt(mAtmService.mContext.getContentResolver(), "start_fake_launcher", 0);
    
    • 从系统设置中读取 start_fake_launcher 标志位,判断是否需要启动伪造的 Launcher(Fake Launcher)。
    • 通过 RootWindowContainer 中的逻辑(稍后解释),此标志位由系统在启动过程中设置为 1,表示应用需要以前台形式启动。
  2. 判断是否为 HomeIntent 或伪造的 Launcher:

    if ((isHomeIntent(intent) && shortComponentName != null && !shortComponentName.contains("FallbackHome")) ||
        ((1==startFakeLauncher) && shortComponentName != null && !shortComponentName.contains("FallbackHome"))) {
    
    • 检查当前 Intent 是否为 Home 类型(即 Launcher)。
    • 同时判断短组件名称(shortComponentName)是否为正常的 Launcher(排除 FallbackHome,即系统备用的 Launcher)。
    • 如果 start_fake_launcher1,也会触发启动逻辑。
  3. 关闭开机动画:

    SystemProperties.set("service.bootanim.exit", "1");
    
    • 通过设置系统属性 service.bootanim.exit=1,通知系统退出开机动画。
    • 这一步骤确保应用界面能够尽快显示,而不会被开机动画遮挡。
  4. 通知 SurfaceFlinger 开机完成:

    IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
    if (surfaceFlinger != null) {
        Parcel data = Parcel.obtain();
        data.writeInterfaceToken("android.ui.ISurfaceComposer");
        surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
        data.recycle();
    }
    
    • 使用 SurfaceFlinger 通知系统图形服务,设备已经完成启动。
    • 这一操作能够让开机流程顺利结束,并切换到 Fake Launcher 的界面。
  5. 重置标志位:

    Settings.System.putInt(mAtmService.mContext.getContentResolver(), "start_fake_launcher", 0);
    
    • start_fake_launcher 标志位重置为 0,防止重复触发伪造的 Launcher 启动。

2.2 修改 RootWindowContainer.java

修改文件:frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

新增代码:

diff --git a/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java b/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
index 197e2188c3..ede69e379e 100644
--- a/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1519,6 +1520,15 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
         ActivityInfo aInfo = null;
         if (taskDisplayArea == getDefaultTaskDisplayArea()) {
             homeIntent = mService.getHomeIntent();
+            String pkgName = SystemProperties.get("persist.fake.launcher.name", "");
+            if (!"".equals(pkgName)) {
+                Slog.i(TAG, "nodka start persist.fake.launcher.name " + pkgName);
+                Intent launchIntent = mService.mContext.getPackageManager().getLaunchIntentForPackage(pkgName);
+                if (launchIntent != null) {
+                    Settings.System.putInt(mService.mContext.getContentResolver(), "start_fake_launcher", 1);
+                    homeIntent = launchIntent;
+                }
+            }
             aInfo = resolveHomeActivity(userId, homeIntent);

代码解释:

  1. 读取 Fake Launcher 的包名:

    String pkgName = SystemProperties.get("persist.fake.launcher.name", "");
    
    • 从系统属性 persist.fake.launcher.name 中读取需要启动的应用包名。
    • 该属性在设备配置文件(device.mk)中配置,例如:persist.fake.launcher.name=com.example.app
  2. 获取目标应用的启动 Intent:

    Intent launchIntent = mService.mContext.getPackageManager().getLaunchIntentForPackage(pkgName);
    
    • 通过包管理器查询目标应用的启动 Intent。
    • 如果目标应用存在,则返回其主入口 Activity 的 Intent。
  3. 设置 start_fake_launcher 标志位:

    Settings.System.putInt(mService.mContext.getContentResolver(), "start_fake_launcher", 1);
    
    • 设置标志位 start_fake_launcher=1,通知后续流程(如 ActivityRecord)需要启动伪造的 Launcher。
  4. 替换默认 HomeIntent:

    homeIntent = launchIntent;
    
    • 将系统默认的 HomeIntent 替换为目标应用的启动 Intent。
    • 这一步确保目标应用能够在开机后以 Launcher 的形式启动。

2.3 修改 WindowManagerService.java

修改文件:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

新增代码:

diff --git a/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java b/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
index 6ee6027326..c68c91aedc 100644
--- a/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3450,6 +3451,10 @@ public class WindowManagerService extends IWindowManager.Stub
 
         //box:skip FallbackHome to fix 2-3s black screen before launcher.
         if (!"box".equals(SystemProperties.get("ro.target.product"))) {
+            KeyguardManager mKeyguardManager = (KeyguardManager) mAtmService.mContext.getSystemService(Context.KEYGUARD_SERVICE);
+            boolean mFlag = mKeyguardManager.inKeyguardRestrictedInputMode();
+            Slog.e(TAG, "nodka performEnableScreen : " + mFlag);
+            if(mFlag) {
             if (!mBootAnimationStopped) {
                 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
                 // stop boot animation
@@ -3477,6 +3482,7 @@ public class WindowManagerService extends IWindowManager.Stub
             } catch (RemoteException ex) {
                 ProtoLog.e(WM_ERROR, "Boot completed: SurfaceFlinger is dead!");
             }
+            }
         }

代码解释:

  1. 判断设备是否处于锁屏状态:

    KeyguardManager mKeyguardManager = (KeyguardManager) mAtmService.mContext.getSystemService(Context.KEYGUARD_SERVICE);
    boolean mFlag = mKeyguardManager.inKeyguardRestrictedInputMode();
    
    • 使用 KeyguardManager 检查设备是否处于锁屏状态。
    • 如果设备处于锁屏状态,则可能不触发前台显示。

2.4 修改设备配置文件 device.mk

修改文件:device/rockchip/rk356x/device.mk

新增配置:

diff --git a/device/rockchip/rk356x/device.mk b/device/rockchip/rk356x/device.mk
index 6d31c46d87..4f7480dcab 100644
--- a/device/rockchip/rk356x/device.mk
+++ b/device/rockchip/rk356x/device.mk
@@ -77,6 +77,7 @@ PRODUCT_PROPERTY_OVERRIDES += \
                 ro.opengles.version=196610 \
                 wifi.interface=wlan0 \
                 ro.audio.monitorOrientation=true \
+                persist.fake.launcher.name = com.kgbs.gatecheck \
                 debug.nfc.fw_download=false \
                 debug.nfc.se=false \
                 vendor.hwc.compose_policy=0 \

代码解释:

  • persist.fake.launcher.name:定义需要启动的应用包名,例如 com.kgbs.gatecheck
  • 核心功能
    • 通过设备配置文件指定启动应用,灵活性更高。

总体总结

通过以上步骤,实现了设备开机后指定应用以前台形式自启的功能。流程如下:

  1. Fake Launcher 替换
    • 修改 RootWindowContainer,替换默认 Launcher 为指定应用。
    • 修改 ActivityRecord,控制启动行为,确保前台显示。
  2. 优化流程:通过 WindowManagerService 停止开机动画,避免黑屏或延迟。
  3. 配置灵活性:通过 device.mk 配置文件定义启动的应用包名,方便修改。

该方案通过结合系统属性和 Launcher 替换机制,绕过了 Android 9+ 的安全限制,实现了应用开机自动前台启动的需求。

留下评论