赞
踩
android12-release
InputManagerService启动-Android12
InputReader线程获取输入事件-Android12
InputDispatcher线程分发事件-Android12
InputChannel通道建立-Android12
InputChannel发送Input给App-Android12
一般手机侧边有三个实体按键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
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());
下面是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
具体功能实现位置在 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; }
如果我们还没有启动完成,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;
}
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.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键启动相机
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; } }
接着查看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; } } } }
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(); } } }
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; } // ... ... }
在此查看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); }
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(); } }); }
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(); } });
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; } } });
Linux Kernel下按键驱动 drivers/input/keyboard/gpio_keys.c:实现了一个体系结构无关的GPIO按键驱动,使用此按键驱动,只需在设备树gpio-key节点添加需要的按键子节点即可。
(相关文章 Linux 驱动之gpio-key驱动分析、gpio_key按键驱动、Android添加新按键)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。