MTK安卓T、U(13、14)版本关机、重启导致自动旋转屏幕关闭的原因及解决方案(附安卓14关机流程)

一、问题现象描述

此问题为MTK(T)版本之后出现的问题
在建设MTK代码的安卓(U)版本时,发现一个有趣的bug,手动开启屏幕自动旋转后,重启、关机会出现关闭屏幕自动旋转功能。下见具体分析。

二、问题初步分析

此问题触发条件为关机或重启,首先想到的就是关机时是否有因素关闭了自动旋转功能。
下面就借助我们熟知的安卓的关机流程,来验证此思路。想直接借鉴解法的可以跳到解决方法

三、具体分析流程

想要验证此想法,我们需要熟悉安卓的关机流程,在关机流程中寻找可能出现的问题,我们都知道手机正常关机主要有两种方式,
一种通过长按power键调出GlobalActions界面,一种通过下拉控制中心点击关机功能键调出GlobalActions界面。
两者在软件层面都是通过GlobalActions启动的关机。下面主要分析长按power键关机的流程。

具体关机流程:
可能涉及到的代码路径

//framework
\frameworks\base\core\res\res\values\config.xml
\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
\frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java
\frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java
\frameworks\base\services\core\java\com\android\server\policy\PowerAction.java
\frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
\frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
\frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java
//system
\system\core\init\property_service.cpp
\system\core\init\init.cpp
\system\core\init\reboot.cpp
\system\core\init\reboot_utils.cpp

关机流程启动是在framework,长按power键触发,进行待机/关机/重启选择。

\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
int policyFlags) {
int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);//进行按键拦截处理
if ((actions & ACTION_PASS_TO_USER) != 0) {
long delayMillis = interceptKeyBeforeDispatching(
focusedToken, fallbackEvent, policyFlags);
if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent)) {
return true;
}
}
return false;
}
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
...
// Handle special keys.
//处理一些特殊按键
switch (keyCode) {
...
case KeyEvent.KEYCODE_POWER: {
EventLogTags.writeInterceptPower(
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
// Any activity on the power button stops the accessibility shortcut
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactiveAndOn);//处理按下power事件
} else {
interceptPowerKeyUp(event, canceled);//处理抬起power事件
}
break;
}
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
...
final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
// Inform the StatusBar; but do not allow it to consume the event.
sendSystemKeyToStatusBarAsync(event);//通知 StatusBar 按下了系统键
// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
//如果此时Power按键仍没有被处理,则根据短按、长按、组合按进行处理
mPowerKeyHandled = mPowerKeyHandled || hungUp|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) {//power事件未被处理
if (!interactive) {//不需要交互
wakeUpFromPowerKey(event.getDownTime());//唤醒屏幕后的处理
}
} else {
// handled by another power key policy.
if (mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
Slog.d(TAG, "Skip power key gesture for other policy has handled it.");
mSingleKeyGestureDetector.reset();
}
}

sendSystemKeyToStatusBarAsync(event)方法,通知 StatusBar 按下了系统键。

\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
private void sendSystemKeyToStatusBarAsync(KeyEvent keyEvent) {
Message message = mHandler.obtainMessage(MSG_SYSTEM_KEY_PRESS, keyEvent);
message.setAsynchronous(true);
mHandler.sendMessage(message);
}

在消息MSG_KEY_DELAYED_PRESS中,调用接口onMultiPress,此接口同样也被PhoneWindowManager的内部类PowerKeyRule实现,并在其方法中调用了powerLongPress方法:

\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
private void powerLongPress(long eventTime) {
final int behavior = getResolvedLongPressOnPowerBehavior();//得到长按电源键行为
...
case LONG_PRESS_POWER_GLOBAL_ACTIONS://getResolvedLongPressOnPowerBehavior()获取的全局长按行为,默认配置见config文件
mPowerKeyHandled = true;
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
false,"Power - Long Press - Global Actions");
showGlobalActions();//触发GlobalAction界面
break;
...

config文件里,安卓原生是这样定义的。

