背景
项目中需要处理Android的原生开机动画,一定条件下还需要做到静默重启(android系统启动进入到桌面前,屏幕保持完全没有亮度的状态)。因为项目使用的rom是MTK平台支持,一开始并不知道Android 的QUIESCENT_REBOOT模式,所以自己想办法实现了此功能,详细见博客:基于Q的Android开机动画。因为后续Linux启用了SELinux增强了权限限制,原有的方案因为权限限制了文件的执行和读写,需要进行比较大的变动。后来,学习了下Android 的QUIESCENT_REBOOT模式,并把它用到了项目中解决了问题。这里介绍下QUIESCENT_REBOOT模式原理。
Android Quiescent的原理和流程
一、powerManager.reboot(PowerManager.REBOOT_QUIESCENT) 来静默重启(本地测试可以通过命令行触发svc power reboot quiescent)
1、代码:frameworks/base/core/java/android/os/PowerManager.java
public void reboot(String reason) {
try {
// mService是PowerManagerService
mService.reboot(false, reason, true);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
2、代码frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
// True if the lights should stay off until an explicit user action.
// 这个标记记录是否静默重启
private static boolean sQuiescent;
@Override // Binder call
public void reboot(boolean confirm, String reason, boolean wait) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
......
final long ident = Binder.clearCallingIdentity();
try {
shutdownOrRebootInternal(HALT_MODE_REBOOT, confirm, reason, wait);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
final String reason, boolean wait) {
// lowLevelReboot会去解析reason并存储到SystemProperties
if (mHandler == null || !mSystemReady) {
if (RescueParty.isAttemptingFactoryReset()) {
// If we're stuck in a really low-level reboot loop, and a
// rescue party is trying to prompt the user for a factory data
// reset, we must GET TO DA CHOPPA!
PowerManagerService.lowLevelReboot(reason);
} else {
throw new IllegalStateException("Too early to call shutdown() or reboot()");
}
}
// 调用ShutdownThread重启
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) {
ShutdownThread.rebootSafeMode(getUiContext(), confirm);
} else if (haltMode == HALT_MODE_REBOOT) {
ShutdownThread.reboot(getUiContext(), reason, confirm);
} else {
ShutdownThread.shutdown(getUiContext(), reason, confirm);
}
}
}
};
......
}
public static void lowLevelReboot(String reason) {
if (reason == null) {
reason = "";
}
// If the reason is "quiescent", it means that the boot process should proceed
// without turning on the screen/lights.
// The "quiescent" property is sticky, meaning that any number
// of subsequent reboots should honor the property until it is reset.
if (reason.equals(PowerManager.REBOOT_QUIESCENT)) {
sQuiescent = true;
reason = "";
} else if (reason.endsWith("," + PowerManager.REBOOT_QUIESCENT)) {
sQuiescent = true;
reason = reason.substring(0,
reason.length() - PowerManager.REBOOT_QUIESCENT.length() - 1);
}
if (sQuiescent) {
// Pass the optional "quiescent" argument to the bootloader to let it know
// that it should not turn the screen/lights on.
reason = reason + ",quiescent";
}
// 这里会存储到系统参数
SystemProperties.set("sys.powerctl", "reboot," + reason);
try {
Thread.sleep(20 * 1000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Slog.wtf(TAG, "Unexpected return from lowLevelReboot!");
}
sQuiescent记录是否静默重启,lowLevelReboot会去解析reason并设置sQuiescent为true,同时把reason存储到SystemProperties,最后调用ShutdownThread带着reason传参。
3、代码frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
这个类中主要是根据一些条件做了一些单独处理,例如recovery等可能还会展示弹窗,这里有兴趣的可以根据代码路径自行观看,静默重启这个类中没有用到。
4、前边说到了SystemProperties.set(“sys.powerctl”, “reboot,” + reason),这里很重要。静默重启会把这个值存储到内核特定的启动参数,这个启动参数在下次重启时会被系统拿到。这里内部就涉及SystemProperties原理和源代码了,不特意分析,知道大概用做啥。
5、同时,内核还会解析reason,存储另一个SystemProperties;大概的流程是lk 中读到 rtc 的 Quiescent 标志位,则不显示开机logo,并且在 cmdline 中添加 androidboot.quiescent=1;lk -> kernel -> init,init 会解析 cmdline,并把其中的 androidboot.quiescent=1 解析出来,并设置成 ro.boot.quiescent=1,这样后续 Android 所有地方都能知道此次是 quiescent 开机。
6、bootanimation (frameworks/base/cmds/bootanimation) 中,读到 ro.boot.quiescent=1,则不显示开机动画
代码:frameworks/base/cmds/bootanimation/BootAnimationUtil.cpp
bool bootAnimationDisabled() {
45 char value[PROPERTY_VALUE_MAX];
46 property_get("debug.sf.nobootanimation", value, "0");
47 if (atoi(value) > 0) {
48 return true;
49 }
50
51 property_get("ro.boot.quiescent", value, "0");
52 return atoi(value) > 0;
53}
代码:frameworks/base/cmds/bootanimation/bootanimation_main.cpp
36int main()
37{
38 setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
39
// 如果noBootAnimation,不展示开机动画
40 bool noBootAnimation = bootAnimationDisabled();
41 ALOGI_IF(noBootAnimation, "boot animation disabled");
42 if (!noBootAnimation) {
43
44 sp<ProcessState> proc(ProcessState::self());
45 ProcessState::self()->startThreadPool();
46
47 // create the boot animation object (may take up to 200ms for 2MB zip)
48 sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());
49
50 waitForSurfaceFlinger();
51
52 boot->run("BootAnimation", PRIORITY_DISPLAY);
53
54 ALOGV("Boot animation set up. Joining pool.");
55
56 IPCThreadState::self()->joinThreadPool();
57 }
58 return 0;
59}
7、这里还会涉及开机log的隐藏,在kernal层根据存储在内核的启动参数作为判断,隐藏logo或者亮度。这里大概属于猜测,具体有兴趣的可以自己深入研究。
解决项目中问题
项目中除了要隐藏开机动画,还需要把亮度调为最低。
1、需要把开机默认设置调度的代码给屏蔽掉
代码:frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
@VisibleForTesting
DisplayManagerService(Context context, Injector injector) {
super(context);
......
PowerManager pm = mContext.getSystemService(PowerManager.class);
// 注释掉的为源代码,这里会取一个默认160的亮度并设置
//mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
// 这里根据静默的参数,增加判断
if (SystemProperties.getInt("ro.boot.quiescent", 0) == 0) {
mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
}
......
}
2、因为除了这个地方会设置默认亮度,系统起来后还会被android层重新设置亮度,就恢复到正常亮度了。