1、背景说明

在项目开发过程中,需要对开机界面进行定制,使得产品界面风格统一。

  • 软件版本:Android 10
  • 方案供应商:高通
  • 目的:定制关机UI
    系统原始的关机UI:

    定制后的关机UI:

2、关机流程

本文定制的关机界面为长按power键触发的关机界面,首先我们先了解Android 10整理的关机流程,熟悉整理流程后再进行定制开发。
关机流程涉及的代码路径如下

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\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsImpl.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\policy\WindowManagerPolicy.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\am\ActivityManagerService.java
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.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

长按power键关机流程时序图如下:

2.1、Power键响应

这里我们不关注驱动及设备层如何识别及上报物理power事件,重点介绍android层如何拦截及响应power key event。
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

//分发未经处理的key,由InputManagerService调用@Overridepublic KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {// Note: This method is only called if the initial down was unhandled.KeyEvent fallbackEvent = null;if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {final KeyCharacterMap kcm = event.getKeyCharacterMap();final int keyCode = event.getKeyCode();final int metaState = event.getMetaState();final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN&& event.getRepeatCount() == 0;// Check for fallback actions specified by the key character map.final FallbackAction fallbackAction;if (initialDown) {if (!interceptFallback(win, fallbackEvent, policyFlags)) {//对特殊按键(power、vol、home、mute等)进行拦截处理,不分发至appfallbackEvent.recycle();fallbackEvent = null;}}...}...private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);//key加入队列之前进行拦截if ((actions & ACTION_PASS_TO_USER) != 0) {long delayMillis = interceptKeyBeforeDispatching(//分发key之前进行拦截win, fallbackEvent, policyFlags);if (delayMillis == 0) {return true;}}return false;}@Overridepublic int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {// Handle special keys....switch (keyCode) {...case KeyEvent.KEYCODE_POWER: {EventLogTags.writeInterceptPower(KeyEvent.actionToString(event.getAction()),mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter);// Any activity on the power button stops the accessibility shortcutcancelPendingAccessibilityShortcutAction();result &= ~ACTION_PASS_TO_USER;isWakeKey = false; // wake-up will be handled separatelyif (down) {interceptPowerKeyDown(event, interactive);//拦截power键down 事件} else {interceptPowerKeyUp(event, interactive, canceled);//拦截power键up 事件}break;}}...}...private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {...// If the power key has still not yet been handled, then detect short// press, long press, or multi press and decide what to do.mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered|| mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted;if (!mPowerKeyHandled) {if (interactive) {// When interactive, we're already awake.// Wait for a long press or for the button to be released to decide what to do.if (hasLongPressOnPowerBehavior()) {//长按power键行为if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {powerLongPress();//处理power键长按} else {...}}}...}private void powerLongPress() {final int behavior = getResolvedLongPressOnPowerBehavior();//从配置文件中获取power键长按行为设置mLongPressOnPowerBehaviorswitch (behavior) {case LONG_PRESS_POWER_NOTHING:break;case LONG_PRESS_POWER_GLOBAL_ACTIONS:mPowerKeyHandled = true;performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,"Power - Long Press - Global Actions");showGlobalActionsInternal();break;case LONG_PRESS_POWER_SHUT_OFF:case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:mPowerKeyHandled = true;performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,"Power - Long Press - Shut Off");sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);break;...}private int getResolvedLongPressOnPowerBehavior() {if (FactoryTest.isLongPressOnPowerOffEnabled()) {return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;}return mLongPressOnPowerBehavior;}mLongPressOnPowerBehavior = mContext.getResources().getInteger(com.android.internal.R.integer.config_longPressOnPowerBehavior);

frameworks\base\core\res\res\values\config.xml

    <!-- Control the behavior when the user long presses the power button.0 - Nothing                                             //不处理power键,即什么也不做1 - Global actions menu                            //关机显示全局行为菜单2 - Power off (with confirmation)               //关机前弹出对话框再次确认3 - Power off (without confirmation)      //关机前不弹出对话框,直接关机4 - Go to voice assist                               //转到语言助手5 - Go to assistant (Settings.Secure.ASSISTANT) 转到设置助手--><integer name="config_longPressOnPowerBehavior">1</integer>//默认为系统全局菜单