frameworks/base/core/res/res/values/config.xml
<!-- Control the behavior when the user long presses the power button.
0 - Nothing
1 - Global actions menu
2 - Power off (with confirmation)
3 - Power off (without confirmation)
4 - Go to voice assist
-->
<integer name="config_longPressOnPowerBehavior">1</integer>

下面来看下showGlobalActions()方法。

\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
@Override
public void showGlobalActions() {
mHandler.removeMessages(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS);
mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS);//此处通过消息机制传达GlobalAction。
}

看一下详细的消息处理步骤。

\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
private class PolicyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
...
case MSG_DISPATCH_SHOW_GLOBAL_ACTIONS:
showGlobalActionsInternal();//这个是调用Globalaction界面的关键
break;
...

在消息处理中showGlobalActionsInternal()是一个核心方法,我们来看一下。

\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
void showGlobalActionsInternal() {
if (mGlobalActions == null) {
mGlobalActions = mGlobalActionsFactory.get();
}
final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());//弹出GlobalAction对话框
// since it took two seconds of long press to bring this up,
// poke the wake lock so they have some time to see the dialog.
mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
}
\frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java
public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
if (mGlobalActionsProvider != null && mGlobalActionsProvider.isGlobalActionsDisabled()) {
return;
}
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = deviceProvisioned;
mShowing = true;
if (mGlobalActionsAvailable) {
mHandler.postDelayed(mShowTimeout, 5000);
mGlobalActionsProvider.showGlobalActions();
} else {
// SysUI isn't alive, show legacy menu.
ensureLegacyCreated();
mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);//传统关机调用mLegacyGlobalActions.showDialog()方法
}
}

关机走到这里有两个分支,我们着重来看传统的关机流程,mLegacyGlobalActions.showDialog()方法。

\frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java
/**
* Show the global actions dialog (creating if necessary)
* @param keyguardShowing True if keyguard is showing
*/
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
// Show delayed, so that the dismiss of the previous dialog completes
mHandler.sendEmptyMessage(MESSAGE_SHOW);
} else {
handleShow();//如果mDialog不为空,则创建
}
}
\frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java
private void handleShow() {
awakenIfNecessary();
mDialog = createDialog();//创建新的对话框,加载关机选项,设置点击选项
prepareDialog();//更新静音、飞行等各种模式
// If we only have 1 item and it's a simple press action, just do this action.
if (mAdapter.getCount() == 1
&& mAdapter.getItem(0) instanceof SinglePressAction
&& !(mAdapter.getItem(0) instanceof LongPressAction)) {
((SinglePressAction) mAdapter.getItem(0)).onPress();
} else {
if (mDialog != null) {
WindowManager.LayoutParams attrs =
mDialog.getWindow().getAttributes();
attrs.setTitle("LegacyGlobalActions");
mDialog.getWindow().setAttributes(attrs);
mDialog.show();
mDialog.getWindow().getDecorView().setSystemUiVisibility(
View.STATUS_BAR_DISABLE_EXPAND);
}
}
}
\frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java
private ActionsDialog createDialog() {
...
mItems = new ArrayList<Action>();
String[] defaultActions = mContext.getResources().getStringArray(
com.android.internal.R.array.config_globalActionsList);//设置默认的全局执行选项
ArraySet<String> addedKeys = new ArraySet<String>();
for (int i = 0; i < defaultActions.length; i++) {
String actionKey = defaultActions[i];
if (addedKeys.contains(actionKey)) {
// If we already have added this, don't add it again.
continue;
}
if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {//关机模式
mItems.add(new PowerAction(mContext, mWindowManagerFuncs));
...
//将默认关机选项加入Global Menu中
addedKeys.add(actionKey);
}
...
ActionsDialog dialog = new ActionsDialog(mContext, params);
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
dialog.getListView().setItemsCanFocus(true);
dialog.getListView().setLongClickable(true);
dialog.getListView().setOnItemLongClickListener(
new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
long id) {
final Action action = mAdapter.getItem(position);
if (action instanceof LongPressAction) {
return ((LongPressAction) action).onLongPress();//处理选项点击事件
}
return false;
}
});
...
return dialog;
}

这里面有两个关键的地方,一个是设置全局默认执行选项,是在config中配置,一个是对关机按键的处理,我们分别来看。
先是全局默认执行选项。

