一、问题现象描述
此问题为MTK(T)版本之后出现的问题
在建设MTK代码的安卓(U)版本时,发现一个有趣的bug,手动开启屏幕自动旋转后,重启、关机会出现关闭屏幕自动旋转功能。下见具体分析。
二、问题初步分析
此问题触发条件为关机或重启,首先想到的就是关机时是否有因素关闭了自动旋转功能。
下面就借助我们熟知的安卓的关机流程,来验证此思路。想直接借鉴解法的可以跳到解决方法。
三、具体分析流程
想要验证此想法,我们需要熟悉安卓的关机流程,在关机流程中寻找可能出现的问题,我们都知道手机正常关机主要有两种方式,
一种通过长按power键调出GlobalActions界面,一种通过下拉控制中心点击关机功能键调出GlobalActions界面。
两者在软件层面都是通过GlobalActions启动的关机。下面主要分析长按power键关机的流程。

具体关机流程:
可能涉及到的代码路径
| |
| |
| \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\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) { |
| ... |
| |
| |
| switch (keyCode) { |
| ... |
| case KeyEvent.KEYCODE_POWER: { |
| EventLogTags.writeInterceptPower( |
| KeyEvent.actionToString(event.getAction()), |
| mPowerKeyHandled ? 1 : 0, |
| mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER)); |
| |
| result &= ~ACTION_PASS_TO_USER; |
| isWakeKey = false; |
| if (down) { |
| interceptPowerKeyDown(event, interactiveAndOn); |
| } else { |
| interceptPowerKeyUp(event, canceled); |
| } |
| break; |
| } |
| |
| private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { |
| ... |
| final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event); |
| |
| |
| sendSystemKeyToStatusBarAsync(event); |
| |
| |
| |
| |
| mPowerKeyHandled = mPowerKeyHandled || hungUp|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted(); |
| if (!mPowerKeyHandled) { |
| if (!interactive) { |
| wakeUpFromPowerKey(event.getDownTime()); |
| } |
| } else { |
| |
| 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: |
| mPowerKeyHandled = true; |
| performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, |
| false,"Power - Long Press - Global Actions"); |
| showGlobalActions(); |
| break; |
| ... |
config文件里,安卓原生是这样定义的。
| frameworks/base/core/res/res/values/config.xml |
| |
| |
| |
| |
| |
| |
| |
| <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); |
| } |
看一下详细的消息处理步骤。
| \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(); |
| 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()); |
| |
| |
| 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 { |
| |
| ensureLegacyCreated(); |
| mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned); |
| } |
| } |
关机走到这里有两个分支,我们着重来看传统的关机流程,mLegacyGlobalActions.showDialog()方法。
| \frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java |
| |
| |
| |
| |
| public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { |
| mKeyguardShowing = keyguardShowing; |
| mDeviceProvisioned = isDeviceProvisioned; |
| if (mDialog != null) { |
| mDialog.dismiss(); |
| mDialog = null; |
| |
| mHandler.sendEmptyMessage(MESSAGE_SHOW); |
| } else { |
| handleShow(); |
| } |
| } |
| \frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java |
| private void handleShow() { |
| awakenIfNecessary(); |
| mDialog = createDialog(); |
| prepareDialog(); |
| |
| |
| 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)) { |
| |
| continue; |
| } |
| if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { |
| mItems.add(new PowerAction(mContext, mWindowManagerFuncs)); |
| ... |
| |
| addedKeys.add(actionKey); |
| } |
| ... |
| ActionsDialog dialog = new ActionsDialog(mContext, params); |
| dialog.setCanceledOnTouchOutside(false); |
| |
| 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 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <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() { |
| |
| |
| 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) { |
| |
| ShutdownThread.rebootSafeMode(ActivityThread.currentActivityThread().getSystemUiContext(),confirm); |
| } |
至此需要用户进行的操作已经走完,下面系统将自行进入关机线程进行处理。
| \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); |
| ... |
| |
| sInstance.mHandler = new Handler() { |
| }; |
| sInstance.start(); |
| } |
关机进度框和run()方法我们分开来看。
下面先介绍进度框。
| frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java |
| private static ProgressDialog showShutdownDialog(Context context) { |
| |
| ProgressDialog pd = new ProgressDialog(context); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) { |
| |
| |
| 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()) { |
| |
| |
| |
| 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 { |
| |
| 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 |
| |
| |
| |
| |
| public void run() { |
| ... |
| final IActivityManager am = IActivityManager.Stub.asInterface(ServiceManager.checkService("activity")); |
| try { |
| am.shutdown(MAX_BROADCAST_TIME); |
| } catch (RemoteException e) { |
| } |
| if (mRebootHasProgressBar) { |
| sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null); |
| } |
| shutdownTimingLog.traceEnd(); |
| metricEnded(METRIC_AM); |
| final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); |
| if (pm != null) { |
| pm.shutdown(); |
| } |
| if (mRebootHasProgressBar) { |
| sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null); |
| } |
| shutdownTimingLog.traceEnd(); |
| metricEnded(METRIC_PM); |
| ... |
| |
| 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) { |
| ... |
| |
| Log.i(TAG, "Performing low-level shutdown..."); |
| PowerManagerService.lowLevelShutdown(reason); |
| } |
| \frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java |
| |
| |
| |
| |
| |
| |
| public static void lowLevelShutdown(String reason) { |
| if (reason == null) { |
| reason = ""; |
| } |
| SystemProperties.set("sys.powerctl", "shutdown," + reason); |
| } |
到此先总结下以上的流程:
- 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") { |
| 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)) { |
| |
| |
| 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); |
| } |
| |
| NotifyPropertyChange(name, value); |
| #endif |
| return {PROP_SUCCESS}; |
| } |
| \system\core\init\property_service.cpp |
| void NotifyPropertyChange(const std::string& name, const std::string& value) { |
| |
| |
| 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) { |
| |
| if (!ActionManager::GetInstance().WatchingPropertyCount(name)) |
| return; |
| |
| |
| |
| |
| |
| |
| |
| |
| 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); }; |
| |
| \system\core\init\init.cpp |
| static class ShutdownState { |
| public: |
| void TriggerShutdown(const std::string& command) { |
| |
| |
| |
| |
| |
| 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) { |
| |
| |
| |
| 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") { |
| |
| |
| run_fsck = true; |
| } else if (cmd_params[1] == "thermal") { |
| |
| TurnOffBacklight(); |
| |
| cmd = ANDROID_RB_THERMOFF; |
| } |
| } |
| }else if (cmd_params[0] == "reboot") { |
| 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; |
| } |
| |
| |
| if (reboot_target == "fastboot" && |
| !android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) |
| { |
| reboot_target = "bootloader"; |
| } |
| |
| |
| 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; |
| } |
| |
| |
| 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(); |
| |
| ActionManager::GetInstance().QueueEventTrigger("shutdown"); |
| |
| 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) { |
| ... |
| |
| |
| |
| |
| 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 (!IsDataMounted("*")) { |
| sync(); |
| RebootSystem(cmd, reboot_target, reason); |
| abort(); |
| } |
| |
| bool do_shutdown_animation = GetBoolProperty("ro.init.shutdown_animation", false); |
| |
| 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())) { |
| |
| 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()) { |
| |
| 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()); |
| } |
| } |
| |
| |
| 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"); |
| |
| |
| boot_anim->Stop(); |
| } |
| |
| for (const auto& service : ServiceList::GetInstance()) { |
| if (service->classnames().count("animation") == 0) { |
| continue; |
| } |
| |
| |
| if (do_shutdown_animation) { |
| service->Start(); |
| } |
| service->SetShutdownCritical(); |
| } |
| |
| if (do_shutdown_animation) { |
| boot_anim->Start(); |
| surface_flinger->SetShutdownCritical(); |
| boot_anim->SetShutdownCritical(); |
| } |
| } |
| |
| |
| |
| |
| if (shutdown_timeout > 0ms) { |
| StopServicesAndLogViolations(stop_first, shutdown_timeout / 2, true ); |
| } |
| |
| StopServicesAndLogViolations(stop_first, 0ms, false ); |
| SubcontextTerminate(); |
| |
| ReapAnyOutstandingChildren(); |
| |
| |
| |
| Service* vold_service = ServiceList::GetInstance().FindService("vold"); |
| if (vold_service != nullptr && vold_service->IsRunning()) { |
| |
| |
| CallVdc("volume", "abort_fuse"); |
| CallVdc("volume", "shutdown"); |
| vold_service->Stop(); |
| } else { |
| LOG(INFO) << "vold not running, skipping vold shutdown"; |
| } |
| |
| |
| StopServices(kDebuggingServices, 0ms, false ); |
| |
| { |
| Timer sync_timer; |
| LOG(INFO) << "sync() before umount..."; |
| sync(); |
| LOG(INFO) << "sync() before umount took" << sync_timer; |
| } |
| |
| KillZramBackingDevice(); |
| |
| LOG(INFO) << "Ready to unmount apexes. So far shutdown sequence took " << t; |
| |
| if (auto ret = UnmountAllApexes(); !ret.ok()) { |
| LOG(ERROR) << ret.error(); |
| } |
| UmountStat stat = |
| TryUmountAndFsck(cmd, run_fsck, shutdown_timeout - t.duration(), &reboot_semaphore); |
| |
| { |
| 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); |
| |
| |
| reboot_monitor_run = false; |
| sem_post(&reboot_semaphore); |
| |
| |
| 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; |
| ... |
| |
| 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; |
| |
| 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; |
| |
| |
| } |
| } 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; |
| |
| try { |
| final IWindowManager wm = IWindowManager.Stub.asInterface( |
| ServiceManager.getService(Context.WINDOW_SERVICE)); |
| if (wm != null) { |
| wm.setEventDispatching(false); |
| } |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| } |
| |
| startBootAnimation(); |
| } |
| |
| protected void mShutdownSeqFinish(Context context) { |
| if (isRestore) { |
| isRestore = false; |
| |
| |
| |
| Settings.System.putInt(context.getContentResolver(), |
| MtkSettingsExt.System.ACCELEROMETER_ROTATION_RESTORE, 1); |
| } |
| |
| |
| |
| |
| shutdownAnimationService(); |
| |
| |
| setBacklightOff(); |
| |
| } |
可以看到MTK为了解决一个动画的bug,将自动旋转在关机的时候关闭,且专门设置了回溯的方法,但在方法中没有将系统自动旋转功能重新打开,我们在此处添上就好了。
五、小结
虽然问题本身不复杂,但是在解决问题的过程中加深了对关机流程的理解,在后续的工作中如果遇到有关关机方面的内容可以进行参考,由于本人技术水平有限,错误之处还请各位大佬斧正。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库