接着上面powerLongPress()往下走
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

private void powerLongPress() {final int behavior = getResolvedLongPressOnPowerBehavior();switch (behavior) {case LONG_PRESS_POWER_NOTHING:break;case LONG_PRESS_POWER_GLOBAL_ACTIONS:mPowerKeyHandled = true;performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,"Power - Long Press - Global Actions");showGlobalActionsInternal();//config_longPressOnPowerBehavior为1则调用mGlobalActions.showDialog();break;//config_longPressOnPowerBehavior为2则调用mWindowManagerFuncs.shutdown();case LONG_PRESS_POWER_SHUT_OFF:case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:mPowerKeyHandled = true;performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,"Power - Long Press - Shut Off");sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);break;...}void showGlobalActionsInternal() {if (mGlobalActions == null) {mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);}final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());// 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);}

2.2、显示关机确认框界面

这里先按源码流程case 1分析,case 2的后续定制关机界面中会介绍。
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.SysUI 不可用则显示传统关机菜单ensureLegacyCreated();mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);}}

沿着传统关机流程继续分析
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;// Show delayed, so that the dismiss of the previous dialog completesmHandler.sendEmptyMessage(MESSAGE_SHOW);} else {handleShow();}}private void handleShow() {awakenIfNecessary();mDialog = createDialog();//创建新的对话框,加载关机选项,设置点击选项prepareDialog();//更新静音、飞行等各种模式...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);}}}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));} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {//飞行模式mItems.add(mAirplaneModeOn);}...addedKeys.add(actionKey);//将默认关机选项加入global action menu}...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() {@Overridepublic 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;}

全局行为模式定义如下:
frameworks\base\core\res\res\values\config.xml

<!-- Defines the default set of global actions. Actions may still be disabled or hidden basedon 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>power</item>             //关机<item>restart</item>        //重启<item>lockdown</item>       //锁屏<item>logout</item>         //注销账户<item>bugreport</item>        //上报错误<item>screenshot</item>       //截屏<item>emergency</item>      //紧急</string-array>