\frameworks\base\core\res\res\values\config.xml
<!-- Defines the default set of global actions. Actions may still be disabled or hidden based
on the current state of the device.
Each item must be one of the following strings:
"power" = Power off
"settings" = An action to launch settings
"airplane" = Airplane mode toggle
"bugreport" = Take bug report, if available
"silent" = silent mode
"users" = list of users
"restart" = restart device
"emergency" = Launch emergency dialer
"lockdown" = Lock down device until the user authenticates
"logout" = Logout the current user
-->
<string-array translatable="false" name="config_globalActionsList">
<item>emergency</item>//紧急
<item>lockdown</item>//锁屏
<item>power</item>//关机
<item>restart</item>//重启
<item>logout</item>//注销账户
<item>screenshot</item>//截屏
<item>bugreport</item>//上报错误
<item>airplane</item>//飞行模式
<item>silent</item>//静音
</string-array>

紧接着是按键的处理。

\frameworks\base\services\core\java\com\android\server\policy\PowerAction.java
@Override
public boolean onLongPress() {
// don't actually trigger the reboot if we are running stability
// tests via monkey
if (ActivityManager.isUserAMonkey()) {
return false;
}
UserManager um = mContext.getSystemService(UserManager.class);
if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.rebootSafeMode(true);//传统关机走了这里。
return true;
}
return false;
}
\frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
@Override
public void rebootSafeMode(boolean confirm) {
// Pass in the UI context, since ShutdownThread requires it (to show UI).
ShutdownThread.rebootSafeMode(ActivityThread.currentActivityThread().getSystemUiContext(),confirm);//进入关机线程ShutdownThread
}

至此需要用户进行的操作已经走完,下面系统将自行进入关机线程进行处理。

\frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
public static void rebootSafeMode(final Context context, boolean confirm) {
...
shutdownInner(context, confirm);
}
\frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
private static void shutdownInner(final Context context, boolean confirm) {
...
if (confirm) {
final CloseDialogReceiver closer = new CloseDialogReceiver(context);
if (sConfirmDialog != null) {
sConfirmDialog.dismiss();
}
sConfirmDialog = new AlertDialog.Builder(context)
.setTitle(mRebootSafeMode
? com.android.internal.R.string.reboot_safemode_title
: com.android.internal.R.string.power_off)
.setMessage(resourceId)
.setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
beginShutdownSequence(context);//选择确认关机,开始执行关机流程
}
})
.setNegativeButton(com.android.internal.R.string.no, null)
.create();
closer.dialog = sConfirmDialog;
sConfirmDialog.setOnDismissListener(closer);
sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
sConfirmDialog.show();
} else {
beginShutdownSequence(context);
}
}
frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
private static void beginShutdownSequence(Context context) {
...
sInstance.mProgressDialog = showShutdownDialog(context);//显示关机进度框
...
//启动关机线程,执行Run()方法
sInstance.mHandler = new Handler() {
};
sInstance.start();
}

关机进度框和run()方法我们分开来看。
下面先介绍进度框。

frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
private static ProgressDialog showShutdownDialog(Context context) {
// Throw up a system dialog to indicate the device is rebooting / shutting down.
ProgressDialog pd = new ProgressDialog(context);
// Path 1: Reboot to recovery for update
// Condition: mReason startswith REBOOT_RECOVERY_UPDATE
//
// Path 1a: uncrypt needed
// Condition: if /cache/recovery/uncrypt_file exists but
// /cache/recovery/block.map doesn't.
// UI: determinate progress bar (mRebootHasProgressBar == True)
//
// * Path 1a is expected to be removed once the GmsCore shipped on
// device always calls uncrypt prior to reboot.
//
// Path 1b: uncrypt already done
// UI: spinning circle only (no progress bar)
//
// Path 2: Reboot to recovery for factory reset
// Condition: mReason == REBOOT_RECOVERY
// UI: spinning circle only (no progress bar)
//
// Path 3: Regular reboot / shutdown
// Condition: Otherwise
// UI: spinning circle only (no progress bar)
// mReason could be "recovery-update" or "recovery-update,quiescent".
if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
// We need the progress bar if uncrypt will be invoked during the
// reboot, which might be time-consuming.
mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
&& !(RecoverySystem.BLOCK_MAP_FILE.exists());
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
if (mRebootHasProgressBar) {
pd.setMax(100);
pd.setProgress(0);
pd.setIndeterminate(false);
pd.setProgressNumberFormat(null);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_update_prepare));
} else {
if (showSysuiReboot()) {
return null;
}
pd.setIndeterminate(true);
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_update_reboot));
}
} else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
if (RescueParty.isAttemptingFactoryReset()) {
// We're not actually doing a factory reset yet; we're rebooting
// to ask the user if they'd like to reset, so give them a less
// scary dialog message.
pd.setTitle(context.getText(com.android.internal.R.string.power_off));
pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
pd.setIndeterminate(true);
} else if (showSysuiReboot()) {
return null;
} else {
// Factory reset path. Set the dialog message accordingly.
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_reset_message));
pd.setIndeterminate(true);
}
} else {
if (showSysuiReboot()) {
return null;
}
pd.setTitle(context.getText(com.android.internal.R.string.power_off));
pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
pd.setIndeterminate(true);
}
pd.setCancelable(false);
pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
pd.show();//显示各自模式的关机对话框,直到整个关机流程结束。
return pd;
}

下面我们来看最终的关机线程run()方法。

\frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
/**
* Makes sure we handle the shutdown gracefully.
* Shuts off power regardless of radio state if the allotted time has passed.
*/
public void run() {
...
final IActivityManager am = IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
try {
am.shutdown(MAX_BROADCAST_TIME);//关机前关闭AMS
} catch (RemoteException e) {
}
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
}
shutdownTimingLog.traceEnd();// ShutdownActivityManager,关闭AMS
metricEnded(METRIC_AM);
final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
if (pm != null) {
pm.shutdown();//关机前关闭PMS
}
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
}
shutdownTimingLog.traceEnd(); // ShutdownPackageManager,关闭PMS
metricEnded(METRIC_PM);
...//关闭一些核心功能
// Remaining work will be done by init, including vold shutdown
rebootOrShutdown(mContext, mReboot, mReason);//执行重启或者关机
\frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
...
// Shutdown power
Log.i(TAG, "Performing low-level shutdown...");
PowerManagerService.lowLevelShutdown(reason);
}
\frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java
/**
* Low-level function turn the device off immediately, without trying
* to be clean. Most people should use {@link ShutdownThread} for a clean shutdown.
*
* @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
*/
public static void lowLevelShutdown(String reason) {
if (reason == null) {
reason = "";
}
SystemProperties.set("sys.powerctl", "shutdown," + reason);//设置系统控制属性sys.powerctl=shutdown
}

到此先总结下以上的流程:

  • power键长按,短按,超长按事件处理,根据系统配置的power长按行为决定是否要交互,还是直接关机。
  • ShutdownThread关机Dialog根据配置文件展示相关的选项,供用户选择。
  • 关机流程最后在PMS通过设置sys.powerctl系统属性向底层传递,并记录关机原因。
    下面来看关机属性定义,这里不作过多说明:
