InputManagerService实体按键及组合按键-Android12
android12-release
InputManagerService启动-Android12
InputReader线程获取输入事件-Android12
InputDispatcher线程分发事件-Android12
InputChannel通道建立-Android12
InputChannel发送Input给App-Android12
1. 手机实体按键
一般手机侧边有三个实体按键power开关机键、音量上键、音量下键:
- KEYCODE_POWER = 26
对应scancode码#define KEY_POWER 116
- KEYCODE_VOLUME_UP = 24
对应scancode码#define KEY_VOLUMEUP 115
- KEYCODE_VOLUME_DOWN = 25
对应scancode码#define KEY_VOLUMEDOWN 114
1.1 实体按键添加
IMS:EventHub 设备添加和InputDevice转化 手机实体按键其实也是添加设备。其中关键方法EventHub::openDeviceLocked()
、InputReader::addDeviceLocked()
ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=%s, "
"configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, ",
deviceId, fd, devicePath.c_str(), device->identifier.name.c_str(),
device->classes.string().c_str(), device->configurationFile.c_str(),
device->keyMap.keyLayoutFile.c_str(), device->keyMap.keyCharacterMapFile.c_str(),
toString(mBuiltInKeyboardId == deviceId));
ALOGI("Device added: id=%d, eventHubId=%d, name='%s', descriptor='%s',sources=0x%08x",
device->getId(), eventHubId, identifier.name.c_str(), identifier.descriptor.c_str(),
device->getSources());
1.2 从dumpsys input信息查看
下面是cepheus_input.txt(小米9机器),查看下面两个dump()信息:
frameworks/native/services/inputflinger/reader/InputReader.cpp中InputReader::dump()
frameworks/native/services/inputflinger/reader/EventHub.cpp中EventHub::dump()
frameworks/native/services/inputflinger/reader/InputDevice.cpp中InputDevice::dump()
frameworks/native/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp中KeyboardInputMapper::dump()
Classes: 0x00000001
设备表示Classes码
Path: /dev/input/event0
添加的设备节点
KeyLayoutFile: /system/usr/keylayout/Generic.kl
按键布局kl文件用来完成映射过程
KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
kcm文件转化为显示在文本框的字符
Sources: 0x00000101
与Classes码对应的Sources码
Keyboard Input Mapper:
添加对应InputMapper文件:frameworks/native/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
2. 实体按键功能
具体功能实现位置在 IMS:Input事件可拦截位置
- 添加队列之前拦截
InputReader::loopOnce() -> EventHub::getEvents() -> InputReader::processEventsLocked()/InputReader::processEventsForDeviceLocked() -> InputDevice::process() -> KeyboardInputMapper::process()处理 -> QueuedInputListener::flush() -> NotifyKeyArgs::notify() -> InputDispatcher::notifyKey() -> mPolicy->interceptKeyBeforeQueueing() -> NativeInputManager::interceptKeyBeforeQueueing()/InputManagerService.java#interceptKeyBeforeQueueing() -> InputManagerCallback.java#interceptKeyBeforeQueueing() -> WindowManagerService.java#mPolicy.interceptKeyBeforeQueueing() -> PhoneWindowManager.java#interceptKeyBeforeQueueing()
- 发送之前拦截
mPolicy->interceptKeyBeforeDispatching() ===> PhoneWindowManager.java#interceptKeyBeforeDispatching()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java#interceptKeyBeforeQueueing
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
final int keyCode = event.getKeyCode();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
|| event.isWakeKey();
if (!mSystemBooted) {
// If we have not yet booted, don't let key events do anything.
// Exception: Wake and power key events are forwarded to PowerManager to allow it to
// wake from quiescent mode during boot.
if (down && (keyCode == KeyEvent.KEYCODE_POWER
|| keyCode == KeyEvent.KEYCODE_TV_POWER)) {
wakeUpFromPowerKey(event.getDownTime());
} else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP)
&& isWakeKeyWhenScreenOff(keyCode)) {
wakeUpFromWakeKey(event);
}
return 0;
}
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
final boolean canceled = event.isCanceled();
final int displayId = event.getDisplayId();
final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;
if (DEBUG_INPUT) {
// If screen is off then we treat the case where the keyguard is open but hidden
// the same as if it were open and in front.
// This will prevent any keys other than the power button from waking the screen
// when the keyguard is hidden by another activity.
final boolean keyguardActive = (mKeyguardDelegate != null
&& (interactive ? isKeyguardShowingAndNotOccluded() :
mKeyguardDelegate.isShowing()));
Log.d(TAG, "interceptKeyTq keycode=" + keyCode
+ " interactive=" + interactive + " keyguardActive=" + keyguardActive
+ " policyFlags=" + Integer.toHexString(policyFlags));
}
// Basic policy based on interactive state.
int result;
if (interactive || (isInjected && !isWakeKey)) {
// When the device is interactive or the key is injected pass the
// key to the application.
result = ACTION_PASS_TO_USER;
isWakeKey = false;
if (interactive) {
// If the screen is awake, but the button pressed was the one that woke the device
// then don't pass it to the application
if (keyCode == mPendingWakeKey && !down) {
result = 0;
}
// Reset the pending key
mPendingWakeKey = PENDING_KEY_NULL;
}
} else if (shouldDispatchInputWhenNonInteractive(displayId, keyCode)) {
// If we're currently dozing with the screen on and the keyguard showing, pass the key
// to the application but preserve its wake key status to make sure we still move
// from dozing to fully interactive if we would normally go from off to fully
// interactive.
result = ACTION_PASS_TO_USER;
// Since we're dispatching the input, reset the pending key
mPendingWakeKey = PENDING_KEY_NULL;
} else {
// When the screen is off and the key is not injected, determine whether
// to wake the device but don't pass the key to the application.
result = 0;
if (isWakeKey && (!down || !isWakeKeyWhenScreenOff(keyCode))) {
isWakeKey = false;
}
// Cache the wake key on down event so we can also avoid sending the up event to the app
if (isWakeKey && down) {
mPendingWakeKey = keyCode;
}
}
// If the key would be handled globally, just return the result, don't worry about special
// key processing.
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.shouldHandleGlobalKey(keyCode)) {
// Dispatch if global key defined dispatchWhenNonInteractive.
if (!interactive && isWakeKey && down
&& mGlobalKeyManager.shouldDispatchFromNonInteractive(keyCode)) {
mGlobalKeyManager.setBeganFromNonInteractive();
result = ACTION_PASS_TO_USER;
// Since we're dispatching the input, reset the pending key
mPendingWakeKey = PENDING_KEY_NULL;
}
if (isWakeKey) {
wakeUpFromWakeKey(event);
}
return result;
}
// Alternate TV power to power key for Android TV device.
final HdmiControlManager hdmiControlManager = getHdmiControlManager();
if (keyCode == KeyEvent.KEYCODE_TV_POWER && mHasFeatureLeanback
&& (hdmiControlManager == null || !hdmiControlManager.shouldHandleTvPowerKey())) {
event = KeyEvent.obtain(
event.getDownTime(), event.getEventTime(),
event.getAction(), KeyEvent.KEYCODE_POWER,
event.getRepeatCount(), event.getMetaState(),
event.getDeviceId(), event.getScanCode(),
event.getFlags(), event.getSource(), event.getDisplayId(), null);
return interceptKeyBeforeQueueing(event, policyFlags);
}
// This could prevent some wrong state in multi-displays environment,
// the default display may turned off but interactive is true.
final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());
final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
handleKeyGesture(event, interactiveAndOn);
}
// Enable haptics if down and virtual key without multiple repetitions. If this is a hard
// virtual key such as a navigation bar button, only vibrate if flag is enabled.
final boolean isNavBarVirtKey = ((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0);
boolean useHapticFeedback = down
&& (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0
&& (!isNavBarVirtKey || mNavBarVirtualKeyHapticFeedbackEnabled)
&& event.getRepeatCount() == 0;
// Handle special keys.
switch (keyCode) {
case KeyEvent.KEYCODE_BACK: {
if (down) {
mBackKeyHandled = false;
} else {
if (!hasLongPressOnBackBehavior()) {
mBackKeyHandled |= backKeyPress();
}
// Don't pass back press to app if we've already handled it via long press
if (mBackKeyHandled) {
result &= ~ACTION_PASS_TO_USER;
}
}
break;
}
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (down) {
sendSystemKeyToStatusBarAsync(event.getKeyCode());
NotificationManager nm = getNotificationService();
if (nm != null && !mHandleVolumeKeysInWM) {
nm.silenceNotificationSound();
}
TelecomManager telecomManager = getTelecommService();
if (telecomManager != null && !mHandleVolumeKeysInWM) {
// When {@link #mHandleVolumeKeysInWM} is set, volume key events
// should be dispatched to WM.
if (telecomManager.isRinging()) {
// If an incoming call is ringing, either VOLUME key means
// "silence ringer". We handle these keys here, rather than
// in the InCallScreen, to make sure we'll respond to them
// even if the InCallScreen hasn't come to the foreground yet.
// Look for the DOWN event here, to agree with the "fallback"
// behavior in the InCallScreen.
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " VOLUME key-down while ringing: Silence ringer!");
// Silence the ringer. (It's safe to call this
// even if the ringer has already been silenced.)
telecomManager.silenceRinger();
// And *don't* pass this key thru to the current activity
// (which is probably the InCallScreen.)
result &= ~ACTION_PASS_TO_USER;
break;
}
}
int audioMode = AudioManager.MODE_NORMAL;
try {
audioMode = getAudioService().getMode();
} catch (Exception e) {
Log.e(TAG, "Error getting AudioService in interceptKeyBeforeQueueing.", e);
}
boolean isInCall = (telecomManager != null && telecomManager.isInCall()) ||
audioMode == AudioManager.MODE_IN_COMMUNICATION;
if (isInCall && (result & ACTION_PASS_TO_USER) == 0) {
// If we are in call but we decided not to pass the key to
// the application, just pass it to the session service.
MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
break;
}
}
if (mUseTvRouting || mHandleVolumeKeysInWM) {
// Defer special key handlings to
// {@link interceptKeyBeforeDispatching()}.
result |= ACTION_PASS_TO_USER;
} else if ((result & ACTION_PASS_TO_USER) == 0) {
// If we aren't passing to the user and no one else
// handled it send it to the session manager to
// figure out.
MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
}
break;
}
case KeyEvent.KEYCODE_ENDCALL: {
result &= ~ACTION_PASS_TO_USER;
if (down) {
TelecomManager telecomManager = getTelecommService();
boolean hungUp = false;
if (telecomManager != null) {
hungUp = telecomManager.endCall();
}
if (interactive && !hungUp) {
mEndCallKeyHandled = false;
mHandler.postDelayed(mEndCallLongPress,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
} else {
mEndCallKeyHandled = true;
}
} else {
if (!mEndCallKeyHandled) {
mHandler.removeCallbacks(mEndCallLongPress);
if (!canceled) {
if ((mEndcallBehavior
& Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0) {
if (goHome()) {
break;
}
}
if ((mEndcallBehavior
& Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) {
sleepDefaultDisplay(event.getEventTime(),
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
isWakeKey = false;
}
}
}
}
break;
}
case KeyEvent.KEYCODE_TV_POWER: {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down && hdmiControlManager != null) {
hdmiControlManager.toggleAndFollowTvPower();
}
break;
}
case KeyEvent.KEYCODE_POWER: {
EventLogTags.writeInterceptPower(
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
// Any activity on the power button stops the accessibility shortcut
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactiveAndOn);
} else {
interceptPowerKeyUp(event, canceled);
}
break;
}
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN:
// fall through
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP:
// fall through
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
// fall through
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
result &= ~ACTION_PASS_TO_USER;
interceptSystemNavigationKey(event);
break;
}
case KeyEvent.KEYCODE_SLEEP: {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false;
if (!mPowerManager.isInteractive()) {
useHapticFeedback = false; // suppress feedback if already non-interactive
}
if (down) {
sleepPress();
} else {
sleepRelease(event.getEventTime());
}
break;
}
case KeyEvent.KEYCODE_SOFT_SLEEP: {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false;
if (!down) {
mPowerManagerInternal.setUserInactiveOverrideFromWindowManager();
}
break;
}
case KeyEvent.KEYCODE_WAKEUP: {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = true;
break;
}
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
// If the global session is active pass all media keys to it
// instead of the active window.
result &= ~ACTION_PASS_TO_USER;
}
if ((result & ACTION_PASS_TO_USER) == 0) {
// Only do this if we would otherwise not pass it to the user. In that
// case, the PhoneWindow class will do the same thing, except it will
// only do it if the showing app doesn't process the key on its own.
// Note that we need to make a copy of the key event here because the
// original key event will be recycled when we return.
mBroadcastWakeLock.acquire();
Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK,
new KeyEvent(event));
msg.setAsynchronous(true);
msg.sendToTarget();
}
break;
}
case KeyEvent.KEYCODE_CALL: {
if (down) {
TelecomManager telecomManager = getTelecommService();
if (telecomManager != null) {
if (telecomManager.isRinging()) {
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " CALL key-down while ringing: Answer the call!");
telecomManager.acceptRingingCall();
// And *don't* pass this key thru to the current activity
// (which is presumably the InCallScreen.)
result &= ~ACTION_PASS_TO_USER;
}
}
}
break;
}
case KeyEvent.KEYCODE_ASSIST: {
final boolean longPressed = event.getRepeatCount() > 0;
if (down && !longPressed) {
Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST, event.getDeviceId(),
0 /* unused */, event.getEventTime() /* eventTime */);
msg.setAsynchronous(true);
msg.sendToTarget();
}
result &= ~ACTION_PASS_TO_USER;
break;
}
case KeyEvent.KEYCODE_VOICE_ASSIST: {
if (!down) {
mBroadcastWakeLock.acquire();
Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
msg.setAsynchronous(true);
msg.sendToTarget();
}
result &= ~ACTION_PASS_TO_USER;
break;
}
case KeyEvent.KEYCODE_WINDOW: {
if (mShortPressOnWindowBehavior == SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE) {
if (mPictureInPictureVisible) {
// Consumes the key only if picture-in-picture is visible to show
// picture-in-picture control menu. This gives a chance to the foreground
// activity to customize PIP key behavior.
if (!down) {
showPictureInPictureMenu(event);
}
result &= ~ACTION_PASS_TO_USER;
}
}
break;
}
}
// Intercept the Accessibility keychord (CTRL + ALT + Z) for keyboard users.
if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(isKeyguardLocked())) {
switch (keyCode) {
case KeyEvent.KEYCODE_Z: {
if (down && event.isCtrlPressed() && event.isAltPressed()) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT));
result &= ~ACTION_PASS_TO_USER;
}
break;
}
}
}
if (useHapticFeedback) {
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
"Virtual Key - Press");
}
if (isWakeKey) {
wakeUpFromWakeKey(event);
}
if ((result & ACTION_PASS_TO_USER) != 0) {
// If the key event is targeted to a specific display, then the user is interacting with
// that display. Therefore, give focus to the display that the user is interacting with.
if (!mPerDisplayFocusEnabled
&& displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
// An event is targeting a non-focused display. Move the display to top so that
// it can become the focused display to interact with the user.
// This should be done asynchronously, once the focus logic is fully moved to input
// from windowmanager. Currently, we need to ensure the setInputWindows completes,
// which would force the focus event to be queued before the current key event.
// TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
Log.i(TAG, "Moving non-focused display " + displayId + " to top "
+ "because a key is targeting it");
mWindowManagerFuncs.moveDisplayToTop(displayId);
}
}
return result;
}
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();
final long key_consumed = -1;
if (DEBUG_INPUT) {
Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
+ repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
}
if (mKeyCombinationManager.isKeyConsumed(event)) {
return key_consumed;
}
if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
final long now = SystemClock.uptimeMillis();
final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(keyCode);
if (now < interceptTimeout) {
return interceptTimeout - now;
}
}
// Cancel any pending meta actions if we see any other keys being pressed between the down
// of the meta key and its corresponding up.
if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
mPendingMetaAction = false;
}
// Any key that is not Alt or Meta cancels Caps Lock combo tracking.
if (mPendingCapsLockToggle && !KeyEvent.isMetaKey(keyCode) && !KeyEvent.isAltKey(keyCode)) {
mPendingCapsLockToggle = false;
}
if (isUserSetupComplete() && !keyguardOn) {
if (mModifierShortcutManager.interceptKey(event)) {
dismissKeyboardShortcutsMenu();
mPendingMetaAction = false;
mPendingCapsLockToggle = false;
return key_consumed;
}
}
switch(keyCode) {
case KeyEvent.KEYCODE_HOME:
// First we always handle the home key here, so applications
// can never break it, although if keyguard is on, we do let
// it handle it, because that gives us the correct 5 second
// timeout.
DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId);
if (handler == null) {
handler = new DisplayHomeButtonHandler(displayId);
mDisplayHomeButtonHandlers.put(displayId, handler);
}
return handler.handleHomeButton(focusedToken, event);
case KeyEvent.KEYCODE_MENU:
// Hijack modified menu keys for debugging features
final int chordBug = KeyEvent.META_SHIFT_ON;
if (down && repeatCount == 0) {
if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
null, null, null, 0, null, null);
return key_consumed;
}
}
break;
case KeyEvent.KEYCODE_APP_SWITCH:
if (!keyguardOn) {
if (down && repeatCount == 0) {
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
}
}
return key_consumed;
case KeyEvent.KEYCODE_N:
if (down && event.isMetaPressed()) {
IStatusBarService service = getStatusBarService();
if (service != null) {
try {
service.expandNotificationsPanel();
} catch (RemoteException e) {
// do nothing.
}
return key_consumed;
}
}
break;
case KeyEvent.KEYCODE_S:
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
: TAKE_SCREENSHOT_FULLSCREEN;
mScreenshotRunnable.setScreenshotType(type);
mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
mHandler.post(mScreenshotRunnable);
return key_consumed;
}
break;
case KeyEvent.KEYCODE_SLASH:
if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
return key_consumed;
}
break;
case KeyEvent.KEYCODE_ASSIST:
Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
return key_consumed;
case KeyEvent.KEYCODE_VOICE_ASSIST:
Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
+ " interceptKeyBeforeQueueing");
return key_consumed;
case KeyEvent.KEYCODE_SYSRQ:
if (down && repeatCount == 0) {
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
mHandler.post(mScreenshotRunnable);
}
return key_consumed;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1;
// Disable autobrightness if it's on
int auto = Settings.System.getIntForUser(
mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
UserHandle.USER_CURRENT_OR_SELF);
if (auto != 0) {
Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
UserHandle.USER_CURRENT_OR_SELF);
}
float min = mPowerManager.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
float max = mPowerManager.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
float step = (max - min) / BRIGHTNESS_STEPS * direction;
int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
float brightness = mDisplayManager.getBrightness(screenDisplayId);
brightness += step;
// Make sure we don't go beyond the limits.
brightness = Math.min(max, brightness);
brightness = Math.max(min, brightness);
mDisplayManager.setBrightness(screenDisplayId, brightness);
startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
UserHandle.CURRENT_OR_SELF);
}
return key_consumed;
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE:
if (mUseTvRouting || mHandleVolumeKeysInWM) {
// On TVs or when the configuration is enabled, volume keys never
// go to the foreground app.
dispatchDirectAudioEvent(event);
return key_consumed;
}
// If the device is in VR mode and keys are "internal" (e.g. on the side of the
// device), then drop the volume keys and don't forward it to the
// application/dispatch the audio event.
if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
final InputDevice d = event.getDevice();
if (d != null && !d.isExternal()) {
return key_consumed;
}
}
break;
case KeyEvent.KEYCODE_TAB:
if (event.isMetaPressed()) {
// Pass through keyboard navigation keys.
return 0;
}
// Display task switcher for ALT-TAB.
if (down && repeatCount == 0) {
if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
final int shiftlessModifiers =
event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(
shiftlessModifiers, KeyEvent.META_ALT_ON)) {
mRecentAppsHeldModifiers = shiftlessModifiers;
showRecentApps(true);
return key_consumed;
}
}
}
break;
case KeyEvent.KEYCODE_ALL_APPS:
if (!down) {
mHandler.removeMessages(MSG_HANDLE_ALL_APPS);
Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
msg.setAsynchronous(true);
msg.sendToTarget();
}
return key_consumed;
case KeyEvent.KEYCODE_NOTIFICATION:
if (!down) {
toggleNotificationPanel();
}
return key_consumed;
case KeyEvent.KEYCODE_SPACE:
// Handle keyboard layout switching.
if ((metaState & (KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK)) == 0) {
return 0;
}
// Share the same behavior with KEYCODE_LANGUAGE_SWITCH.
case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
if (down && repeatCount == 0) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
return key_consumed;
}
break;
case KeyEvent.KEYCODE_META_LEFT:
case KeyEvent.KEYCODE_META_RIGHT:
if (down) {
if (event.isAltPressed()) {
mPendingCapsLockToggle = true;
mPendingMetaAction = false;
} else {
mPendingCapsLockToggle = false;
mPendingMetaAction = true;
}
} else {
// Toggle Caps Lock on META-ALT.
if (mPendingCapsLockToggle) {
mInputManagerInternal.toggleCapsLock(event.getDeviceId());
mPendingCapsLockToggle = false;
} else if (mPendingMetaAction) {
launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
event.getDeviceId(),
event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
mPendingMetaAction = false;
}
}
return key_consumed;
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
if (down) {
if (event.isMetaPressed()) {
mPendingCapsLockToggle = true;
mPendingMetaAction = false;
} else {
mPendingCapsLockToggle = false;
}
} else {
// hide recent if triggered by ALT-TAB.
if (mRecentAppsHeldModifiers != 0
&& (metaState & mRecentAppsHeldModifiers) == 0) {
mRecentAppsHeldModifiers = 0;
hideRecentApps(true, false);
return key_consumed;
}
// Toggle Caps Lock on META-ALT.
if (mPendingCapsLockToggle) {
mInputManagerInternal.toggleCapsLock(event.getDeviceId());
mPendingCapsLockToggle = false;
return key_consumed;
}
}
break;
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
return key_consumed;
}
// Reserve all the META modifier combos for system behavior
if ((metaState & KeyEvent.META_META_ON) != 0) {
return key_consumed;
}
// Let the application handle the key.
return 0;
}
2.1 没有启动完成KeyEvents不做任何事情,其中KEYCODE_POWER例外
如果我们还没有启动完成,KeyEvents不做任何事情。
例外:唤醒和电源键KeyEvents事件被转发到PowerManager,以允许它在引导期间从静态模式唤醒。
if (!mSystemBooted) {
// If we have not yet booted, don't let key events do anything.
// Exception: Wake and power key events are forwarded to PowerManager to allow it to
// wake from quiescent mode during boot.
if (down && (keyCode == KeyEvent.KEYCODE_POWER
|| keyCode == KeyEvent.KEYCODE_TV_POWER)) {
wakeUpFromPowerKey(event.getDownTime());
} else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP)
&& isWakeKeyWhenScreenOff(keyCode)) {
wakeUpFromWakeKey(event);
}
return 0;
}
2.2 双击power键启动相机
Android S上
handleKeyGesture()
新增改变,注释解释为了解决多屏显示的场景下出现错误的状态
handleCameraGesture()
处理启动相机,最终调用interceptKeyBeforeQueueing() -> handleKeyGesture() -> handleCameraGesture() -> mGestureLauncherService.interceptPowerKeyDown() -> handleCameraGesture() -> StatusBarManagerService.java#onCameraLaunchGestureDetected(source) -> mBar.onCameraLaunchGestureDetected(source) -> StatusBar.java#onCameraLaunchGestureDetected()
GestureLauncherService.interceptPowerKeyDown()
中计算判断
mBarService.registerStatusBar(mCommandQueue)
注册到StatusBarManagerService
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java- 功能开关判断
com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled、Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED
// This could prevent some wrong state in multi-displays environment,
// the default display may turned off but interactive is true.
final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());
final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
handleKeyGesture(event, interactiveAndOn);
}
private void handleKeyGesture(KeyEvent event, boolean interactive) {
if (mKeyCombinationManager.interceptKey(event, interactive)) {
// handled by combo keys manager.
mSingleKeyGestureDetector.reset();
return;
}
if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
mPowerKeyHandled = handleCameraGesture(event, interactive);
if (mPowerKeyHandled) {
// handled by camera gesture.
mSingleKeyGestureDetector.reset();
return;
}
}
mSingleKeyGestureDetector.interceptKey(event, interactive);
}
frameworks/base/services/core/java/com/android/server/GestureLauncherService.java
public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
MutableBoolean outLaunched) {
if (event.isLongPress()) {
// Long presses are sent as a second key down. If the long press threshold is set lower
// than the double tap of sequence interval thresholds, this could cause false double
// taps or consecutive taps, so we want to ignore the long press event.
return false;
}
boolean launchCamera = false;
boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
synchronized (this) {
powerTapInterval = event.getEventTime() - mLastPowerDown;
mLastPowerDown = event.getEventTime();
if (powerTapInterval >= POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) {
// Tap too slow, reset consecutive tap counts.
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps = 1;
} else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
// Tap too slow for shortcuts
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps++;
} else {
// Fast consecutive tap
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
}
// Check if we need to launch camera or emergency gesture flows
if (mEmergencyGestureEnabled) {
// Commit to intercepting the powerkey event after the second "quick" tap to avoid
// lockscreen changes between launching camera and the emergency gesture flow.
if (mPowerButtonConsecutiveTaps > 1) {
intercept = interactive;
}
if (mPowerButtonConsecutiveTaps == EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD) {
launchEmergencyGesture = true;
}
}
if (mCameraDoubleTapPowerEnabled
&& powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS
&& mPowerButtonConsecutiveTaps == CAMERA_POWER_TAP_COUNT_THRESHOLD) {
launchCamera = true;
intercept = interactive;
}
}
if (mPowerButtonConsecutiveTaps > 1 || mPowerButtonSlowConsecutiveTaps > 1) {
Slog.i(TAG, Long.valueOf(mPowerButtonConsecutiveTaps)
+ " consecutive power button taps detected, "
+ Long.valueOf(mPowerButtonSlowConsecutiveTaps)
+ " consecutive slow power button taps detected");
}
if (launchCamera) {
Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval="
+ powerTapInterval + "ms");
launchCamera = handleCameraGesture(false /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
if (launchCamera) {
mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
(int) powerTapInterval);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
}
} else if (launchEmergencyGesture) {
Slog.i(TAG, "Emergency gesture detected, launching.");
launchEmergencyGesture = handleEmergencyGesture();
mUiEventLogger.log(GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
}
mMetricsLogger.histogram("power_consecutive_short_tap_count",
mPowerButtonSlowConsecutiveTaps);
mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
outLaunched.value = launchCamera || launchEmergencyGesture;
// Intercept power key event if the press is part of a gesture (camera, eGesture) and the
// user has completed setup.
return intercept && isUserSetupComplete();
}
2.3 五次点击power键启动SOS紧急呼叫
在
2.2 双击power键启动相机
发现GestureLauncherService.interceptPowerKeyDown()
中有SOS紧急呼叫功能
,也是通过GestureLauncherService\StatusBarManagerService服务调用到StatusBar.java中onEmergencyActionLaunchGestureDetected(),不同的是SOS紧急呼叫功能
需要5次(EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5
)点击power键
- 功能开关
com.android.internal.R.bool.config_emergencyGestureEnabled、Settings.Secure.EMERGENCY_GESTURE_ENABLED
- 有个疑问,不知道为什么这样设计,这里是优先处理
2.2 双击power键启动相机
2.4 power长按功能 PowerKeyRule
,如唤起语音助手、关机、重启等弹框
回到
handleKeyGesture(event, interactiveAndOn)
power键处理mSingleKeyGestureDetector.interceptKey(event, interactive)
- PhoneWindowManager.java初始化是在initSingleKeyGestureRules中添加new PowerKeyRule(powerKeyGestures)
- 长按调用PowerKeyRule中
onLongPress()
,powerLongPress(eventTime)处理长按功能:
LONG_PRESS_POWER_ASSISTANT
唤醒助手界面launchAssistAction()LONG_PRESS_POWER_GO_TO_VOICE_ASSIST
唤醒语音助手launchVoiceAssistLONG_PRESS_POWER_GLOBAL_ACTIONS
弹出关机、重启、飞行模式等选项对话框showGlobalActions()LONG_PRESS_POWER_SHUT_OFF
关闭所有窗口sendCloseSystemWindows
,执行mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF)
- config_LongPressOnPowerBehavior配置值
frameworks/base/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
void interceptKey(KeyEvent event, boolean interactive) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
// Store the non interactive state when first down.
if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) {
mBeganFromNonInteractive = !interactive;
}
interceptKeyDown(event);
} else {
interceptKeyUp(event);
}
}
private void interceptKeyDown(KeyEvent event) {
final int keyCode = event.getKeyCode();
// same key down.
if (mDownKeyCode == keyCode) {
if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0
&& mActiveRule.supportLongPress() && !mHandledByLongPress) {
if (DEBUG) {
Log.i(TAG, "Long press key " + KeyEvent.keyCodeToString(keyCode));
}
mHandledByLongPress = true;
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
mActiveRule.onLongPress(event.getEventTime());
}
return;
}
// ... ...
mDownKeyCode = keyCode;
// ... ...
}
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private void initSingleKeyGestureRules() {
mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
int powerKeyGestures = 0;
if (hasVeryLongPressOnPowerBehavior()) {
powerKeyGestures |= KEY_VERYLONGPRESS;
}
if (hasLongPressOnPowerBehavior()) {
powerKeyGestures |= KEY_LONGPRESS;
}
mSingleKeyGestureDetector.addRule(new PowerKeyRule(powerKeyGestures));
if (hasLongPressOnBackBehavior()) {
mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS));
}
}
private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
@Override
void onLongPress(long eventTime) {
if (mSingleKeyGestureDetector.beganFromNonInteractive()
&& !mSupportLongPressPowerWhenNonInteractive) {
Slog.v(TAG, "Not support long press power when device is not interactive.");
return;
}
powerLongPress(eventTime);
}
}
private void powerLongPress(long eventTime) {
final int behavior = getResolvedLongPressOnPowerBehavior();
Slog.d(TAG, "powerLongPress: eventTime=" + eventTime
+ " mLongPressOnPowerBehavior=" + 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");
showGlobalActions();
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;
case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
mPowerKeyHandled = true;
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
"Power - Long Press - Go To Voice Assist");
// Some devices allow the voice assistant intent during setup (and use that intent
// to launch something else, like Settings). So we explicitly allow that via the
// config_allowStartActivityForLongPressOnPowerInSetup resource in config.xml.
launchVoiceAssist(mAllowStartActivityForLongPressOnPowerDuringSetup);
break;
case LONG_PRESS_POWER_ASSISTANT:
mPowerKeyHandled = true;
performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON, false,
"Power - Long Press - Go To Assistant");
final int powerKeyDeviceId = Integer.MIN_VALUE;
launchAssistAction(null, powerKeyDeviceId, eventTime,
AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS);
break;
}
}
2.5 power键亮灭屏
接着查看power键点击亮灭屏,
KeyEvent.ACTION_DOWN
按下interceptKeyDown\KeyEvent.ACTION_UP
抬起interceptKeyUp;关注抬起后操作未触发长按继续处理,可能是多次点击power键延时处理MSG_KEY_DELAYED_PRESS
,看到onPress()\onMultiPress()最终都是调用powerPress()
,这里没有处理通知亮屏逻辑。
- 延时时间
MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout()
,默认时间是DEFAULT_MULTI_PRESS_TIMEOUT = 300
- 查看时,mShortPressOnPowerBehavior基本有两种行为动作
SHORT_PRESS_POWER_GO_TO_SLEEP
灭屏sleepDefaultDisplayFromPowerButton()SHORT_PRESS_POWER_GO_HOME
回到HOME桌面
- config_shortPressOnPowerBehavior配置值
frameworks/base/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
void interceptKey(KeyEvent event, boolean interactive) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
// Store the non interactive state when first down.
if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) {
mBeganFromNonInteractive = !interactive;
}
interceptKeyDown(event);
} else {
interceptKeyUp(event);
}
}
private void interceptKeyDown(KeyEvent event) {
// ... ...
}
private boolean interceptKeyUp(KeyEvent event) {
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
if (mActiveRule == null) {
return false;
}
if (mHandledByLongPress) {
mHandledByLongPress = false;
mKeyPressCounter = 0;
return true;
}
final long downTime = event.getDownTime();
if (event.getKeyCode() == mActiveRule.mKeyCode) {
// Directly trigger short press when max count is 1.
if (mActiveRule.getMaxMultiPressCount() == 1) {
if (DEBUG) {
Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode()));
}
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
1, downTime);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
return true;
}
// This could be a multi-press. Wait a little bit longer to confirm.
mKeyPressCounter++;
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
mKeyPressCounter, downTime);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
return true;
}
reset();
return false;
}
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
// ... ...
@Override
void onPress(long downTime) {
powerPress(downTime, 1 /*count*/,
mSingleKeyGestureDetector.beganFromNonInteractive());
}
// ... ...
@Override
void onMultiPress(long downTime, int count) {
powerPress(downTime, count, mSingleKeyGestureDetector.beganFromNonInteractive());
}
}
private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
// ... ...
if (count == 2) {
powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
} else if (count == 3) {
powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
} else if (interactive && !beganFromNonInteractive) {
if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) {
Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+ "fingerprint sensor");
return;
}
switch (mShortPressOnPowerBehavior) {
case SHORT_PRESS_POWER_NOTHING:
break;
case SHORT_PRESS_POWER_GO_TO_SLEEP:
sleepDefaultDisplayFromPowerButton(eventTime, 0);
break;
case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
sleepDefaultDisplayFromPowerButton(eventTime,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
break;
case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:
if (sleepDefaultDisplayFromPowerButton(eventTime,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE)) {
launchHomeFromHotKey(DEFAULT_DISPLAY);
}
break;
case SHORT_PRESS_POWER_GO_HOME:
shortPressPowerGoHome();
break;
case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: {
if (mDismissImeOnBackKeyPressed) {
if (mInputMethodManagerInternal == null) {
mInputMethodManagerInternal =
LocalServices.getService(InputMethodManagerInternal.class);
}
if (mInputMethodManagerInternal != null) {
mInputMethodManagerInternal.hideCurrentInputMethod(
SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME);
}
} else {
shortPressPowerGoHome();
}
break;
}
}
}
}
2.5.1 power键亮屏
Android S上
handleKeyGesture()
新增改变处理长按、连击、灭屏
等事件,优先处理组合按键功能KeyCombinationManager
,继续查看interceptKeyBeforeQueueing()
代码,发现PhoneWindowManager.java中有power键处理interceptPowerKeyDown()、interceptPowerKeyUp()
,亮屏处理就是在interceptPowerKeyDown()中:
mPowerKeyWakeLock.acquire()
持有power的WakeLock;此处可能会耗时,有可能WakeLock底层未释放
wakeUpFromPowerKey(event.getDownTime())
wakeUp亮屏
// Handle special keys.
switch (keyCode) {
case KeyEvent.KEYCODE_POWER: {
// ... ...
EventLogTags.writeInterceptPower(
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
// Any activity on the power button stops the accessibility shortcut
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactiveAndOn);
} else {
interceptPowerKeyUp(event, canceled);
}
break;
}
// ... ...
}
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
// Hold a wake lock until the power key is released.
if (!mPowerKeyWakeLock.isHeld()) {
mPowerKeyWakeLock.acquire();
}
mWindowManagerFuncs.onPowerKeyDown(interactive);
// Stop ringing or end call if configured to do so when power is pressed.
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();
}
}
final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
// Inform the StatusBar; but do not allow it to consume the event.
sendSystemKeyToStatusBarAsync(event.getKeyCode());
// 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 = mPowerKeyHandled || hungUp
|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) {
if (!interactive) {
wakeUpFromPowerKey(event.getDownTime());
}
} else {
// handled by another power key policy.
if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
mSingleKeyGestureDetector.reset();
}
}
}
2.6 音量上下键调节音量KEYCODE_VOLUME_DOWN\KEYCODE_VOLUME_UP
KeyEvent.KEYCODE_VOLUME_MUTE
这个一般就是耳机上的按键,主要控制音乐暂停\播放等。这里主要查看KEYCODE_VOLUME_DOWN\KEYCODE_VOLUME_UP按键:
sendSystemKeyToStatusBarAsync(event.getKeyCode())
通知到SystemUI中StatusBar.java#handleSystemKey()
,这里看到只有按下DOWN时通知,没有看到抬起UP时通知MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent()
调用MediaSessionService.java#dispatchVolumeKeyEvent()调节音量
// Handle special keys.
switch (keyCode) {
// ... ...
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (down) {
sendSystemKeyToStatusBarAsync(event.getKeyCode());
NotificationManager nm = getNotificationService();
if (nm != null && !mHandleVolumeKeysInWM) {
nm.silenceNotificationSound();
}
TelecomManager telecomManager = getTelecommService();
if (telecomManager != null && !mHandleVolumeKeysInWM) {
// When {@link #mHandleVolumeKeysInWM} is set, volume key events
// should be dispatched to WM.
if (telecomManager.isRinging()) {
// If an incoming call is ringing, either VOLUME key means
// "silence ringer". We handle these keys here, rather than
// in the InCallScreen, to make sure we'll respond to them
// even if the InCallScreen hasn't come to the foreground yet.
// Look for the DOWN event here, to agree with the "fallback"
// behavior in the InCallScreen.
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " VOLUME key-down while ringing: Silence ringer!");
// Silence the ringer. (It's safe to call this
// even if the ringer has already been silenced.)
telecomManager.silenceRinger();
// And *don't* pass this key thru to the current activity
// (which is probably the InCallScreen.)
result &= ~ACTION_PASS_TO_USER;
break;
}
}
int audioMode = AudioManager.MODE_NORMAL;
try {
audioMode = getAudioService().getMode();
} catch (Exception e) {
Log.e(TAG, "Error getting AudioService in interceptKeyBeforeQueueing.", e);
}
boolean isInCall = (telecomManager != null && telecomManager.isInCall()) ||
audioMode == AudioManager.MODE_IN_COMMUNICATION;
if (isInCall && (result & ACTION_PASS_TO_USER) == 0) {
// If we are in call but we decided not to pass the key to
// the application, just pass it to the session service.
MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
break;
}
}
if (mUseTvRouting || mHandleVolumeKeysInWM) {
// Defer special key handlings to
// {@link interceptKeyBeforeDispatching()}.
result |= ACTION_PASS_TO_USER;
} else if ((result & ACTION_PASS_TO_USER) == 0) {
// If we aren't passing to the user and no one else
// handled it send it to the session manager to
// figure out.
MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
}
break;
}
// ... ...
}
3. 实体按键组合功能KeyCombinationManager
在此查看handleKeyGesture方法,优先处理
KeyCombinationManager.interceptKey()
;下面具体查看initKeyCombinationRules()
组合按键规则添加
private void handleKeyGesture(KeyEvent event, boolean interactive) {
if (mKeyCombinationManager.interceptKey(event, interactive)) {
// handled by combo keys manager.
mSingleKeyGestureDetector.reset();
return;
}
if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
mPowerKeyHandled = handleCameraGesture(event, interactive);
if (mPowerKeyHandled) {
// handled by camera gesture.
mSingleKeyGestureDetector.reset();
return;
}
}
mSingleKeyGestureDetector.interceptKey(event, interactive);
}
3.1 POWER+VOLUME_DOWN截图功能
initKeyCombinationRules()
添加new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER)
,截图interceptScreenshotChord()
final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableScreenshotChord);
if (screenshotChordEnabled) {
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) {
@Override
void execute() {
mPowerKeyHandled = true;
interceptScreenshotChord();
}
@Override
void cancel() {
cancelPendingScreenshotChordAction();
}
});
}
3.2 VOLUME_DOWN+VOLUME_UP辅助功能快捷方式
initKeyCombinationRules()
添加new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP)
,执行interceptAccessibilityShortcutChord()
激活辅助功能accessibilityShortcutActivated()
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP) {
@Override
boolean preCondition() {
return mAccessibilityShortcutController
.isAccessibilityShortcutAvailable(isKeyguardLocked());
}
@Override
void execute() {
interceptAccessibilityShortcutChord();
}
@Override
void cancel() {
cancelPendingAccessibilityShortcutAction();
}
});
3.3 POWER+VOLUME_UP 静音
initKeyCombinationRules()
添加new TwoKeysCombinationRule(KEYCODE_VOLUME_UP, KEYCODE_POWER)
,执行execute()
执行中区分两种行为:
POWER_VOLUME_UP_BEHAVIOR_MUTE
执行interceptRingerToggleChord()
设置mAudioManagerInternal.silenceRingerModeInternal("volume_hush")
静音模式RINGER_MODE_SILENTshowGlobalActions()
执行showGlobalActions()
弹出关机、重启、飞行模式等选项对话框;power长按功能中也有行为实现该功能
// Volume up + power can either be the "ringer toggle chord" or as another way to
// launch GlobalActions. This behavior can change at runtime so we must check behavior
// inside the TwoKeysCombinationRule.
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_UP, KEYCODE_POWER) {
@Override
boolean preCondition() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
return mRingerToggleChord != VOLUME_HUSH_OFF;
default:
return true;
}
}
@Override
void execute() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
// no haptic feedback here since
interceptRingerToggleChord();
mPowerKeyHandled = true;
break;
case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS:
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
"Power + Volume Up - Global Actions");
showGlobalActions();
mPowerKeyHandled = true;
break;
default:
break;
}
}
@Override
void cancel() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
cancelPendingRingerToggleChordAction();
break;
case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS:
cancelGlobalActionsAction();
break;
}
}
});
4. Kernel按键驱动
Linux Kernel下按键驱动 drivers/input/keyboard/gpio_keys.c:实现了一个体系结构无关的GPIO按键驱动,使用此按键驱动,只需在设备树gpio-key节点添加需要的按键子节点即可。
(相关文章 Linux 驱动之gpio-key驱动分析、gpio_key按键驱动、Android添加新按键)