1、背景說明
在項(xiàng)目開發(fā)過程中,需要對(duì)開機(jī)界面進(jìn)行定制,使得產(chǎn)品界面風(fēng)格統(tǒng)一。
- 軟件版本:Android 10
- 方案供應(yīng)商:高通
- 目的:定制關(guān)機(jī)UI
系統(tǒng)原始的關(guān)機(jī)UI:
定制后的關(guān)機(jī)UI:
2、關(guān)機(jī)流程
本文定制的關(guān)機(jī)界面為長(zhǎng)按power鍵觸發(fā)的關(guān)機(jī)界面,首先我們先了解Android 10整理的關(guān)機(jī)流程,熟悉整理流程后再進(jìn)行定制開發(fā)。
關(guān)機(jī)流程涉及的代碼路徑如下
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
長(zhǎng)按power鍵關(guān)機(jī)流程時(shí)序圖如下:
2.1、Power鍵響應(yīng)
這里我們不關(guān)注驅(qū)動(dòng)及設(shè)備層如何識(shí)別及上報(bào)物理power事件,重點(diǎn)介紹android層如何攔截及響應(yīng)power key event。
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
//分發(fā)未經(jīng)處理的key,由InputManagerService調(diào)用
@Override
public 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)) {//對(duì)特殊按鍵(power、vol、home、mute等)進(jìn)行攔截處理,不分發(fā)至app
fallbackEvent.recycle();
fallbackEvent = null;
}
}
...
}
...
private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);//key加入隊(duì)列之前進(jìn)行攔截
if ((actions & ACTION_PASS_TO_USER) != 0) {
long delayMillis = interceptKeyBeforeDispatching(//分發(fā)key之前進(jìn)行攔截
win, fallbackEvent, policyFlags);
if (delayMillis == 0) {
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, mPowerKeyPressCounter);
// Any activity on the power button stops the accessibility shortcut
cancelPendingAccessibilityShortcutAction();
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (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()) {//長(zhǎng)按power鍵行為
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
powerLongPress();//處理power鍵長(zhǎng)按
} else {
...
}
}
}
...
}
private void powerLongPress() {
final int behavior = getResolvedLongPressOnPowerBehavior();//從配置文件中獲取power鍵長(zhǎng)按行為設(shè)置mLongPressOnPowerBehavior
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();
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 //關(guān)機(jī)顯示全局行為菜單
2 - Power off (with confirmation) //關(guān)機(jī)前彈出對(duì)話框再次確認(rèn)
3 - Power off (without confirmation) //關(guān)機(jī)前不彈出對(duì)話框,直接關(guān)機(jī)
4 - Go to voice assist //轉(zhuǎn)到語言助手
5 - Go to assistant (Settings.Secure.ASSISTANT) 轉(zhuǎn)到設(shè)置助手
-->
<integer name="config_longPressOnPowerBehavior">1</integer>//默認(rèn)為系統(tǒng)全局菜單
接著上面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則調(diào)用mGlobalActions.showDialog();
break;
//config_longPressOnPowerBehavior為2則調(diào)用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、顯示關(guān)機(jī)確認(rèn)框界面
這里先按源碼流程case 1分析,case 2的后續(xù)定制關(guān)機(jī)界面中會(huì)介紹。
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 不可用則顯示傳統(tǒng)關(guān)機(jī)菜單
ensureLegacyCreated();
mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
}
}
沿著傳統(tǒng)關(guān)機(jī)流程繼續(xù)分析
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) {//關(guān)機(jī)對(duì)話框已存在
mDialog.dismiss();
mDialog = null;
// Show delayed, so that the dismiss of the previous dialog completes
mHandler.sendEmptyMessage(MESSAGE_SHOW);
} else {
handleShow();
}
}
private void handleShow() {
awakenIfNecessary();
mDialog = createDialog();//創(chuàng)建新的對(duì)話框,加載關(guān)機(jī)選項(xiàng),設(shè)置點(diǎn)擊選項(xiàng)
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);//設(shè)置默認(rèn)的全局行為選項(xiàng)
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)) {//關(guān)機(jī)模式
mItems.add(new PowerAction(mContext, mWindowManagerFuncs));
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {//飛行模式
mItems.add(mAirplaneModeOn);
}
...
addedKeys.add(actionKey);//將默認(rèn)關(guān)機(jī)選項(xiàng)加入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() {
@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();//處理選項(xiàng)點(diǎn)擊事件
}
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 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>power</item> //關(guān)機(jī)
<item>restart</item> //重啟
<item>lockdown</item> //鎖屏
<item>logout</item> //注銷賬戶
<item>bugreport</item> //上報(bào)錯(cuò)誤
<item>screenshot</item> //截屏
<item>emergency</item> //緊急
</string-array>
接著PowerAction.java onLongPress()
frameworks\base\services\core\java\com\android\server\policy\PowerAction.java
@Override
public 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
@Override
public void rebootSafeMode(boolean confirm) {
// Pass in the UI context, since ShutdownThread requires it (to show UI).
ShutdownThread.rebootSafeMode(ActivityThread.currentActivityThread().getSystemUiContext(),
confirm);
}
2.3、顯示關(guān)機(jī)進(jìn)度框
進(jìn)入關(guān)機(jī)線程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)//創(chuàng)建關(guān)機(jī)確認(rèn)對(duì)話框
.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);//選擇確認(rèn)關(guān)機(jī),開始執(zhí)行關(guān)機(jī)流程
}
})
.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 play
shutdown 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);//顯示關(guān)機(jī)進(jìn)度框
...
if (SecurityLog.isLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
}
// start the thread that initiates shutdown
sInstance.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()將各自模式的關(guān)機(jī)進(jìn)度對(duì)話框顯示,直至系統(tǒng)關(guān)機(jī)。至此,定制化關(guān)機(jī)界面所需處理的流程到這里就可以結(jié)束了,但為了進(jìn)一步了解關(guān)機(jī)流程我們繼續(xù)深入follow。
在beginShutdownSequence()方法最后開啟了一個(gè)線程,執(zhí)行了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);//關(guān)機(jī)前關(guān)閉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();//關(guān)機(jī)前關(guān)閉PMS
}
// Shutdown radios.
shutdownRadios(MAX_RADIO_WAIT_TIME);//關(guān)機(jī)前關(guān)閉radios
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
}
...
// Remaining work will be done by init, including vold shutdown
rebootOrShutdown(mContext, mReboot, mReason);//進(jìn)入重啟或關(guān)機(jī)
}
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
...
final boolean shutdownAnimEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_shutdownAnimationEnabled);//是否啟用關(guān)機(jī)動(dòng)畫
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 down
Vibrator 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 power
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);//設(shè)置系統(tǒng)控制屬性sys.powerctl=shutdown
}
在設(shè)置系統(tǒng)屬性流程中對(duì)于特殊屬性值的改變需進(jìn)行監(jiān)聽,做特殊處理.
具體屬性設(shè)置流程可以參考我的另一篇文檔:Android 系統(tǒng)屬性(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,并設(shè)置關(guān)機(jī)標(biāo)志位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;//設(shè)置do shutdown關(guān)機(jī)標(biāo)志
}
...
}
int SecondStageMain(int argc, char** argv) {
...
//監(jiān)聽do_shutdown值變化,為true時(shí)調(diào)用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)) {//發(fā)送關(guān)機(jī)msg
shutting_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") {//關(guān)機(jī)
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();//息屏,關(guān)背光
// run_fsck is false to avoid delay
cmd = 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);//進(jìn)行關(guān)機(jī)
break;
...
}
// In normal case, reboot should not return.
PLOG(ERROR) << "reboot call returned";
abort();
}
reboot后續(xù)調(diào)用kernel相關(guān)進(jìn)行硬件層面的關(guān)機(jī)流程,到這里關(guān)機(jī)流程跑完了,在流程中也發(fā)現(xiàn)了息屏動(dòng)作是如何設(shè)置的。
3、定制關(guān)機(jī)界面
從上述關(guān)機(jī)流程中不難看出有多種方式實(shí)現(xiàn)關(guān)機(jī)界面的定制化,這里給出兩種方案。需要注意的是定制的關(guān)機(jī)界面實(shí)際上是兩個(gè)界面:關(guān)機(jī)關(guān)機(jī)確認(rèn)界面和關(guān)機(jī)進(jìn)度界面,所以在定制時(shí)需要將兩個(gè)界面都替換,并保持風(fēng)格統(tǒng)一。
3.1、GobalActionsMenu關(guān)機(jī)界面定制:
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);//注冊(cè)監(jiān)聽
} 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();//顯示全局關(guān)機(jī)選項(xiàng)界面
} else {
// SysUI isn't alive, show legacy menu.SysUI 不可用則顯示傳統(tǒng)關(guān)機(jī)菜單
ensureLegacyCreated();
mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
}
}
frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsImpl.java
@Override
public 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 completes
mHandler.sendEmptyMessage(MESSAGE_SHOW);
} else {
handleShow();
}
}
...
//在這里才真正實(shí)例化關(guān)機(jī)dialog并show出來,因此在這里進(jìn)行定制化
private void handleShow() {
awakenIfNecessary();
mDialog = createDialog();
//Mart!nHu Patch Start
Dialog mNewDialog = ceateNewDialog();//創(chuàng)建定制關(guān)機(jī)選擇框界面
//Mart!nHu Patch End
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 {
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();//隱藏默認(rèn)關(guān)機(jī)界面
mNewDialog.show();//顯示定制關(guān)機(jī)界面
//Mart!nHu Patch End
mWindowManagerFuncs.onGlobalActionsShown();
}
}
//Mart!nHu Patch Start
private Dialog createShutDownConfirmDialog(){
if(mShutDownConfirmDialog != null){
return mShutDownConfirmDialog;
}
mShutDownConfirmDialog = new Dialog(mContext,com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
// Window initialization
Window 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);
//設(shè)置點(diǎn)擊事件
tv_confirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mShutDownConfirmDialog.dismiss();
mWindowManagerFuncs.shutdown();
}
});
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mShutDownConfirmDialog.dismiss();
}
});
return mShutDownConfirmDialog;
}
//Mart!nHu Patch End
3.2、ShutdownThread關(guān)機(jī)界面定制
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 //關(guān)機(jī)顯示全局行為菜單
2 - Power off (with confirmation) //關(guān)機(jī)前彈出對(duì)話框再次確認(rèn)
3 - Power off (without confirmation) //關(guān)機(jī)前不彈出對(duì)話框,直接關(guān)機(jī)
4 - Go to voice assist //轉(zhuǎn)到語言助手
5 - Go to assistant (Settings.Secure.ASSISTANT) 轉(zhuǎn)到設(shè)置助手
-->
//Mart!nHu Patch Start
<integer name="config_longPressOnPowerBehavior">2</integer>//選用帶確認(rèn)的關(guān)機(jī)界面
//Mart!nHu Patch End
修改后系統(tǒng)關(guān)機(jī)界面如下:
在powerLongPress()調(diào)用mWindowManagerFuncs.shutdown()
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
private void powerLongPress() {
final int behavior = getResolvedLongPressOnPowerBehavior();//從配置文件中獲取power鍵長(zhǎng)按行為設(shè)置mLongPressOnPowerBehavior
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();
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
@Override
public 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文章來源:http://www.zghlxwxcb.cn/news/detail-402061.html
//Mart!nHu Patch Start
private static Dialog sCustomizedConfirmDialog;
//Mart!nHu Patch End
public 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();//隱藏默認(rèn)關(guān)機(jī)確認(rèn)框
if(sCustomizedConfirmDialog != null) {
sCustomizedConfirmDialog.dismiss();
sCustomizedConfirmDialog = null;
}
sCustomizedConfirmDialog = createCustomizedConfirmDialog(context);//創(chuàng)建定制關(guān)機(jī)確認(rèn)框
closer.dialog = sCustomizedConfirmDialog;
sCustomizedConfirmDialog.setOnDismissListener(closer);
sCustomizedConfirmDialog.show();//顯示定制關(guān)機(jī)界面
} else {
beginShutdownSequence(context);
}
}
private static Dialog CustomizedConfirmDialog(final Context context) {
sCustomizedConfirmDialog = new Dialog(context);
Window window = sCustomizedConfirmDialog.getWindow();
setCustomizedShutdownWindow(window);//對(duì)定制dialog所在window進(jìn)行配置
sCustomizedConfirmDialog.setContentView(com.android.internal.R.layout.shutdown_confirm_dialog);
sCustomizedConfirmDialog.setCancelable(false);//設(shè)置點(diǎn)擊空白處不關(guān)閉dialog
TextView 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() {
@Override
public void onClick(View view) {
sCustomizedConfirmDialog.dismiss();
beginShutdownSequence(context);//選擇確認(rèn)后執(zhí)行后續(xù)關(guān)機(jī)流程
}
});
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sCustomizedConfirmDialog.dismiss();
}
});
return sCustomizedConfirmDialog;
}
private static void setCustomizedShutdownWindow(Window window) {
window.requestFeature(Window.FEATURE_NO_TITLE);//去掉window標(biāo)題欄
window.getDecorView();
window.setGravity(Gravity.CENTER);//居中顯示
window.setBackgroundDrawableResource(android.R.color.transparent);//設(shè)置背景透明
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 play
shutdown 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);//顯示關(guān)機(jī)進(jìn)度框,這里也需要進(jìn)行定制
...
// start the thread that initiates shutdown
sInstance.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 Start
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);
sCustomizedShuttingDownDialog = createCustomizedShuttingDownDialog(context);//創(chuàng)建定制關(guān)機(jī)進(jìn)度框
}
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);
//定制關(guān)機(jī)動(dòng)畫
AnimatedImageDrawable shuttingDownGif = (AnimatedImageDrawable) context.getDrawable(com.android.internal.R.drawable.shutting_down);
shuttingDownImage.setImageDrawable(shuttingDownGif);
shuttingDownGif.start();//播放關(guān)機(jī)動(dòng)畫
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、總結(jié)
本文從代碼流程角度,大致的梳理了系統(tǒng)power鍵關(guān)機(jī)從framework開始的后續(xù)流程,通過代碼流程了解到:文章來源地址http://www.zghlxwxcb.cn/news/detail-402061.html
- 系統(tǒng)默認(rèn)關(guān)機(jī)界面有多種樣式及模式
- 關(guān)機(jī)界面加載關(guān)機(jī)選項(xiàng)的實(shí)現(xiàn)過程
- 通過設(shè)置sys.powerctl=shutdown,init進(jìn)程循環(huán)監(jiān)聽該屬性變化并觸發(fā)shutdown流程
- 定制關(guān)機(jī)界面通常需要定制關(guān)機(jī)確認(rèn)界面以及關(guān)機(jī)進(jìn)度條界面
到了這里,關(guān)于Android 10關(guān)機(jī)界面定制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!