\system\core\init\property_service.cpp
std::optional<uint32_t> HandlePropertySet(const std::string& name, const std::string& value,const std::string& source_context, const ucred& cr,SocketConnection* socket, std::string* error) {
...
if (name == "sys.powerctl") {//记录sys.powerctl 属性的关机进程
std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
std::string process_cmdline;
std::string process_log_string;
if (ReadFileToString(cmdline_path, &process_cmdline)) {
// Since cmdline is null deliminated, .c_str() conveniently gives us just the process
// path.
process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
}
LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
<< process_log_string;
if (value == "reboot,userspace" && !is_userspace_reboot_supported().value_or(false)) {
*error = "Userspace reboot is not supported by this device";
return {PROP_ERROR_INVALID_VALUE};
}
}
...
return PropertySet(name, value, socket, error);//设置关机属性
}
\system\core\init\property_service.cpp
static std::optional<uint32_t> PropertySet(const std::string& name, const std::string& value,SocketConnection* socket, std::string* error) {
...
if (socket && persistent_properties_loaded && StartsWith(name, "persist.")) {
if (persist_write_thread) {
persist_write_thread->Write(name, value, std::move(*socket));
return {};
}
WritePersistentProperty(name, value);//写persist属性到rom
}
NotifyPropertyChange(name, value);//通知属性值有更新
#endif
return {PROP_SUCCESS};
}
\system\core\init\property_service.cpp
void NotifyPropertyChange(const std::string& name, const std::string& value) {
// If init hasn't started its main loop, then it won't be handling property changed messages
// anyway, so there's no need to try to send them.
auto lock = std::lock_guard{accept_messages_lock};
#ifdef MTK_LOG
pid_t currentTid = gettid();
#endif
if (accept_messages) {
#ifdef MTK_LOG
if (PropServThrGetTid() == currentTid)
SnapshotPropertyFlowTraceLog("SPC");
#endif
PropertyChanged(name, value);//通知属性值有更新
}
}
\system\core\init\init.cpp
void PropertyChanged(const std::string& name, const std::string& value) {
#ifdef G1122717
if (!ActionManager::GetInstance().WatchingPropertyCount(name))
return;
#endif
// If the property is sys.powerctl, we bypass the event queue and immediately handle it.
// This is to ensure that init will always and immediately shutdown/reboot, regardless of
// if there are other pending events to process or if init is waiting on an exec service or
// waiting on a property.
// In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
// commands to be executed.
if (name == "sys.powerctl") {//根据属性值触发
trigger_shutdown(value);//关机触发器
}
if (property_triggers_enabled) {
ActionManager::GetInstance().QueuePropertyChange(name, value);
WakeMainInitThread();
}
prop_waiter_state.CheckAndResetWait(name, value);
}
trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };//执行shutdown_state中的TriggerShutdown()函数。
\system\core\init\init.cpp
static class ShutdownState {
public:
void TriggerShutdown(const std::string& command) {
// We can't call HandlePowerctlMessage() directly in this function,
// because it modifies the contents of the action queue, which can cause the action queue
// to get into a bad state if this function is called from a command being executed by the
// action queue. Instead we set this flag and ensure that shutdown happens before the next
// command is run in the main init loop.
auto lock = std::lock_guard{shutdown_command_lock_};
shutdown_command_ = command;
do_shutdown_ = true;
WakeMainInitThread();//唤醒主线程
}
std::optional<std::string> CheckShutdown() __attribute__((warn_unused_result)) {
auto lock = std::lock_guard{shutdown_command_lock_};
if (do_shutdown_ && !IsShuttingDown()) {
do_shutdown_ = false;
return shutdown_command_;
}
return {};
}
private:
std::mutex shutdown_command_lock_;
std::string shutdown_command_ GUARDED_BY(shutdown_command_lock_);
bool do_shutdown_ = false;
} shutdown_state;
\system\core\init\init.cpp
int SecondStageMain(int argc, char** argv) {
...
while (true) {
// By default, sleep until something happens. Do not convert far_future into
// std::chrono::milliseconds because that would trigger an overflow. The unit of boot_clock
// is 1ns.
const boot_clock::time_point far_future = boot_clock::time_point::max();
boot_clock::time_point next_action_time = far_future;
auto shutdown_command = shutdown_state.CheckShutdown();
if (shutdown_command) {//执行关机
LOG(INFO) << "Got shutdown_command '" << *shutdown_command
<< "' Calling HandlePowerctlMessage()";
HandlePowerctlMessage(*shutdown_command);//处理关机消息
}
...
\system\core\init\reboot.cpp
void HandlePowerctlMessage(const std::string& command) {
unsigned int cmd = 0;
std::vector<std::string> cmd_params = Split(command, ",");
std::string reboot_target = "";
bool run_fsck = false;
bool command_invalid = false;
bool userspace_reboot = false;
if (cmd_params[0] == "shutdown") {//关机
cmd = ANDROID_RB_POWEROFF;//这里指示关机标志,后续流程中会用到
if (cmd_params.size() >= 2) {
if (cmd_params[1] == "userrequested") {//挂机原因为用户触发请求
// The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED.
// Run fsck once the file system is remounted in read-only mode.
run_fsck = true;
} else if (cmd_params[1] == "thermal") {
// Turn off sources of heat immediately.
TurnOffBacklight();//关闭屏幕背光
// run_fsck is false to avoid delay
cmd = ANDROID_RB_THERMOFF;//关闭温度控制
}
}
}else if (cmd_params[0] == "reboot") {//重启,这里面包含着进入fastboot、bootloader、以及recovery的内容,这里不做过多阐述
cmd = ANDROID_RB_RESTART2;
if (cmd_params.size() >= 2) {
reboot_target = cmd_params[1];
if (reboot_target == "userspace") {
LOG(INFO) << "Userspace reboot requested";
userspace_reboot = true;
}
// adb reboot fastboot should boot into bootloader for devices not
// supporting logical partitions.
if (reboot_target == "fastboot" &&
!android::base::GetBoolProperty("ro.boot.dynamic_partitions", false))
{
reboot_target = "bootloader";
}
// When rebooting to the bootloader notify the bootloader writing
// also the BCB.
if (reboot_target == "bootloader") {
std::string err;
if (!write_reboot_bootloader(&err)) {
LOG(ERROR) << "reboot-bootloader: Error writing "
"bootloader_message: "
<< err;
}
} else if (reboot_target == "recovery") {
bootloader_message boot = {};
if (std::string err; !read_bootloader_message(&boot, &err)) {
LOG(ERROR) << "Failed to read bootloader message: " << err;
}
// Update the boot command field if it's empty, and preserve
// the other arguments in the bootloader message.
if (!CommandIsPresent(&boot)) {
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
if (std::string err; !write_bootloader_message(boot, &err)) {
LOG(ERROR) << "Failed to set bootloader message: " << err;
return;
}
}
}
...
LOG(INFO) << "Clear action queue and start shutdown trigger";
ActionManager::GetInstance().ClearQueue();
// Queue shutdown trigger first
ActionManager::GetInstance().QueueEventTrigger("shutdown");
// Queue built-in shutdown_done
auto shutdown_handler = [cmd, command, reboot_target, run_fsck](const BuiltinArguments&) {
DoReboot(cmd, command, reboot_target, run_fsck);//执行后续的关机流程
return Result<void>{};
};
...

这里需要注意一下,cmd = ANDROID_RB_POWEROFF,在后续reboot_utils.cpp中会有使用。

\system\core\init\reboot.cpp
static void DoReboot(unsigned int cmd, const std::string& reason, const std::string& reboot_target,bool run_fsck) {
...
// Ensure last reboot reason is reduced to canonical
// alias reported in bootloader or system boot reason.
// 确保关机原因符合bootloader或system的规范
size_t skip = 0;
std::vector<std::string> reasons = Split(reason, ",");
if (reasons.size() >= 2 && reasons[0] == "reboot" &&
(reasons[1] == "recovery" || reasons[1] == "bootloader" || reasons[1] == "cold" ||
reasons[1] == "hard" || reasons[1] == "warm")) {
skip = strlen("reboot,");
}
PersistRebootReason(reason.c_str() + skip, true);
// If /data isn't mounted then we can skip the extra reboot steps below, since we don't need to
// worry about unmounting it.
//如果/data没有被挂载,我们可以跳过下面的额外重启步骤,因为我们不需要它,不用担心没有挂载的问题
if (!IsDataMounted("*")) {
sync();
RebootSystem(cmd, reboot_target, reason);//data分区没有挂载,跳过额外重启步骤
abort();
}
//获取关机动画属性值
bool do_shutdown_animation = GetBoolProperty("ro.init.shutdown_animation", false);
// watchdogd is a vendor specific component but should be alive to complete shutdown safely.
const std::set<std::string> to_starts{"watchdogd"};
std::set<std::string> stop_first;
for (const auto& s : ServiceList::GetInstance()) {
if (kDebuggingServices.count(s->name())) {
// keep debugging tools until non critical ones are all gone.
s->SetShutdownCritical();
} else if (to_starts.count(s->name())) {
if (auto result = s->Start(); !result.ok()) {
LOG(ERROR) << "Could not start shutdown 'to_start' service '" << s->name()
<< "': " << result.error();
}
s->SetShutdownCritical();
} else if (do_shutdown_animation) {//有关机动画则跳出本次循环
continue;
} else if (s->IsShutdownCritical()) {
// Start shutdown critical service if not started.
if (auto result = s->Start(); !result.ok()) {
LOG(ERROR) << "Could not start shutdown critical service '" << s->name()
<< "': " << result.error();
}
} else {
stop_first.insert(s->name());
}
}
// remaining operations (specifically fsck) may take a substantial duration
if (!do_shutdown_animation && (cmd == ANDROID_RB_POWEROFF || is_thermal_shutdown)) {
TurnOffBacklight();//关闭背光
}
Service* boot_anim = ServiceList::GetInstance().FindService("bootanim");//获取关机动画
Service* surface_flinger = ServiceList::GetInstance().FindService("surfaceflinger");
if (boot_anim != nullptr && surface_flinger != nullptr && surface_flinger->IsRunning()) {
if (do_shutdown_animation) {
SetProperty("service.bootanim.exit", "0");
SetProperty("service.bootanim.progress", "0");
// Could be in the middle of animation. Stop and start so that it can pick
// up the right mode.
boot_anim->Stop();
}
for (const auto& service : ServiceList::GetInstance()) {
if (service->classnames().count("animation") == 0) {
continue;
}
// start all animation classes if stopped.
if (do_shutdown_animation) {
service->Start();
}
service->SetShutdownCritical(); // will not check animation class separately
}
if (do_shutdown_animation) {
boot_anim->Start();//启动关机动画
surface_flinger->SetShutdownCritical();
boot_anim->SetShutdownCritical();
}
}
// optional shutdown step
//可选的关机步骤
// 1. terminate all services except shutdown critical ones. wait for delay to finish
if (shutdown_timeout > 0ms) {
StopServicesAndLogViolations(stop_first, shutdown_timeout / 2, true /* SIGTERM */);
}
// Send SIGKILL to ones that didn't terminate cleanly.
StopServicesAndLogViolations(stop_first, 0ms, false /* SIGKILL */);
SubcontextTerminate();
// Reap subcontext pids.
ReapAnyOutstandingChildren();
// 3. send volume abort_fuse and volume shutdown to vold
//通知Vold服务关闭
Service* vold_service = ServiceList::GetInstance().FindService("vold");
if (vold_service != nullptr && vold_service->IsRunning()) {
// Manually abort FUSE connections, since the FUSE daemon is already dead
// at this point, and unmounting it might hang.
CallVdc("volume", "abort_fuse");
CallVdc("volume", "shutdown");
vold_service->Stop();
} else {
LOG(INFO) << "vold not running, skipping vold shutdown";
}
// logcat stopped here
//关闭logcat
StopServices(kDebuggingServices, 0ms, false /* SIGKILL */);
// 4. sync, try umount, and optionally run fsck for user shutdown
{
Timer sync_timer;
LOG(INFO) << "sync() before umount...";
sync();
LOG(INFO) << "sync() before umount took" << sync_timer;
}
// 5. drop caches and disable zram backing device, if exist
KillZramBackingDevice();//用于停止使用zram作为后备设备
LOG(INFO) << "Ready to unmount apexes. So far shutdown sequence took " << t;
// 6. unmount active apexes, otherwise they might prevent clean unmount of /data.
if (auto ret = UnmountAllApexes(); !ret.ok()) {
LOG(ERROR) << ret.error();
}
UmountStat stat =
TryUmountAndFsck(cmd, run_fsck, shutdown_timeout - t.duration(), &reboot_semaphore);
// Follow what linux shutdown is doing: one more sync with little bit delay
{
Timer sync_timer;
LOG(INFO) << "sync() after umount...";
sync();
LOG(INFO) << "sync() after umount took" << sync_timer;
}
if (!is_thermal_shutdown) std::this_thread::sleep_for(100ms);
LogShutdownTime(stat, &t);
// Send signal to terminate reboot monitor thread.
reboot_monitor_run = false;
sem_post(&reboot_semaphore);
// Reboot regardless of umount status. If umount fails, fsck after reboot will fix it.
if (IsDataMounted("f2fs")) {
uint32_t flag = F2FS_GOING_DOWN_FULLSYNC;
unique_fd fd(TEMP_FAILURE_RETRY(open("/data", O_RDONLY)));
int ret = ioctl(fd.get(), F2FS_IOC_SHUTDOWN, &flag);
if (ret) {
PLOG(ERROR) << "Shutdown /data: ";
} else {
LOG(INFO) << "Shutdown /data";
}
}
RebootSystem(cmd, reboot_target, reason);//关闭系统
abort();
}

前面cmd = ANDROID_RB_POWEROFF的标志,在这里有使用到。

\system\core\init\reboot_utils.cpp
void __attribute__((noreturn))
RebootSystem(unsigned int cmd, const std::string& rebootTarget, const std::string& reboot_reason) {
...
switch (cmd) {
case ANDROID_RB_POWEROFF://很明确地指示到了这里
reboot(RB_POWER_OFF);//关闭电源
break;
...
// In normal case, reboot should not return.
PLOG(ERROR) << "reboot call returned";
abort();//终止
}

至此,传统模式下的手机关机流程已经全部执行完毕。

四、解决方法

从上面的关机流程,我们可以看到关机时候,关闭AMS、PMS等功能,一般是在ShutdownThread这个关机线程下,因为MTK将此部分功能overlay到了vendor下,我们去vendor下查看。

\vendor\mediatek\proprietary\frameworks\base\services\core\java\com\mediatek\server\MtkShutdownThread.java
private static void bootanimCust(Context context) {
boolean isRotaionEnabled = false;
// [MTK] fix shutdown animation timing issue
SystemProperties.set("service.shutanim.running", "0");
Log.i(TAG, "set service.shutanim.running to 0");
try {
isRotaionEnabled = Settings.System.getInt(context.getContentResolver(),
Settings.System.ACCELEROMETER_ROTATION, 1) != 0;
if (isRotaionEnabled) {//自动旋转打开
final IWindowManager wm = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
if (wm != null) {
wm.freezeRotation(Surface.ROTATION_0);
}
Settings.System.putInt(context.getContentResolver(),
Settings.System.ACCELEROMETER_ROTATION, 0);//将自动旋转关闭
isRestore = true;//设置自动旋转打开的标志位
// Settings.System.putInt(context.getContentResolver(),
// MtkSettingsExt.System.ACCELEROMETER_ROTATION_RESTORE, 1);
}
} catch (NullPointerException ex) {
Log.e(TAG, "check Rotation: context object is null when get Rotation");
} catch (RemoteException e) {
e.printStackTrace();
}
beginAnimationTime = SystemClock.elapsedRealtime() + MIN_SHUTDOWN_ANIMATION_PLAY_TIME;
//Disable key dispatch
try {
final IWindowManager wm = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
if (wm != null) {
wm.setEventDispatching(false);
}
} catch (RemoteException e) {
e.printStackTrace();
}
//Disable key dispatch
startBootAnimation();
}
protected void mShutdownSeqFinish(Context context) {
if (isRestore) {
isRestore = false;
//add@**添加在此
//Settings.System.putInt(context.getContentResolver(),
// Settings.System.ACCELEROMETER_ROTATION, 1);
Settings.System.putInt(context.getContentResolver(),
MtkSettingsExt.System.ACCELEROMETER_ROTATION_RESTORE, 1);
}
/* For UX, NO set backlight-off earlier to reduce */
/* backlight-off duration of reboot. */
// for Shutdown animation
shutdownAnimationService();
// if (reboot == false) {
setBacklightOff();
// }
}

可以看到MTK为了解决一个动画的bug,将自动旋转在关机的时候关闭,且专门设置了回溯的方法,但在方法中没有将系统自动旋转功能重新打开,我们在此处添上就好了。

五、小结

虽然问题本身不复杂,但是在解决问题的过程中加深了对关机流程的理解,在后续的工作中如果遇到有关关机方面的内容可以进行参考,由于本人技术水平有限,错误之处还请各位大佬斧正。

posted @   Chace_Lee  阅读(670)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示