接着PowerAction.java onLongPress()
frameworks\base\services\core\java\com\android\server\policy\PowerAction.java

    @Overridepublic boolean onLongPress() {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

@Overridepublic void rebootSafeMode(boolean confirm) {// Pass in the UI context, since ShutdownThread requires it (to show UI).ShutdownThread.rebootSafeMode(ActivityThread.currentActivityThread().getSystemUiContext(),confirm);}

2.3、显示关机进度框

进入关机线程ShutdownThread
frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java

public static void rebootSafeMode(final Context context, boolean confirm) {...shutdownInner(context, confirm);}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);}}private static void beginShutdownSequence(Context context) {.../* If shutdown animation enabled, notify bootanimation module to playshutdown animation by set prop */final boolean shutdownAnimationEnabled = context.getResources().getBoolean(com.android.internal.R.bool.config_shutdownAnimationEnabled);if (shutdownAnimationEnabled) {SystemProperties.set("sys.powerctl", "shutdownanim");SystemProperties.set("service.bootanim.exit", "0");SystemProperties.set("ctl.start", "bootanim");}sInstance.mProgressDialog = showShutdownDialog(context);//显示关机进度框...if (SecurityLog.isLoggingEnabled()) {SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);}// start the thread that initiates shutdownsInstance.mHandler = new Handler() {};sInstance.start();}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 {// 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;}

pd.show()将各自模式的关机进度对话框显示,直至系统关机。至此,定制化关机界面所需处理的流程到这里就可以结束了,但为了进一步了解关机流程我们继续深入follow。
在beginShutdownSequence()方法最后开启了一个线程,执行了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"));if (am != null) {try {am.shutdown(MAX_BROADCAST_TIME);//关机前关闭AMS} catch (RemoteException e) {}}if (mRebootHasProgressBar) {sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);}...final PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");if (pm != null) {pm.shutdown();//关机前关闭PMS}// Shutdown radios.shutdownRadios(MAX_RADIO_WAIT_TIME);//关机前关闭radiosif (mRebootHasProgressBar) {sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);}...// Remaining work will be done by init, including vold shutdownrebootOrShutdown(mContext, mReboot, mReason);//进入重启或关机}public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {...           final boolean shutdownAnimEnabled = context.getResources().getBoolean(com.android.internal.R.bool.config_shutdownAnimationEnabled);//是否启用关机动画if (shutdownAnimEnabled) {final int shutdownAnimDuration = context.getResources().getInteger(com.android.internal.R.integer.config_shutdownAnimationDurationMs);int sleepDuration = reboot ? shutdownAnimDuration: shutdownAnimDuration - SHUTDOWN_VIBRATE_MS;try {if (sleepDuration > 0) {Thread.sleep(sleepDuration);}} catch (InterruptedException unused) {}}if (reboot) {Log.i(TAG, "Rebooting, reason: " + reason);PowerManagerService.lowLevelReboot(reason);//reboot重启流程Log.e(TAG, "Reboot failed, will attempt shutdown instead");reason = null;} else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {// vibrate before shutting downVibrator vibrator = new SystemVibrator(context);try {vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);} catch (Exception e) {// Failure to vibrate shouldn't interrupt shutdown.  Just log it.Log.w(TAG, "Failed to vibrate during shutdown.", e);}// Shutdown powerLog.i(TAG, "Performing low-level shutdown...");PowerManagerService.lowLevelShutdown(reason);}

frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java

public static void lowLevelShutdown(String reason) {if (reason == null) {reason = "";}SystemProperties.set("sys.powerctl", "shutdown," + reason);//设置系统控制属性sys.powerctl=shutdown}

在设置系统属性流程中对于特殊属性值的改变需进行监听,做特殊处理.
具体属性设置流程可以参考我的另一篇文档:Android 系统属性(SystemProperties)介绍
system\core\init\property_service.cpp

static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {...property_changed(name, value);return PROP_SUCCESS;
}

property_changed()中获取sys.powerctl值传给shutdown_command,并设置关机标志位do_shutdown为true
system\core\init\init.cpp

void property_changed(const std::string& name, const std::string& value) {// 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") {// Despite the above comment, we can't call HandlePowerctlMessage() 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.// TODO: once property service is removed from init, this will never happen from a builtin,// but rather from a callback from the property service socket, in which case this hack can// go away.shutdown_command = value;do_shutdown = true;//设置do shutdown关机标志}...
}int SecondStageMain(int argc, char** argv) {...//监听do_shutdown值变化,为true时调用HandlePowerctlMessage()while (true) {// By default, sleep until something happens.auto epoll_timeout = std::optional<std::chrono::milliseconds>{};if (do_shutdown && !shutting_down) {do_shutdown = false;if (HandlePowerctlMessage(shutdown_command)) {//发送关机msgshutting_down = true;}}...}...
}

system\core\init\reboot.cpp

bool HandlePowerctlMessage(const std::string& command) {...if (cmd_params.size() > 3) {command_invalid = true;} else 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 delaycmd = ANDROID_RB_THERMOFF;}}} else if (cmd_params[0] == "reboot") {//重启cmd = ANDROID_RB_RESTART2;if (cmd_params.size() >= 2) {reboot_target = cmd_params[1];// 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....auto shutdown_handler = [cmd, command, reboot_target, run_fsck](const BuiltinArguments&) {DoReboot(cmd, command, reboot_target, run_fsck);return Success();};...return true;
}

system\core\init\reboot_utils.cpp

