Android之路

导航

Android结合源码分析Power按键处理流程

    这是之前团队进行技术交流时,我选择的一个主题,那段时间解决power锁屏按键的bug,搞得头大,所以借此机会结合Android8.0源码去分析Power键的处理流程,也将此分享出来,希望对大家有所帮助,本文为博主原创文章,有不对的地方,欢迎大家指正!

作者: Android之路

出处: https://www.cnblogs.com/sparrowlhl/p/11174488.html

版权声明:本文为博主原创文章,转载请注明出处,谢谢!

    Android系统中,一般的按键都可以在应用中处理,但是,对于系统级别的按键上层应用是无法收到消息的,也就是说,你的APP是无法直接处理的。针对这种系统级的按键事件,都是在Event事件分发前处理。Event事件分发后,只有包含有Activity的APP才能处理事件;若APP无法处理,则需要在PhoneWindowManager中处理。

    本文所讲的Power键则属于该种情况。即用户触发Power键,底层收到按键会回调InputMonitor的函数dispatchUnhandledKey()。

一、为何最终处理者是PhoneWindowManager?

    通过上文可知最终事件的处理是由PhoneWindowManager完成的;那么,按键后,系统是如何传递到PhoneWindowManager?下面就从源码的角度分析一下该过程。

    • WindowManagerService:Framework 最核心的服务之一,负责窗口管理。
    • InputManagerService:输入管理服务。

    上述两个服务与Power按键相关,但是两者是如何关联的,就要从它们的创建说起.我们都知道,Android系统中的核心进程是system_server,对应SystemServer类,在其run()方法中会启动一堆的service,当然包括上述两个服务。具体源码分析如下:

    1、先创建inputManager,再创建WindowManagerService对象时,可发现作为参数引用了上述inputManager,且创建了PhoneWindowManager实例:

源码路径:frameworks/base/services/java/com/android/server/SystemServer.java

private
void startOtherServices() { ...... inputManager = new InputManagerService(context); //输入系统服务 【step_SystemServer_1】 ......     //【step_SystemServer_2】 wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore, new PhoneWindowManager()); //PhoneWindowManager实例 ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); }
   
源码路径:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
            WindowManagerPolicy policy) {
            ......

             mPolicy = policy;    //实例: PhoneWindowManager对象      【step_InputMonitor_2】
       ......
}

    2、启动inputManager之前,设置了一个回调接口:

      //消息分发之前回调--->查看InputManagerService
      inputManager.setWindowManagerCallbacks(wm.getInputMonitor());   
      inputManager.start();

    3、InputMonitor.java:【底层是C/C++相关的,博主对C也不太了解,此处就不作分析了】    

    底层收到按键会回调InputManagerService的dispatchUnhandledKey()--->InputMonitor的函数dispatchUnhandledKey()。具体由底层InputDispatcher.cpp调用。

源码路径: frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

  //【Power键属于系统级按键,因此处理方法是dispatchUnhandledKey】
    /* Provides an opportunity for the window manager policy to process a key that
     * the application did not handle. */
    @Override
    public KeyEvent dispatchUnhandledKey(
            InputWindowHandle focus, KeyEvent event, int policyFlags) {
        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
        
        //此处 mservice: WindowManagerService    【step_InputMonitor_0】
        return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
    }

    由这三步可知,最终由PhoneWindowManager处理。将上述整理成时序图:

二、Power按键触发后的具体执行逻辑分析.

列出几种常见的触发Power键的情况:

情况一:长按Power键
情况二:单独短按Power键 情况三:Power
+ 音量键(-)
以下也以这三种情况结合源码分析流程。

    由上文可知,真正的处理逻辑在PhoneWindowManager类中,该类有两个方法:interceptKeyBeforeDispatching和interceptKeyBeforeQueueing,包括了几乎所有按键的处理。

    •   interceptKeyBeforeDispatching:主要处理Home键、Menu键、Search键等。
    •   interceptKeyBeforeQueueing:主要处理音量键、电源键(Power键)、耳机键等。

    当前Power键处理流程:

    dispatchUnhandledKey()------>interceptFallback()---->interceptKeyBeforeQueueing()

    下面从interceptKeyBeforeQueueing(KeyEvent event, int policyFlags)分析。

    而一个按键包含两个动作Down和UP,因此从这两个方面分析interceptKeyBeforeQueueing()的执行流程。

    • 按下:  interceptPowerKeyDown(KeyEvent event, boolean interactive)
    • 释放:  interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled)

    参数含义:    

          interactive:是否亮屏

            KeyEvent.FLAG_FALLBACK:不被应用处理的按键事件或一些在  键值映射中不被处理的事件(例:轨迹球事件等)。

    根据操作按键时系统是否亮屏,代码执行的逻辑也不同,因此每个事件下分别从亮灭屏来分析,具体如下:

    (下述代码的执行过程中有对一些变量的判断,而这些值都是系统配置的,在config.xml中,因此具体执行哪个流程以当前平台配置为准)    

