Android 11 关于按键拦截/按键事件处理分享
Dispatcher线程的处理 -- 关于按键
-
InputReader线程将事件放入到队列mInboundQueue中,Dispatcher线程将其取出,稍加处理后放入outBoundQueue队列中。
-
稍加处理
a.分类:Golbal System User
b.处理紧急事件
对Global和System键默认是不会发给App的,Dispatcher线程处理完后直接把它给丢掉。对于要发给App的事件也要放到目标App的队列中。
无论是Global System 还是User key 只要其policy_flag是TO_USER,都会放到out队列中给APP. -
发给谁:
传给App的时候向WMS查询当前窗口,得到目标App的connection(WM创建的),从outBoundQueue取出数据通过connection发给App。 -
参考:http://www.cnblogs.com/samchen2009/p/3368158.html 关注里面的Dispatcher处理流程
5.对Global key的处理
GlobalKeyManager.java中:
loadGlobalKeys(Context context)
context.getResources().getXml(com.android.internal.R.xml.global_keys);
mKeyMapping.put(keyCode, ComponentName.unflattenFromString(componentName)); //如果以后可以从mKeyMapping中获取到,那么就是Global key
全局按键定义在global_keys.xml,格式如下:
frameworks/base/core/res/res/xml/global_keys.xml
<global_keys version="1">
<!-- Example format: keyCode = keycode to handle globally. component = component which will handle this key. -->
<!-- <key keyCode="KEYCODE_VOLUME_UP" component="com.android.example.keys/.VolumeKeyHandler" /> -->
<!-- I add it, eg: -->
<key keyCode="KEYCODE_TV" component="com.android.example.xxxxxx" /> //按下这个按键会发广播给这个组件(App)
</global_keys>
6.对应SystemKey分类处理
//以音量键为例查看怎么处理这些SystemKey
interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) //PhoneWindowManager.java
case KeyEvent.KEYCODE_VOLUME_DOWN:
/*首先判断一下是否需要截屏,若是同时按下音量键和电源键就会截屏*/
interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0);
//frameworks\base\core\java\android\view\WindowManagerPolicy.java
// result |= ACTION_PASS_TO_USER; 确保事件传递给前台应用,不再执行后续的 SystemUI / AudioService 相关处理。
public final static int ACTION_PASS_TO_USER = 0x00000001;
~ACTION_PASS_TO_USER是对ACTION_PASS_TO_USER取反,结果是0x11111110,&=是result = result & ~ACTION_PASS_TO_USER,因此result = result & 0x11111110。
假设result 之前为0x0000 0000,进行运算。
0x0000 0000
0x1111 1110
———————
0x0000 0000
因此当输入事件被处理时,result = 0,即interceptKeyBeforeQueueing返回0,输入事件不再向下传递。
interceptKeyBeforeQueueing除了拦截还可以消费事件,例如当设备需要调节音乐的音量大小,但却缺少音量键,但是有上下左右按键,那么可以在 加上case KeyEvent.KEYCODE_DPAD_DOWN,
然后判断Audiomanager.isMusicActive()(判断当前是否有音频运行),
然后调用getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER, AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG)。
第二次是拦截是PhoneWindowManager中的interceptKeyBeforeDispatching函数。
interceptKeyBeforeDispatching对事件做了二次拦截,返回为-1则说明事件被拦截,返回值为0则说明事件被放行。
系统在frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java处理按键事件,不管是物理按键还是
SystemUI的nav_bar上的虚拟按键(使用了KeyEvent类中的,比如:KeyEvent.KEYCODE_VOLUME_UP).
主要注意的有两个函数:
interceptKeyBeforeQueueing 加入队列之前拦截事件
最开始的事件拦截,也就是拦截的优先级最高,系统级“紧急开关”,确保全局按键(如电源键)立即生效,无需等待应用响应,事件也压根不会传递给应用。
interceptKeyBeforeDispatching 分发之前拦截事件;
如果interceptKeyBeforeQueueing方法不拦截该事件,那么就开始准备派发给应用,会先获取当前的FocusWindow,在派发应用前进行事件相关事件拦截。应用级“筛选器”,根据当前窗口状态决定是否将事件传递给应用,比如HOME按键按下时候,就会考虑当前是否为锁屏画面。
重学input:为什么要进行二次拦截?interceptKeyBeforeQueueing和interceptKeyBeforeDispatching 区别-CSDN博客
长按物理音量上键弹出重启Dialog,去除Dialog界面部分选项
@Override
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
final int metaState = event.getMetaState();
final int flags = event.getFlags();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final int displayId = event.getDisplayId();
if (true) {
Log.d("tag", "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
+ repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
}
//add text
Log.d("tag", "policyFlags:" + policyFlags);//长按和短按会产生不同的policyFlags
//短按流程 按下 down=true repeatCount=0 2次 抬起 down=false repeatCount=0
//长按流程 按下 down=true repeatCount++ 不断自增 抬起 down=false repeatCount=0
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {//1107296256 long //1644167168 short
if (policyFlags == 1107296256 || policyFlags == 1644167168) {
if (repeatCount == 20) {
mHandler.removeMessages(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS);
mHandler.sendEmptyMessageDelayed(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS, 2000 * 1);
}
return -1;
}
}
//add text
//infrare simulate mouse
boolean isBox = "box".equals(SystemProperties.get("ro.target.product"));
if(isBox){
...
}
./frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java:650:
private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10;
./frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java:722:
case MSG_DISPATCH_SHOW_GLOBAL_ACTIONS:
//
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
->
powerLongPress();
||
showGlobalActions();
frameworks/base/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
//dialog所有菜单选项
<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>
</string-array>
@VisibleForTesting
protected void createActionItems() {
// Simple toggle style if there's no vibrator, otherwise use a tri-state
if (!mHasVibrator) {
mSilentModeAction = new SilentModeToggleAction();
} else {
mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler);
}
mAirplaneModeOn = new AirplaneModeAction();
onAirplaneModeChanged();
mItems.clear();
mOverflowItems.clear();
mPowerItems.clear();
String[] defaultActions = {"restart"};//getDefaultActions();//add text
ShutDownAction shutdownAction = new ShutDownAction();
RestartAction restartAction = new RestartAction();
ArraySet<String> addedKeys = new ArraySet<String>();
List<Action> tempActions = new ArrayList<>();
CurrentUserProvider currentUser = new CurrentUserProvider();
//add text
// make sure emergency affordance action is first, if needed
/*if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
addIfShouldShowAction(tempActions, new EmergencyAffordanceAction());
addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY);
}*/
//add text
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;
}
...
}
//点击dialog外,让它消失
protected static ActionsDialog mDialog;//add static
private void initializeLayout() {
setContentView(com.android.systemui.R.layout.global_actions_grid_v2);
fixNavBarClipping();
mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls);
mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public boolean dispatchPopulateAccessibilityEvent(
View host, AccessibilityEvent event) {
// Populate the title here, just as Activity does
event.getText().add(mContext.getString(R.string.global_actions));
return true;
}
});
//add text
ViewGroup black_view = findViewById(com.android.systemui.R.id.global_actions_grid_root);
black_view.setClickable(true);
black_view.setOnClickListener(v -> {
if (mDialog != null) mDialog.dismiss();
//dismissDialog();
});
//add text
mGlobalActionsLayout.setRotationListener(this::onRotate);
mGlobalActionsLayout.setAdapter(mAdapter);
./frameworks/base/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java (Android T)
//衍生案例1: 长按电源键直接关机不弹出dialog
void showGlobalActionsInternal() {
//add text
/*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);*/
mPowerManager.shutdown(false,null,false);//add text
//add text
}
//也可以修改属性实现
./frameworks/base/core/res/res/values/config.xml
<!-- Control the behavior when the user long presses the power button.
0 - Nothing
1 - Global actions menu - 长按电源按钮将显示一个包含各种全局操作选项的菜单
2 - Power off (with confirmation) 长按电源按钮将触发设备的关机动作,弹出需要用户确认的dialog
3 - Power off (without confirmation) 长按电源按钮将触发设备的关机动作,不需要用户确认
4 - Go to voice assist 长按电源按钮将启动语音助手
5 - Go to assistant (Settings.Secure.ASSISTANT) 长按电源按钮将启动设备的默认助手应用程序
-->
<integer name="config_longPressOnPowerBehavior">1</integer>
//衍生案例2: 长按power键按住三秒即可自动关机
<integer name="config_longPressOnPowerBehavior">3</integer> //先去掉dialog
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
...
case KeyEvent.KEYCODE_POWER: {
//add text 长按3s关机,达不到3s取消
if (down) {
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,3000);
} else {
mHandler.removeMessages(MSG_POWER_LONG_PRESS);
}
//add text
break;
}
//注释掉原来的逻辑
/*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);
} else {
interceptPowerKeyUp(event, interactive, canceled);
}
break;
}*/
}
Adroid11.0长按power键关机流程分析
Android 设备按键处理
一些按键模拟案例
@Override
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
...
} else if (keyCode == KeyEvent.KEYCODE_MENU) {
// Hijack modified menu keys for debugging features
final int chordBug = KeyEvent.META_SHIFT_ON;
//add text 模拟长按
if (event.getAction() == KeyEvent.ACTION_UP && (event.getEventTime() - event.getDownTime() > 800)) {
Log.d(TAG,"this is a KEYCODE_MENU's long press");
//to do
return -1;
}
//add text
...
}
关于唤醒按键实现原理
isWakeKey()方法在Android系统中非常重要,因为它决定了哪些按键可以在设备处于深度睡眠状态时唤醒设备。
例如,默认情况下,只有电源键(Power Key)才能唤醒设备,但通过修改isWakeKey方法,可以实现其他按键(如Home键)也能唤醒设备的功能。这对于提升用户体验和设备的可操作性非常有帮助。
frameworks/base/core/java/android/view/KeyEvent.java
public static final boolean isWakeKey(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_CAMERA:
case KeyEvent.KEYCODE_MENU:
case KeyEvent.KEYCODE_PAIRING:
case KeyEvent.KEYCODE_STEM_1:
./frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java:
private boolean isWakeKeyWhenScreenOff(int keyCode) {
...
}
ps: 一键唤醒应用
/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
...
switch (keyCode) {
+ case KeyEvent.KEYCODE_xxx: {//KeyEvent.KEYCODE_VOLUME_UP
+
+ String uPackageName = "com.android.xx";
+ String uClassName = "com.android.xx.SoundRecorder";
+
+ KeyguardManager mKeyguardManager = (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ boolean flag = mKeyguardManager.inKeyguardRestrictedInputMode();//是否锁屏
+ if(down){
+ if(uPackageName != null && uClassName != null && !flag){
+ Intent intent = new Intent();
+ intent.setClassName(uPackageName,uClassName);
+ if("com.android.soundrecorder".equals(uPackageName))
+ mContext.startActivity(intent);
+ }
+ }
+ break;
+ }

浙公网安备 33010602011771号