void __attribute__((noreturn)) RebootSystem(unsigned int cmd, const std::string& rebootTarget) {LOG(INFO) << "Reboot ending, jumping to kernel";if (!IsRebootCapable()) {// On systems where init does not have the capability of rebooting the// device, just exit cleanly.exit(0);}switch (cmd) {case ANDROID_RB_POWEROFF:reboot(RB_POWER_OFF);//进行关机break;...}// In normal case, reboot should not return.PLOG(ERROR) << "reboot call returned";abort();
}

reboot后续调用kernel相关进行硬件层面的关机流程,到这里关机流程跑完了,在流程中也发现了息屏动作是如何设置的。

3、定制关机界面

从上述关机流程中不难看出有多种方式实现关机界面的定制化,这里给出两种方案。需要注意的是定制的关机界面实际上是两个界面:关机关机确认界面和关机进度界面,所以在定制时需要将两个界面都替换,并保持风格统一。

3.1、GobalActionsMenu关机界面定制:

frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java

public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {mContext = context;mHandler = new Handler();mWindowManagerFuncs = windowManagerFuncs;mGlobalActionsProvider = LocalServices.getService(GlobalActionsProvider.class);if (mGlobalActionsProvider != null) {mGlobalActionsProvider.setGlobalActionsListener(this);//注册监听} else {Slog.i(TAG, "No GlobalActionsProvider found, defaulting to LegacyGlobalActions");}}...public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {...mKeyguardShowing = keyguardShowing;mDeviceProvisioned = deviceProvisioned;mShowing = true;if (mGlobalActionsAvailable) {//全局行为可使用mHandler.postDelayed(mShowTimeout, 5000);mGlobalActionsProvider.showGlobalActions();//显示全局关机选项界面} else {// SysUI isn't alive, show legacy menu.SysUI 不可用则显示传统关机菜单ensureLegacyCreated();mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);}}

frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsImpl.java

@Overridepublic void showGlobalActions(GlobalActionsManager manager) {if (mDisabled) return;if (mGlobalActions == null) {mGlobalActions = new GlobalActionsDialog(mContext, manager);}mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),mDeviceProvisionedController.isDeviceProvisioned(),mPanelExtension.get());KeyguardUpdateMonitor.getInstance(mContext).requestFaceAuth();}

frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsDialog.java

   public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned,GlobalActionsPanelPlugin panelPlugin) {mKeyguardShowing = keyguardShowing;mDeviceProvisioned = isDeviceProvisioned;mPanelPlugin = panelPlugin;if (mDialog != null) {mDialog.dismiss();mDialog = null;// Show delayed, so that the dismiss of the previous dialog completesmHandler.sendEmptyMessage(MESSAGE_SHOW);} else {handleShow();}}...//在这里才真正实例化关机dialog并show出来,因此在这里进行定制化private void handleShow() {awakenIfNecessary();mDialog = createDialog();//Mart!nHu Patch StartDialog mNewDialog = ceateNewDialog();//创建定制关机选择框界面//Mart!nHu Patch EndprepareDialog();// 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 {WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();attrs.setTitle("ActionsDialog");attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;mDialog.getWindow().setAttributes(attrs);//Mart!nHu Patch Start//mDialog.show();//隐藏默认关机界面mNewDialog.show();//显示定制关机界面//Mart!nHu Patch EndmWindowManagerFuncs.onGlobalActionsShown();}}
//Mart!nHu Patch Startprivate Dialog createShutDownConfirmDialog(){if(mShutDownConfirmDialog != null){return mShutDownConfirmDialog;}mShutDownConfirmDialog = new Dialog(mContext,com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);// Window initializationWindow window = mShutDownConfirmDialog.getWindow();window.requestFeature(Window.FEATURE_NO_TITLE);// Inflate the decor view, so the attributes below are not overwritten by the theme.window.getDecorView();window.setGravity(Gravity.CENTER);window.setBackgroundDrawableResource(android.R.color.transparent);window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LAYOUT_STABLE| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;window.setLayout(WRAP_CONTENT, WRAP_CONTENT);window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);mShutDownConfirmDialog.setContentView(com.android.systemui.R.layout.shutdown_confirm_dialog);//使用定制布局TextView tv_confirm = mShutDownConfirmDialog.findViewById(com.android.systemui.R.id.btn_confirm);TextView tv_cancel = mShutDownConfirmDialog.findViewById(com.android.systemui.R.id.btn_cancel);//设置点击事件tv_confirm.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {mShutDownConfirmDialog.dismiss();mWindowManagerFuncs.shutdown();}});tv_cancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {mShutDownConfirmDialog.dismiss();}});return mShutDownConfirmDialog;}
//Mart!nHu Patch End

3.2、ShutdownThread关机界面定制

frameworks\base\core\res\res\values\config.xml

    <!-- Control the behavior when the user long presses the power button.0 - Nothing                                             //不处理power键,即什么也不做1 - Global actions menu                            //关机显示全局行为菜单2 - Power off (with confirmation)               //关机前弹出对话框再次确认3 - Power off (without confirmation)      //关机前不弹出对话框,直接关机4 - Go to voice assist                               //转到语言助手5 - Go to assistant (Settings.Secure.ASSISTANT) 转到设置助手-->//Mart!nHu Patch Start  <integer name="config_longPressOnPowerBehavior">2</integer>//选用带确认的关机界面//Mart!nHu Patch End

修改后系统关机界面如下:

在powerLongPress()调用mWindowManagerFuncs.shutdown()
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

private void powerLongPress() {final int behavior = getResolvedLongPressOnPowerBehavior();//从配置文件中获取power键长按行为设置mLongPressOnPowerBehaviorswitch (behavior) {case LONG_PRESS_POWER_NOTHING:break;case LONG_PRESS_POWER_GLOBAL_ACTIONS:mPowerKeyHandled = true;performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,"Power - Long Press - Global Actions");showGlobalActionsInternal();break;case LONG_PRESS_POWER_SHUT_OFF:case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:mPowerKeyHandled = true;mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);break;...}

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

@Overridepublic void shutdown(boolean confirm) {// Pass in the UI context, since ShutdownThread requires it (to show UI).ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),PowerManager.SHUTDOWN_USER_REQUESTED, confirm);}

frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java

//Mart!nHu Patch Start
private static Dialog sCustomizedConfirmDialog;
//Mart!nHu Patch Endpublic static void shutdown(final Context context, String reason, boolean confirm) {mReboot = false;mRebootSafeMode = false;mReason = reason;shutdownInner(context, confirm);}private static void shutdownInner(final Context context, boolean confirm) {...final int longPressBehavior = context.getResources().getInteger(com.android.internal.R.integer.config_longPressOnPowerBehavior);final int resourceId = mRebootSafeMode? com.android.internal.R.string.reboot_safemode_confirm: (longPressBehavior == 2? com.android.internal.R.string.shutdown_confirm_question: com.android.internal.R.string.shutdown_confirm);Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);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);//Mart!nHu Patch Start//sConfirmDialog.show();//隐藏默认关机确认框if(sCustomizedConfirmDialog != null) {sCustomizedConfirmDialog.dismiss();sCustomizedConfirmDialog = null;}sCustomizedConfirmDialog = createCustomizedConfirmDialog(context);//创建定制关机确认框closer.dialog = sCustomizedConfirmDialog;sCustomizedConfirmDialog.setOnDismissListener(closer);sCustomizedConfirmDialog.show();//显示定制关机界面} else {beginShutdownSequence(context);}}private static Dialog CustomizedConfirmDialog(final Context context) {sCustomizedConfirmDialog = new Dialog(context);Window window = sCustomizedConfirmDialog.getWindow();setCustomizedShutdownWindow(window);//对定制dialog所在window进行配置sCustomizedConfirmDialog.setContentView(com.android.internal.R.layout.shutdown_confirm_dialog);sCustomizedConfirmDialog.setCancelable(false);//设置点击空白处不关闭dialogTextView shutdownConfirmMsg = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.tv_shutdown_confirm_msg);TextView tv_confirm = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.btn_confirm);TextView tv_cancel = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.btn_cancel);shutdownConfirmMsg.setText(com.android.internal.R.string.shutdown_confirm_question);tv_confirm.setText(com.android.internal.R.string.yes);tv_cancel.setText(com.android.internal.R.string.no);tv_confirm.setOnClickListener(new View.OnClickListener() { @Overridepublic void onClick(View view) {sCustomizedConfirmDialog.dismiss();beginShutdownSequence(context);//选择确认后执行后续关机流程}});tv_cancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {sCustomizedConfirmDialog.dismiss();}});return sCustomizedConfirmDialog;}private static void setCustomizedShutdownWindow(Window window) {window.requestFeature(Window.FEATURE_NO_TITLE);//去掉window标题栏window.getDecorView();window.setGravity(Gravity.CENTER);//居中显示window.setBackgroundDrawableResource(android.R.color.transparent);//设置背景透明window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LAYOUT_STABLE| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;window.setLayout(WRAP_CONTENT, WRAP_CONTENT);window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);}private static void beginShutdownSequence(Context context) {.../* If shutdown animation enabled, notify bootanimation module to playshutdown animation by set prop */final boolean shutdownAnimationEnabled = context.getResources().getBoolean(com.android.internal.R.bool.config_shutdownAnimationEnabled);if (shutdownAnimationEnabled) {SystemProperties.set("sys.powerctl", "shutdownanim");SystemProperties.set("service.bootanim.exit", "0");SystemProperties.set("ctl.start", "bootanim");}sInstance.mProgressDialog = showShutdownDialog(context);//显示关机进度框,这里也需要进行定制...// start the thread that initiates shutdownsInstance.mHandler = new Handler() {};sInstance.start();}private static ProgressDialog showShutdownDialog(Context context) {// Throw up a system dialog to indicate the device is rebooting / shutting down.ProgressDialog pd = new ProgressDialog(context);...} else {if (showSysuiReboot()) {return null;}//Mart!nHu Patch Startpd.setTitle(context.getText(com.android.internal.R.string.power_off));pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));pd.setIndeterminate(true);sCustomizedShuttingDownDialog = createCustomizedShuttingDownDialog(context);//创建定制关机进度框}pd.setCancelable(false);pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);if(sCustomizedShuttingDownDialog != null) {sCustomizedShuttingDownDialog.show();} else {pd.show();}return pd;}private static Dialog createCustomizedShuttingDownDialog(final Context context) {if(sCustomizedShuttingDownDialog != null) {return sCustomizedShuttingDownDialog;}sCustomizedShuttingDownDialog = new Dialog(context);Window window = sCustomizedShuttingDownDialog.getWindow();setCustomizedShutdownWindow(window);sCustomizedShuttingDownDialog.setContentView(com.android.internal.R.layout.shuttingdown_dialog);sCustomizedShuttingDownDialog.setCancelable(false);ImageView shuttingDownImage = sCustomizedShuttingDownDialog.findViewById(com.android.internal.R.id.tv_shutting_down);//定制关机动画AnimatedImageDrawable shuttingDownGif = (AnimatedImageDrawable) context.getDrawable(com.android.internal.R.drawable.shutting_down);shuttingDownImage.setImageDrawable(shuttingDownGif);shuttingDownGif.start();//播放关机动画TextView message = sCustomizedShuttingDownDialog.findViewById(com.android.internal.R.id.tv_shutting_down_msg);message.setText(com.android.internal.R.string.shutdown_progress);return sCustomizedShuttingDownDialog;}//Mart!nHu Patch End

4、总结

本文从代码流程角度,大致的梳理了系统power键关机从framework开始的后续流程,通过代码流程了解到:

  • 系统默认关机界面有多种样式及模式
  • 关机界面加载关机选项的实现过程
  • 通过设置sys.powerctl=shutdown,init进程循环监听该属性变化并触发shutdown流程
  • 定制关机界面通常需要定制关机确认界面以及关机进度条界面

Android 10关机界面定制相关推荐

  1. android 自定义关机界面,怎么定制Android关机界面

    在Android系统中,长按Power键默认会弹出对话框让你选择"飞行模式","静音","关机"等功能.这些功能对于手机非常适用,但是对于机 ...

  2. android 自定义关机界面,android源码探索之定制android关机界面的方法

    本文实例讲述了android源码探索之定制android关机界面的方法.分享给大家供大家参考.具体如下: 在Android系统中,长按Power键默认会弹出对话框让你选择"飞行模式" ...

  3. android 10小米界面,小米MIUI 10界面提前曝光 升级机型列表曝光

    [TechWeb报道]小米将在5月31日在深圳举办年度新品发布会,在这次发布会上小米除了带来全新旗舰小米8外,还会在会上正式发布MIUI 10.今天MIUI 10的界面得到了曝光,风格和刚刚发布不久的 ...

  4. android8关机界面,修改android 的关机界面

    这里主要是修改,系统调用关机时候的代码,已达到自己想要的结果. 长按POWER键,将会关机,弹出"设备将要关机"选择对话框.如果可以选择"是"关机,和" ...

  5. 最新android 手机型号,各大安卓手机厂商Android 10系统更新汇总,你的机型支持吗?...

    原标题:各大安卓手机厂商Android 10系统更新汇总,你的机型支持吗? 对于安卓手机来说系统更新是一件非常好的事情,这意味着手机新增很多功能,同时新系统也能够给用户带来更好的体验.对于谷歌今年9月 ...

  6. Android 10.0 关机对话框UI定制化开发(二)

    1.概述 在10.0的定制化开发中,需要对关机对话框的UI界面进行定制化开发,需要对话框全屏,去掉多余项保留关机 重启 飞行模式 静音模式等选项 现在开始定制化二的开发 实现关机 重启 飞行模式 静音 ...

  7. Android 10.0 关机对话框UI定制化开发(一)

    目录 1.概述 2.关机对话框UI定制的核心功能 1.概述 在10.0的定制化开发中,需要对关机对话框的UI界面进行定制化开发,需要对话框全屏,去掉多余项 保留关机 重启 飞行模式 静音模式等选项 现 ...

  8. Android 12.0关机界面全屏显示(UI全屏显示)

    1.概述 在12.0的系统定制化开发中,原生系统关机界面 UI是靠右边显示的,但是客户需求要求全屏显示 重启和关机功能键居中显示,所以就涉及到调整UI 然后全屏显示,需要实现窗口的全局布局实现全屏功能 ...

  9. android 6.0在关机界面添加截图功能

    在关机界面添加截图功能,可以参考如下步骤修改实现: 1.步骤 1.1 <string name="global_action_screenshot_xxxx">scre ...

  10. android仿iphone页面,Android仿苹果关机界面实现代码

    本文实例为大家分享了Android仿苹果关机界面的具体代码,供大家参考,具体内容如下 主class 用来控制viewdialog的显示 package com.android.server.polic ...

最新文章

  1. ***如何优雅的选择字体(font-family)
  2. GAN和PS合体会怎样?东京大学图像增强新研究:无需配对图像,增强效果还可解释...
  3. php7 数据库查询结果,php如何获取数据库查询结果
  4. echart多个柱状图 设置y轴显示_Origin神教程:柱状图还是2D的吗?也没有误差棒?...
  5. SpringCloud教程- 路由网关Zuul (SpringCloud版本Greenwich.SR4)
  6. XMNetworking 网络库的设计与使用
  7. 计算机组成原理r型指令logisim实现_大学本科计算机科学与技术专业知识体系
  8. 制作漫画风图片(无需下载APP无需PS无需电脑)
  9. 物联网技术,主要应用于哪十大行业
  10. Haystack全文搜索
  11. mysql 判断是数据类型_mysql数据类型判断
  12. 将Imagenet2012比赛数据解析为图像
  13. 猫哥教你写爬虫 034--爬虫-BeautifulSoup实践
  14. 使用 Nginx 反向代理域名
  15. 从键盘输入一个英文字母,进行大小写字母转换,并输出。
  16. python语法详解_关于python:NLTK中解析的英语语法
  17. 中国叶酒市场趋势报告、技术动态创新及市场预测
  18. 软件测试工程师职业发展漫谈
  19. 人事管理系统hrm的总结
  20. 笔记本计算机接口类型,小白入门必看!笔记本电脑常见接口都在这了

热门文章

  1. 教你如何删除顽固文件
  2. 服务器有时候显示美国,美国服务器不通的情况解决方法
  3. 手写个Tomcat雏型
  4. dotnet 使用 Obsolete 特性标记成员过时保持库和框架的兼容性
  5. 你真的懂Java的ArrayList吗?
  6. SpringMVC(07) -- RESTful
  7. python爬取网页的内层页_python爬取网页 下一页
  8. eclipse mars2 安装web插件
  9. Wamp5出现的问题
  10. 2021-04-17 ffmpeg视频合并报错;视频合并中间添加空白