Android 10&15 Framework 允许设置系统时间早于编译时间

一、背景说明

Android系统默认不允许系统时间设置早于系统固件编译时间,毕竟时间是始终向前的,通常不会设置某个过去的无效时间。但在项目测试环节提出了此问题:
当设置系统时间为早于编译时间的时刻时,设备重启时间复位为固件编译时间。

二、根因分析

先以Android10展开分析,从现象分析可知:
1.设置系统时间后是重启设备才复位,因此是开机过程某处重置了系统时间
2.时间被复位为固件编译时间

分析开机log可看到相关log:

AlarmManager Current time only xxx , advancing to build time xxx

从而可知开机时时间被复位成固件编译时间,响应log代码位于:
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java

    @Override
    public void onStart() {
        mInjector.init();
        synchronized (mLock) {
           ...
            // Ensure that we're booting with a halfway sensible current time.  Use the
            // most recent of Build.TIME, the root file system's timestamp, and the
            // value of the ro.build.date.utc system property (which is in seconds).
            final long systemBuildTime =  Long.max(
                    1000L * SystemProperties.getLong("ro.build.date.utc", -1L),
                    Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
            if (mInjector.getCurrentTimeMillis() < systemBuildTime) {
                Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis()
                        + ", advancing to build time " + systemBuildTime);
                mInjector.setKernelTime(systemBuildTime);
            }

此外我们知道固件编译保存在ro.build.date.utc属性中,全局搜索代码,也较容易定位到上述代码。
由此可知,开机AlarmManager服务启动时,从ro.builddate.utc属性中固件编译时间,并于当前系统时间比较,若当前系统时间早于编译时间,则以固件编译时间为系统时间。

三、解决方案

Android10
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java

    @Override
    public void onStart() {
        mInjector.init();
        synchronized (mLock) {
           ...
            // Ensure that we're booting with a halfway sensible current time.  Use the
            // most recent of Build.TIME, the root file system's timestamp, and the
            // value of the ro.build.date.utc system property (which is in seconds).
            //Patch Start
            /*
            final long systemBuildTime =  Long.max(
                    1000L * SystemProperties.getLong("ro.build.date.utc", -1L),
                    Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
            if (mInjector.getCurrentTimeMillis() < systemBuildTime) {
                Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis()
                        + ", advancing to build time " + systemBuildTime);
                mInjector.setKernelTime(systemBuildTime);
            }
            */
            //Patch End

Android15 流程有些变化,在initializeTimeIfRequired()初始化合适的系统时间
frameworks/base/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java

    @Override
    public void onStart() {
        mInjector.init();
        mHandler = new AlarmHandler();
        ...
        synchronized (mLock) {
            ...

            // Ensure that we're booting with a halfway sensible current time.
            //Patch Start
            //mInjector.initializeTimeIfRequired();
            //Patch End
            ...
            }
            ...
    }

frameworks/base/services/core/java/com/android/server/SystemClockTime.java

    /**
     * Sets the system clock time to a reasonable lower bound. Used during boot-up to ensure the
     * device has a time that is better than a default like 1970-01-01.
     */
    public static void initializeIfRequired() {
        // Use the most recent of Build.TIME, the root file system's timestamp, and the
        // value of the ro.build.date.utc system property (which is in seconds).
        final long systemBuildTime = Long.max(
                1000L * SystemProperties.getLong("ro.build.date.utc", -1L),
                Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
        long currentTimeMillis = getCurrentTimeMillis();
        if (currentTimeMillis < systemBuildTime) {
            String logMsg = "Current time only " + currentTimeMillis
                    + ", advancing to build time " + systemBuildTime;
            Slog.i(TAG, logMsg);
            
            setTimeAndConfidence(systemBuildTime, TIME_CONFIDENCE_LOW, logMsg);
        }
    }
&lt;think&gt; 我们被要求实现Android 11系统开机直接进入applist界面的方法。这里需要理解&ldquo;applist界面&rdquo;通常指的是显示所有已安装应用程序的列表界面,也就是类似Launcher中的应用程序列表。 根据引用[2]我们知道,Launcher是系统启动后显示桌面和应用程序图标的应用程序。因此,如果我们想要开机后直接进入applist界面(即应用程序列表),我们需要做的是替换默认的Launcher,或者修改Launcher的行为,使其在启动时直接进入应用程序列表而不是主屏幕。 然而,在Android系统中,开机后启动的Launcher是由系统决定的,通常是由用户选择的默认Launcher。因此,要实现这个功能,我们可以考虑以下两种方法: 方法一:开发一个自定义的Launcher,该Launcher在启动时直接显示应用程序列表(而不是主屏幕)。 方法二:修改系统默认的Launcher(如果系统允许,比如定制ROM),使其启动时直接进入应用程序列表。 由于问题中提到&ldquo;设备开机直接进入applist界面&rdquo;,我们假设是在系统级别进行修改(即系统级指令,用户不可见),因此我们将重点放在方法二上,也就是修改系统Launcher的行为。 步骤: 1. 获取系统Launcher的源代码(例如AOSP中的Launcher3)。 2. 修改Launcher的启动行为,使其在启动时自动跳转到应用程序列表界面(即All Apps界面)。 3. 重新编译Launcher并集成到系统中。 具体操作: 在Launcher3的源代码中,我们可以找到Launcher的入口Activity(通常是`Launcher.java`)。我们需要在启动时触发显示All Apps界面。 在`Launcher`类中,有一个方法`onStart()`或`onResume()`,我们可以在其中添加代码,在Launcher启动后显示All Apps界面。 但是注意:我们可能只需要在第一次启动(即开机后)显示,还是每次启动都显示?根据要求是开机直接进入,因此我们每次启动Launcher时都进入applist界面。 修改示例(以AOSP中的Launcher3为例): 在`Launcher.java`中,我们可以重写`onStart()`方法,在适当的时候调用显示All Apps界面的方法。 通常,显示All Apps界面的逻辑是通过`AllAppsTransitionController`来控制的,我们可以触发打开All Apps事件。 但是,为了避免界面还未准备好,我们可以在`onResume`中通过Handler稍作延迟来执行。 示例代码: ```java @Override protected void onResume() { super.onResume(); // 延迟一段时间,确保界面初始化完成 new Handler().postDelayed(new Runnable() { @Override public void run() { // 显示All Apps界面 getAppsView().getVisibilityAnim().setFuture(AnimatorListeners.forSuccessCallback(() -&gt; { // 确保AllAppsController处于显示状态 showAppsView(true, true); })); } }, 500); // 延迟500毫秒 } ``` 然而,上述方法可能会干扰正常的Launcher使用,比如用户按Home键返回Launcher时也会进入All Apps。因此,我们可以考虑增加一个条件:只有当系统刚刚启动完成(即第一次启动Launcher)时才进入All Apps界面。但是,Android系统并不直接提供&ldquo;开机第一次启动&rdquo;的标志。 替代方案:我们可以通过检测系统启动完成的广播,然后设置一个标志,在Launcher启动时使用。但这样需要Launcher有持久化存储的能力(如SharedPreferences)来记录是否已经显示过主屏幕。但根据需求,我们要求每次开机都直接进入applist,所以我们可以忽略后续的Home键返回,或者要求每次都进入applist。 如果需求是每次启动Launcher(包括按Home键)都进入applist,那么我们就可以像上面那样做。 但是,如果需求只是开机后第一次进入Launcher时显示applist,而之后按Home键还是回到主屏幕,那么我们可以利用系统启动完成的广播。 步骤: 1. 在Launcher中注册一个广播接收器,监听`ACTION_BOOT_COMPLETED`。 2. 当收到广播时,设置一个标志(比如在SharedPreferences中记录这次开机)。 3. 在Launcher的`onResume`中检查这个标志,如果标志存在,则显示applist界面,并清除标志(或记录这次已经显示,避免重复)。 但是注意:Launcher在系统启动后就会启动,而`ACTION_BOOT_COMPLETED`广播是在系统启动完成后发送的,所以Launcher可能早于广播接收。因此,我们需要在Launcher启动时等待这个广播,或者使用其他方式。 另一种方法是,在Launcher启动时检查系统启动的时间,如果是刚启动(比如距离当前时间小于1分钟),则显示applist。但这并不准确。 鉴于问题的复杂性,且要求是开机直接进入,我们这里简化处理:每次Launcher启动(无论是开机还是按Home键)都进入applist界面。 因此,修改Launcher的`onResume`方法,如上所述,延迟后显示All Apps界面。 另外,我们还需要注意,如果All Apps界面已经显示,则不需要重复显示。可以通过检查当前状态来避免。 修改后的`onResume`可能如下: ```java @Override protected void onResume() { super.onResume(); // 延迟执行,避免界面未准备好 new Handler().postDelayed(() -&gt; { if (!isAllAppsVisible()) { showAppsView(true /* animated */, true /* resetListToTop */); } }, 500); } ``` 其中,`isAllAppsVisible`是判断当前是否已经显示All Apps的方法,通常可以通过`getStateManager().getState() == ALL_APPS`来判断。 然而,以上修改是针对Launcher3源代码的,具体实现可能因Launcher版本不同而有所差异。 另外,我们还需要考虑系统权限。修改系统Launcher需要系统签名或系统权限,通常只有定制ROM才能做到。 对于非系统开发,如果设备允许更换Launcher,我们可以开发一个第三方Launcher,并在其启动时直接进入All Apps界面,然后将其设置为默认Launcher。 总结:由于问题要求系统级指令(用户不可见),我们采用修改系统Launcher的方式。 步骤概括: 1. 获取系统Launcher的源代码(如Launcher3)。 2. 修改Launcher的`onResume`方法,在适当的时候显示All Apps界面。 3. 编译生成新的Launcher APK。 4. 将新的Launcher APK集成到系统镜像中,并设置为默认Launcher。 注意:在Android 11上,还需要注意Launcher3的版本和适配问题。 由于修改系统Launcher需要系统开发环境,普通用户无法直接操作,因此这个方案适用于设备制造商或ROM定制者。 最后,生成相关问题: &lt;/think&gt; ### 在 Android 11 系统中实现开机直接进入应用列表界面的方法 #### 核心技术原理 在 Android 系统中,Launcher 是启动后显示应用程序图标的界面[^2]。要让设备开机直接进入应用列表界面(AppList),需要实现以下核心逻辑: $$ \text{系统启动} \rightarrow \text{替换默认Launcher} \rightarrow \text{启动自定义Activity} \rightarrow \text{显示应用列表} $$ #### 实现步骤(系统级修改) 1. **创建自定义应用列表 Activity** ```java public class AppListActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 获取PackageManager服务 PackageManager pm = getPackageManager(); // 查询所有应用列表 List&lt;ApplicationInfo&gt; apps = pm.getInstalledApplications(0); // 构建应用列表视图(示例代码) ListView listView = new ListView(this); ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;&gt;(this, android.R.layout.simple_list_item_1, getAppNames(apps)); listView.setAdapter(adapter); setContentView(listView); } private List&lt;String&gt; getAppNames(List&lt;ApplicationInfo&gt; apps) { List&lt;String&gt; names = new ArrayList&lt;&gt;(); for (ApplicationInfo app : apps) { names.add(app.loadLabel(getPackageManager()).toString()); } return names; } } ``` 2. **修改 AndroidManifest.xml 声明** ```xml &lt;activity android:name=&quot;.AppListActivity&quot; android:clearTaskOnLaunch=&quot;true&quot; android:excludeFromRecents=&quot;true&quot; android:stateNotNeeded=&quot;true&quot;&gt; &lt;intent-filter&gt; &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt; &lt;category android:name=&quot;android.intent.category.HOME&quot; /&gt; &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt; &lt;/intent-filter&gt; &lt;/activity&gt; ``` 3. **设置前台服务保持运行(可选)** ```java // 在Service中保持前台状态(参考引用[3]) Notification notification = new Notification.Builder(this, &quot;channel_id&quot;) .setContentTitle(&quot;系统启动中&quot;) .setContentText(&quot;正在加载应用列表&quot;) .setSmallIcon(R.drawable.ic_launcher) .build(); startForeground(1, notification); ``` 4. **系统级集成要求** - 需要系统签名权限(`android:sharedUserId=&quot;android.uid.system&quot;`) - 在`/system/priv-app/`目录部署应用 - 修改`framework/base/core/res/res/values/config.xml`: ```xml &lt;string name=&quot;config_customLauncher&quot; translatable=&quot;false&quot;&gt;com.your.package/.AppListActivity&lt;/string&gt; ``` #### 设备厂商适配方案 对于主流设备厂商(小米/华为/Samsung),需使用其专属API: | 厂商 | 特殊配置方式 | |---------|-------------------------------------| | 小米 | 修改`ro.miui.cta`系统属性 | | 华为 | 使用`HwStartupControl`服务 | | Samsung | 配置`feature.xml`中的`SEC_FLOATING_FEATURE_SETTINGS_CONFIG_DEFAULT_HOME` | #### 安全注意事项 1. **用户防范机制** 使用户可通过长按HOME键验证当前界面真实性[^1],避免恶意应用伪装 $$ \text{验证逻辑} = \begin{cases} \text{小米设备} &amp; \text{近期任务首项=当前应用} \\ \text{其他设备} &amp; \text{近期任务含当前应用} \end{cases} $$ 2. **权限最小化原则** ```xml &lt;uses-permission android:name=&quot;android.permission.SET_PREFERRED_APPLICATIONS&quot; tools:ignore=&quot;ProtectedPermissions&quot; /&gt; ``` #### 效果验证 1. 编译系统镜像刷机测试 2. 检查启动日志: ```bash adb logcat | grep -E &quot;ActivityTaskManager|AppListActivity&quot; ``` 3. 验证系统属性: ```bash adb shell settings get global device_provisioned ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值