涉及到的配置信息的相关源码路径:
frameworks/base/core/java/android/view/ViewConfiguration.java
frameworks/base/core/res/res/values/config.xml

    1、按下(ACTION_DOWN):先上源码PhoneWindowManager.java中的interceptPowerKeyDown函数。

  1  private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
  2         //FACE_UNLOCK_SUPPORT start
  3         Slog.i("FaceUnlockUtil", "interceptPowerKeyDown interactive = " + interactive);
  4         Settings.System.putInt(mContext.getContentResolver(), "faceunlock_start", 1);
  5         //FACE_UNLOCK_SUPPORT end
  6         // Hold a wake lock until the power key is released.
  7         if (!mPowerKeyWakeLock.isHeld()) {
  8             mPowerKeyWakeLock.acquire();          //获得唤醒锁
  9         }
 10 
 11         // Cancel multi-press detection timeout.
 12         if (mPowerKeyPressCounter != 0) {
 13             mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
 14         }
 15 
 16         // Detect user pressing the power button in panic when an application has
 17         // taken over the whole screen.
 18         boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,
 19                 SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags),
 20                 isNavBarEmpty(mLastSystemUiFlags));
 21         if (panic) {
 22             mHandler.post(mHiddenNavPanic);
 23         }
 24 
 25         // Latch power key state to detect screenshot chord.
 26         if (interactive && !mScreenshotChordPowerKeyTriggered
 27                 && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {   
 28             mScreenshotChordPowerKeyTriggered = true;                //标记按下power key,用于组合键截屏,具体参考下述3
 29             mScreenshotChordPowerKeyTime = event.getDownTime();
 30             interceptScreenshotChord();
 31         }
 32 
 33         // Stop ringing or end call if configured to do so when power is pressed.
 34         TelecomManager telecomManager = getTelecommService();
 35         boolean hungUp = false;
 36         if (telecomManager != null) {
 37             if (telecomManager.isRinging()) {
 38                 // Pressing Power while there's a ringing incoming
 39                 // call should silence the ringer.
 40                 telecomManager.silenceRinger();
 41             } else if ((mIncallPowerBehavior
 42                     & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
 43                     && telecomManager.isInCall() && interactive) {
 44                 // Otherwise, if "Power button ends call" is enabled,
 45                 // the Power button will hang up any current active call.
 46                 hungUp = telecomManager.endCall();
 47             }
 48         }
 49 
 50         GestureLauncherService gestureService = LocalServices.getService(
 51                 GestureLauncherService.class);
 52         boolean gesturedServiceIntercepted = false;
 53         if (gestureService != null) {
 54             gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,
 55                     mTmpBoolean);
 56             if (mTmpBoolean.value && mRequestedOrGoingToSleep) {
 57                 mCameraGestureTriggeredDuringGoingToSleep = true;
 58             }
 59         }
 60 
 61         // Inform the StatusBar; but do not allow it to consume the event.
 62         sendSystemKeyToStatusBarAsync(event.getKeyCode());     //
 63 
 64         // If the power key has still not yet been handled, then detect short
 65         // press, long press, or multi press and decide what to do.
 66         mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
 67                 || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted;
 68         if (!mPowerKeyHandled) {
 69             if (interactive) {   //亮屏
 70                 // When interactive, we're already awake.
 71                 // Wait for a long press or for the button to be released to decide what to do.
 72                 if (hasLongPressOnPowerBehavior()) { //长按----判断是否为弹出操作界面的逻辑
 73                     Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
 74                     msg.setAsynchronous(true);
 75                     mHandler.sendMessageDelayed(msg,
 76                             ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());  //500ms
 77                 }
 78             } else {
 79                 wakeUpFromPowerKey(event.getDownTime());     //唤醒屏幕---......
 80 
 81                 if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { //当前配置mSupportLongPressPowerWhenNonInteractive=false
 82                     Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
 83                     msg.setAsynchronous(true);
 84                     mHandler.sendMessageDelayed(msg,
 85                             ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
 86                     mBeganFromNonInteractive = true;
 87                 } else {
 88                     final int maxCount = getMaxMultiPressPowerCount();   //当前配置不支持config.xml
 89 
 90                     if (maxCount <= 1) {
 91                         mPowerKeyHandled = true;      //执行此处#
 92                     } else {
 93                         mBeganFromNonInteractive = true;
 94                     }
 95                 }
 96             }
 97         }
 98     }
 99 
100 ......
101 
102     private int getResolvedLongPressOnPowerBehavior() {
103         if (FactoryTest.isLongPressOnPowerOffEnabled()) { //默认false,
104             return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;    //长按Power,直接关机;对应属性:factory.long_press_power_off
105         }
106         return mLongPressOnPowerBehavior;
107     }
108 
109     private boolean hasLongPressOnPowerBehavior() {
110         return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
111     }

    根据上述代码分析:

    (1)亮屏:第69-76行代码。

      hasLongPressOnPowerBehavior()---mHanlder发送消息(500ms)--powerLongPress() ------>getResolvedLongPressOnPowerBehavior()根据获取的值来 执行相应的流程:    

    • LONG_PRESS_POWER_GLOBAL_ACTIONS:弹出操作界面---->showGlobalActionsInternal()--->sendCloseSystemWindows(String reason) /mGlobalActions.showDialog()---->PhoneWindow.sendCloseSystemWindows(mContext, reason)....
    • LONG_PRESS_POWER_SHUT_OFF/LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:直接关机,相当于点击上述弹框中的关机操作。此处可结合源码查看:  对应属性:factory.long_press_power_off  使用命令:

          adb shell getprop/setprop...即可测试效果。

    (2)灭屏:第79-96行代码。

      wakeUpFromPowerKey()---->wakeUp()----  >mPowerManager.wakeUp()....调用PowerManagerService唤 醒屏幕.

 1     private void wakeUpFromPowerKey(long eventTime) {
 2         wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey, "android.policy:POWER");
 3     }
 4 
 5     private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, String reason) {
 6         ......
10         final boolean theaterModeEnabled = isTheaterModeEnabled();
11         if (!wakeInTheaterMode && theaterModeEnabled) {
12             return false;
13         }
14 
15         // Settings.Global.THEATER_MODE_ON:
16         if (theaterModeEnabled) {
17             Settings.Global.putInt(mContext.getContentResolver(),
18                     Settings.Global.THEATER_MODE_ON, 0);
19         }
20 
21         mPowerManager.wakeUp(wakeTime, reason);
22         return true;
23     }

    2、释放(ACTION_UP):

 1 private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {
 2         final boolean handled = canceled || mPowerKeyHandled;  //在interceptPowerKeyDown()中灭屏或长按 对应#上述interceptPowerKeyDown(...)代码中的第91行 mPowerKeyHandled=true
 3         mScreenshotChordPowerKeyTriggered = false;
 4         cancelPendingScreenshotChordAction();
 5         cancelPendingPowerKeyAction();   //取消长按事件---即500ms内未监听到释放,才执行长按事件
 6 
 7         if (!handled) { //亮屏 短按Power释放时执行此处##  因mPowerKeyHandled=false
 8             // Figure out how to handle the key now that it has been released.
 9             mPowerKeyPressCounter += 1;
10 
11             final int maxCount = getMaxMultiPressPowerCount();  //maxCount=1
12             final long eventTime = event.getDownTime();
13             if (mPowerKeyPressCounter < maxCount) {  //不成立
14                 // This could be a multi-press.  Wait a little bit longer to confirm.
15                 // Continue holding the wake lock.
16                 Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,
17                         interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);
18                 msg.setAsynchronous(true);
19                 mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout());
20                 return;
21             }
22 
23             // No other actions.  Handle it immediately.
24             powerPress(eventTime, interactive, mPowerKeyPressCounter);    //mPowerKeyPressCounter=1
25         }
26 
27         // Done.  Reset our state.
28         finishPowerKeyPress();   
29     }
30 
31     private void finishPowerKeyPress() {
32         mBeganFromNonInteractive = false;
33         mPowerKeyPressCounter = 0;
34         if (mPowerKeyWakeLock.isHeld()) {
35             mPowerKeyWakeLock.release();    //释放down事件时获得的锁
36         }
37     }
38 ......
39 
40     private void powerPress(long eventTime, boolean interactive, int count) {
41         if (mScreenOnEarly && !mScreenOnFully) {
42             Slog.i(TAG, "Suppressed redundant power key press while "
43                     + "already in the process of turning the screen on.");
44             return;
45         }
46 
47         if (count == 2) {
48             powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
49         } else if (count == 3) {
50             powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
51         } else if (interactive && !mBeganFromNonInteractive) {   //  mBeganFromNonInteractive=false
52             switch (mShortPressOnPowerBehavior) {  //mShortPressOnPowerBehavior : 配置为1
53                 case SHORT_PRESS_POWER_NOTHING:
54                     break;
55                 case SHORT_PRESS_POWER_GO_TO_SLEEP: //执行#
56                     goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
57                     break;
58           .......
59       }
60 } 
65

    (1)亮屏:       

      powerPress()--->goToSleep()--- >mPowerManager.goToSleep()...调用PowerManagerService 使系统睡眠。

    (2)灭屏:

      finishPowerKeyPress()...

    3、组合键(Power + 音量减):功能就是我们常用的屏幕截图的快捷方式。

  1     @Override
  2     public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
  3         if (!mSystemBooted) {
  4             // If we have not yet booted, don't let key events do anything.
  5             return 0;
  6         }
  7         
  8         ......//代码略
  9         final int keyCode = event.getKeyCode();
 10         // Basic policy based on interactive state.
 11         int result;
 12         
 13         // Handle special keys.
 14         switch (keyCode) {
 15             ......
 16             case KeyEvent.KEYCODE_VOLUME_MUTE: {
 17                 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {   //音量键减-     【Power + VOLUME_DOWN】截屏操作
 18                     if (down) {
 19                         if (interactive && !mScreenshotChordVolumeDownKeyTriggered
 20                                 && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
 21                             mScreenshotChordVolumeDownKeyTriggered = true;
 22                             mScreenshotChordVolumeDownKeyTime = event.getDownTime();
 23                             mScreenshotChordVolumeDownKeyConsumed = false;
 24                             cancelPendingPowerKeyAction();
 25                             interceptScreenshotChord();
 26                             interceptAccessibilityShortcutChord();
 27                         }
 28                     } else {
 29                         mScreenshotChordVolumeDownKeyTriggered = false;
 30                         cancelPendingScreenshotChordAction();
 31                         cancelPendingAccessibilityShortcutAction();
 32                     }
 33                 } else if(...){
 34                     ......
 35                 }
 36                 ......
 37                 break;    
 38             }
 39             case KeyEvent.KEYCODE_POWER: {      //POWER 键
 40                 if(SystemProperties.getBoolean("sys.requireKey", false)) break;
 41                 // Any activity on the power button stops the accessibility shortcut
 42                 cancelPendingAccessibilityShortcutAction();
 43                 result &= ~ACTION_PASS_TO_USER;        //不分发按键至应用
 44                 isWakeKey = false; // wake-up will be handled separately
 45                 if (down) {
 46                     interceptPowerKeyDown(event, interactive);   //在上述1中interceptPowerKeyDown()的第28行可见标记mScreenshotChordPowerKeyTriggered = true;
 47                 } else {
 48                     interceptPowerKeyUp(event, interactive, canceled);
 49                 }
 50                 break;
 51             }
 52             ......
 53         }
 54         
 55         ......
 56         return result;    
 57     }
 58     
 59     //截屏
 60     private void interceptScreenshotChord() {
 61         if (mScreenshotChordEnabled
 62                 && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered    //同时按下Volum_down + Power, 后执行的该方法
 63                 && !mA11yShortcutChordVolumeUpKeyTriggered) {
 64             final long now = SystemClock.uptimeMillis();
 65             if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
 66                     && now <= mScreenshotChordPowerKeyTime
 67                             + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {   //150ms
 68                 mScreenshotChordVolumeDownKeyConsumed = true;
 69                 cancelPendingPowerKeyAction();
 70                 mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
 71                 mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());   //根据是否在锁屏界面设置不同的延迟时间,了解即可
 72             }
 73         }
 74     }
 75     
 76     private class ScreenshotRunnable implements Runnable {
 77         private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
 78 
 79         public void setScreenshotType(int screenshotType) {
 80             mScreenshotType = screenshotType;
 81         }
 82 
 83         @Override
 84         public void run() {
 85             takeScreenshot(mScreenshotType); //#
 86         }
 87     }
 88     
 89     private static final String SYSUI_PACKAGE = "com.android.systemui";
 90     private static final String SYSUI_SCREENSHOT_SERVICE =
 91             "com.android.systemui.screenshot.TakeScreenshotService";
 92     
 93     private void takeScreenshot(final int screenshotType) {
 94         synchronized (mScreenshotLock) {
 95             if (mScreenshotConnection != null) {
 96                 return;
 97             }
 98             final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
 99                     SYSUI_SCREENSHOT_SERVICE);  //SystemUI中的截屏服务
100             final Intent serviceIntent = new Intent();
101             serviceIntent.setComponent(serviceComponent);
102             ServiceConnection conn = new ServiceConnection() {
103                 @Override
104                 public void onServiceConnected(ComponentName name, IBinder service) {
105                     synchronized (mScreenshotLock) {
106                         if (mScreenshotConnection != this) {
107                             return;
108                         }
109                         Messenger messenger = new Messenger(service);
110                         Message msg = Message.obtain(null, screenshotType);
111                         final ServiceConnection myConn = this;
112                         Handler h = new Handler(mHandler.getLooper()) {
113                             @Override
114                             public void handleMessage(Message msg) {
115                                 synchronized (mScreenshotLock) {
116                                     if (mScreenshotConnection == myConn) {
117                                         mContext.unbindService(mScreenshotConnection);
118                                         mScreenshotConnection = null;
119                                         mHandler.removeCallbacks(mScreenshotTimeout);
120                                     }
121                                 }
122                             }
123                         };
124                         msg.replyTo = new Messenger(h);
125                         msg.arg1 = msg.arg2 = 0;
126                         if (mStatusBar != null && mStatusBar.isVisibleLw())
127                             msg.arg1 = 1;
128                         if (mNavigationBar != null && mNavigationBar.isVisibleLw())
129                             msg.arg2 = 1;
130                         try {
131                             messenger.send(msg);
132                         } catch (RemoteException e) {
133                         }
134                     }
135                 }
136 
137                 @Override
138                 public void onServiceDisconnected(ComponentName name) {
139                     synchronized (mScreenshotLock) {
140                         if (mScreenshotConnection != null) {
141                             mContext.unbindService(mScreenshotConnection);
142                             mScreenshotConnection = null;
143                             mHandler.removeCallbacks(mScreenshotTimeout);
144                             notifyScreenshotError();
145                         }
146                     }
147                 }
148             };
149             if (mContext.bindServiceAsUser(serviceIntent, conn,
150                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
151                     UserHandle.CURRENT)) {
152                 mScreenshotConnection = conn;
153                 mHandler.postDelayed(mScreenshotTimeout, 10000);
154             }
155         }
156     }     

    结合上述代码,可概括流程为:    

    interceptScreenshotChord():----->启动线程ScreenshotRunnable---->takeScreenshot()----可看出真正的截图操作是在SystemUI中.(感兴趣的话,可自行研究)。

    将上述代码执行细节整理出下述流程图:(在线作图:https://www.processon.com/)查看原图

三、总结:

    由上文可知,Android系统响应Power键的情况可总述为以下内容:

    按下Down:

      (1)亮屏长按----弹出操作框(关机、重启.....)

      (2)灭屏短按----wakeUp唤醒屏幕

    释放Up:

      (1)亮屏长按不处理

      (2)灭屏不处理

      (3)亮屏短按----gotoSleep

    暂时就介绍到这里吧,若有什么不对的地方,欢迎交流指正,一起学习进步!  

 

posted on 2019-07-18 15:23  Android之路  阅读(5691)  评论(1编辑  收藏  举报