今天记录下跟踪的Android O版本,power键按下流程,包括关机,锁屏,亮屏流程,没有太多新的扩展,只是做了源码流程的记录分析,若有异议,欢迎提出,下面开始
##power按键传递
对于按下power按键,在fwk中首先会传递到PhoneWindowManager#dispatchUnhandledKey
@Override
public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {....if (!interceptFallback(win, fallbackEvent, policyFlags)) {fallbackEvent.recycle();fallbackEvent = null;}
}
继续看PhoneWindowManager#interceptFallback
private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);if ((actions & ACTION_PASS_TO_USER) != 0) {long delayMillis = interceptKeyBeforeDispatching(win, fallbackEvent, policyFlags);if (delayMillis == 0) {return true;}}return false;
}
可以看到上面的interceptKeyBeforeQueueing方法的返回值决定了是否由应用fwk自己处理power按键
由于Power Key默认都是由系统的Framework来响应,如果想让自己的应用捕获响应,可以在这里做具体的处理
##power按键处理
有了上面的分析,可以知道,power按键按下和抬起都是在interceptKeyBeforeQueueing中处理的
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {switch (keyCode) {....case KeyEvent.KEYCODE_POWER: {// 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);} else {interceptPowerKeyUp(event, interactive, canceled);}break;}....}return result;
}
###power按下流程
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {// Hold a wake lock until the power key is released.if (!mPowerKeyWakeLock.isHeld()) {mPowerKeyWakeLock.acquire();}// Cancel multi-press detection timeout.if (mPowerKeyPressCounter != 0) {mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);}// 是否符合截屏条件if (interactive && !mScreenshotChordPowerKeyTriggered&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {mScreenshotChordPowerKeyTriggered = true;mScreenshotChordPowerKeyTime = event.getDownTime();interceptScreenshotChord();}// 停止tele相关的服务操作,比如打电话TelecomManager telecomManager = getTelecommService();boolean hungUp = false;if (telecomManager != null) {if (telecomManager.isRinging()) {// Pressing Power while there's a ringing incoming// call should silence the ringer.telecomManager.silenceRinger();} else if ((mIncallPowerBehavior& Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0&& telecomManager.isInCall() && interactive) {// Otherwise, if "Power button ends call" is enabled,// the Power button will hang up any current active call.hungUp = telecomManager.endCall();}}if (!mPowerKeyHandled) {// interactive表示当前是否亮屏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按键Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);msg.setAsynchronous(true);mHandler.sendMessageDelayed(msg,ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());}} else {// 唤醒系统wakeUpFromPowerKey(event.getDownTime());// 下面处理灭屏幕时候长按power按键处理if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) {Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);msg.setAsynchronous(true);mHandler.sendMessageDelayed(msg,ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());mBeganFromNonInteractive = true;} else {final int maxCount = getMaxMultiPressPowerCount();if (maxCount <= 1) {mPowerKeyHandled = true;} else {mBeganFromNonInteractive = true;}}}}}
以上,需要说明的是,不同OEM厂商可能定制的power按钮事件不同,有的亮屏的时候,按下power就熄屏了,有的是在抬起时候熄屏的,这里我们就就以AOSP为参考来分析吧
可以看出,原生的代码,对于按下power只有满足指定条件截屏的处理,这里那如果在亮屏期间只是单纯按了power键,肯定是会锁屏的,所以这里的锁屏就是在抬起power键时候触发的
###亮屏抬起power按键
private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {final boolean handled = canceled || mPowerKeyHandled;mScreenshotChordPowerKeyTriggered = false;cancelPendingScreenshotChordAction();cancelPendingPowerKeyAction();if (!handled) {// Figure out how to handle the key now that it has been released.mPowerKeyPressCounter += 1;final int maxCount = getMaxMultiPressPowerCount();final long eventTime = event.getDownTime();if (mPowerKeyPressCounter < maxCount) { // 处理频繁多次按下抬起power按键// This could be a multi-press. Wait a little bit longer to confirm.// Continue holding the wake lock.Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);msg.setAsynchronous(true);mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout());return;}// 这里会具体处理,当前来说就是灭屏操作powerPress(eventTime, interactive, mPowerKeyPressCounter);}// Done. Reset our state.finishPowerKeyPress();
}
private void powerPress(long eventTime, boolean interactive, int count) {....if (interactive && !mBeganFromNonInteractive) {switch (mShortPressOnPowerBehavior) {case SHORT_PRESS_POWER_NOTHING:break;case SHORT_PRESS_POWER_GO_TO_SLEEP:goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);break;}....}
上述抬起行为和mShortPressOnPowerBehavior的配置有关,默认也是1,即SHORT_PRESS_POWER_GO_TO_SLEEP
mShortPressOnPowerBehavior = mContext.getResources().getInteger(com.android.internal.R.integer.config_shortPressOnPowerBehavior);
private void goToSleep(long eventTime, int reason, int flags) {mRequestedOrGoingToSleep = true;mPowerManager.goToSleep(eventTime, reason, flags);
}
@Override // Binder call
public void goToSleep(long eventTime, int reason, int flags) {if (eventTime > SystemClock.uptimeMillis()) {throw new IllegalArgumentException("event time must not be in the future");}mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);final int uid = Binder.getCallingUid();final long ident = Binder.clearCallingIdentity();try {goToSleepInternal(eventTime, reason, flags, uid);} finally {Binder.restoreCallingIdentity(ident);}
}
到此为止,具体处理锁屏的操作是PowerManagerService#goToSleep处理的
##按下power按键,亮屏处理
继续回到interceptPowerKeyDown方法,灭屏时候,按下power键触发亮屏的流程
private void wakeUpFromPowerKey(long eventTime) {wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey, "android.policy:POWER");}private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, String reason) {final boolean theaterModeEnabled = isTheaterModeEnabled();if (!wakeInTheaterMode && theaterModeEnabled) {return false;}if (theaterModeEnabled) {Settings.Global.putInt(mContext.getContentResolver(),Settings.Global.THEATER_MODE_ON, 0);}mPowerManager.wakeUp(wakeTime, reason);return true;}
##亮屏长按power弹出关机界面
上面在interceptPowerKeyDown方法中,可以看到,若当前亮屏,长按power,会发送MSG_POWER_LONG_PRESS消息
private class PolicyHandler extends Handler {@Overridepublic void handleMessage(Message msg) {case MSG_POWER_LONG_PRESS:powerLongPress();break;} }
继续看powerLongPress方法
private void powerLongPress() {final int behavior = getResolvedLongPressOnPowerBehavior();switch (behavior) {case LONG_PRESS_POWER_NOTHING:break;case LONG_PRESS_POWER_GLOBAL_ACTIONS: // 正常用户长按power的消息,会弹出确认框mPowerKeyHandled = true;performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);showGlobalActionsInternal();break;case LONG_PRESS_POWER_SHUT_OFF: case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: // LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM是对工厂模式测试的处理mPowerKeyHandled = true;performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF); break;}
}
上面的分析可以看出,用户直接长按power按键的处理行为和behavior的值有关系
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);
所以默认的处理行为和config_longPressOnPowerBehavior的配置有关系
/frameworks/base/core/res/res/values/config.xml中默认配置的是1,也即是LONG_PRESS_POWER_GLOBAL_ACTIONS消息会弹出确认框,
这里若是LONG_PRESS_POWER_SHUT_OFF和LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM会直接关机处理,所以如果有这样的需求,可以直接配置config_longPressOnPowerBehavior默认值为2即可
继续分析LONG_PRESS_POWER_GLOBAL_ACTIONS
PhoneWindowManager#showGlobalActionsInternal
void showGlobalActionsInternal() {sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);if (mGlobalActions == null) {mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);}final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());if (keyguardShowing) {// 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);}
}
GlobalActions#showDialog
public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);mKeyguardShowing = keyguardShowing;mDeviceProvisioned = deviceProvisioned;mShowing = true;if (mStatusBarConnected) {mStatusBarInternal.showGlobalActions();mHandler.postDelayed(mShowTimeout, 5000);} else {// SysUI isn't alive, show legacy menu.ensureLegacyCreated();mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);}
}
上述GlobalActions#showDialog方法中,不管if条件分支是哪一个,都会到LegacyGlobalActions#showDialog方法处理
LegacyGlobalActions#showDialog
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();}
}
可以看到,当长按power按钮,最终会通过handleShow方法显示关机确认弹框
显示关机确认弹框
private void handleShow() {awakenIfNecessary();mDialog = createDialog(); prepareDialog();// If we only have 1 item and it's a simple press action, just do this action.if (mAdapter.getCount() == 1&& mAdapter.getItem(0) instanceof SinglePressAction&& !(mAdapter.getItem(0) instanceof LongPressAction)) {((SinglePressAction) mAdapter.getItem(0)).onPress();} else {if (mDialog != null) {WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();attrs.setTitle("LegacyGlobalActions");mDialog.getWindow().setAttributes(attrs);mDialog.show();mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);}}
}
上述createDialog是创建关机界面的布局显示,以及对应的按钮按键行为的,所以如果需要自己定制关机界面,就可以在这里来修改定制自己的界面和行为了
private GlobalActionsDialog createDialog() {// Simple toggle style if there's no vibrator, otherwise use a tri-stateif (!mHasVibrator) {mSilentModeAction = new SilentModeToggleAction();} else {mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);}mAirplaneModeOn = new ToggleAction(R.drawable.ic_lock_airplane_mode,R.drawable.ic_lock_airplane_mode_off,R.string.global_actions_toggle_airplane_mode,R.string.global_actions_airplane_mode_on_status,R.string.global_actions_airplane_mode_off_status) {@Overridevoid onToggle(boolean on) {if (mHasTelephony && Boolean.parseBoolean(SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {mIsWaitingForEcmExit = true;// Launch ECM exit dialogIntent ecmDialogIntent =new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mContext.startActivity(ecmDialogIntent);} else {changeAirplaneModeSystemSetting(on);}}@Overrideprotected void changeStateFromPress(boolean buttonOn) {if (!mHasTelephony) return;// In ECM mode airplane state cannot be changedif (!(Boolean.parseBoolean(SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {mState = buttonOn ? State.TurningOn : State.TurningOff;mAirplaneState = mState;}}@Overridepublic boolean showDuringKeyguard() {return true;}@Overridepublic boolean showBeforeProvisioning() {return false;}};onAirplaneModeChanged();mItems = new ArrayList<Action>();// 获取fwk中配置的默认显示的处理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());} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {mItems.add(mAirplaneModeOn);} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {if (Settings.Global.getInt(mContext.getContentResolver(),Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {mItems.add(new BugReportAction());}} else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {if (mShowSilentToggle) {mItems.add(mSilentModeAction);}} else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {addUsersToMenu(mItems);}} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {mItems.add(getSettingsAction());} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {mItems.add(getLockdownAction());} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {mItems.add(getVoiceAssistAction());} else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {mItems.add(getAssistAction());} else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {mItems.add(new RestartAction());} else {Log.e(TAG, "Invalid global action key " + actionKey);}// Add here so we don't add more than one.addedKeys.add(actionKey);}if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {mItems.add(getEmergencyAction());}mAdapter = new MyAdapter();AlertParams params = new AlertParams(mContext);params.mAdapter = mAdapter;params.mOnClickListener = this;params.mForceInverseBackground = true;GlobalActionsDialog dialog = new GlobalActionsDialog(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;}});dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);dialog.setOnDismissListener(this);return dialog;}
上述创建dialog的时候,默认显示fwk中 config_globalActionsList的配置,如下:
可以看到,上述对于关机按键的处理,由PowerAction来执行
private final class PowerAction extends SinglePressAction implements LongPressAction {private PowerAction() {super(com.android.internal.R.drawable.ic_lock_power_off,R.string.global_action_power_off);}@Overridepublic boolean onLongPress() {UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {mWindowManagerFuncs.rebootSafeMode(true);return true;}return false;}@Overridepublic boolean showDuringKeyguard() {return true;}@Overridepublic boolean showBeforeProvisioning() {return true;}@Overridepublic void onPress() {// 执行关机操作// shutdown by making sure radio and power are handled accordingly.mWindowManagerFuncs.shutdown(false /* confirm */);}}
好了,这一篇就到这,预备下一篇,定制关机界面。。。。_
专注技术分享,包括Java,python,AI人工智能,Android分享,不定期更新学习视频,欢迎关注