This article is based on Android S source code.

Learning goals

Combine AOSP source code to understand the system flow of the screen after pressing the Power key.

Series of review

PowerManager Learning Notes -Power button Off process

Introduction to the Power screen

When the user presses the Power button, InputReader reads the event from EventHub and sends it to InputDispatcher for distribution. Then it informs PhoneWindowManager to process the event. After PhoneWindowManager processes, PowerManagerService is notified to conduct wakeUp processing. PowerManagerService then updates wakeUp internally and issues a bright screen broadcast. The DisplayPowerController is then notified to update the screen status. During this period, the DisplayPowerController blocks the screen based on the Window drawing status. After the drawing is complete, the DisplayPowerController animates the screen and sets the screen brightness.

Here’s a sequence of the entire Power screen:

TODO: There may be a class diagram missing, please fill it in later.

Here, if you just know about the Power button screen, you can not look down, the following is a detailed introduction of the important links in the specific business.

The business card

Related classes

  • frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
  • frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
  • frameworks/base/services/core/java/com/android/server/power/Notifier.java
  • frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java
  • frameworks/base/services/core/java/com/android/server/display/RampAnimator.java
  • frameworks/base/services/core/java/com/android/server/display/ColorFade.java
  • frameworks/base/services/core/java/com/android/server/display/DisplayPowerState.java
  • frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
  • frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java
  • frameworks/base/services/core/java/com/android/server/lights/LightsService.java

Partial class name explanation and abbreviation

The name of the class role abbreviations
PowerManagerService Power Status Management PMS
DisplayPowerController Manage the Display status of the device, mainly dealing with the close sensor, on and off screen DPC
Notifier The Power side is used to notify other system modules
RampAnimator Display side brightness animation
ColorFade High-end machines used to light up the screen mask

1. Power key event processing

This chapter describes the process when the Power button is pressed to light up the screen. Those of you who are familiar with the system must know that after pressing the Power button, the kernel will interrupt the event. After InputReader reads the event from EventHub, InputDispatcher will give PhoneWindowManager callback, the callback method is interceptKeyBeforeQueueing:

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java // TODO(b/117479243): handle it in InputPolicy /** { @inheritDoc } */ @Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { ... If ((event.getFlags() & keyevent.flag_fallback) == 0) {handleKeyGesture(event, interactiveAndOn); if (event.getFlags() & keyevent.flag_fallback) == 0) {handleKeyGesture(event, interactiveAndOn); }... // Handle special keys. switch (keyCode) { ... // Handle Power down and up events 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; }... }Copy the code

When the power key is pressed, the event is actually divided into Down and Up events, and the process with a bright screen triggers the down event processing method:

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java private void InterceptPowerKeyDown (KeyEvent Event, Boolean interactive) {// Get a wakelock, Hold a wake lock until the power key is released. If (! mPowerKeyWakeLock.isHeld()) { mPowerKeyWakeLock.acquire(); } mWindowManagerFuncs.onPowerKeyDown(interactive); // For incoming calls, // Stop ringing or end call if configured to do so when power is pressed getTelecommService(); boolean hungUp = false; if (telecomManager ! = null) { if (telecomManager.isRinging()) { // Pressing Power while there's a ringing incoming // call should silence the ringer. telecomManager.silenceRinger(); } else if ((mIncallPowerBehavior & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) ! = 0 && telecomManager.isInCall() && interactive) { // Otherwise, if "Power button ends call" is enabled, // the Power button will hang up any current active call. hungUp = telecomManager.endCall(); }}... // If 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) { // wake up start wakeUpFromPowerKey(event.getDownTime()); } } else { // handled by another power key policy. if (! mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) { mSingleKeyGestureDetector.reset(); } } } private void wakeUpFromPowerKey(long eventTime) { ... if (wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey, PowerManager.WAKE_REASON_POWER_BUTTON, "android.policy:POWER")) { // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout if (shouldWakeUpWithHomeIntent()) { startDockOrHome(DEFAULT_DISPLAY, /*fromHomeKey*/ false, /*wakenFromDreams*/ true, PowerManager.wakeReasonToString(PowerManager.WAKE_REASON_POWER_BUTTON)); } } } private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason, String details) { ... // call powermanagerservice wakeup mPowerManager.wakeUp(wakeTime, reason, details); return true; }Copy the code

To sum up the business flow of the Down event, when KEYCODE_POWER is received, in its down event, it first determines whether mPowerKeyWakeLock is held. If not, it applies for a wakelock. This prevents CPU from going to sleep during the service period. The wakeUp interface of PowerManager is called through a series of criteria.

The up event is relatively simple and consists of finishing up and releasing wakelock:

private void interceptPowerKeyUp(KeyEvent event, boolean canceled) { final boolean handled = canceled || mPowerKeyHandled; if (! handled) { if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == 0) { // Abort possibly stuck animations only when power key up without long press case. mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); } } else { // handled by single key or another power key policy. if (! mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) { mSingleKeyGestureDetector.reset(); } } finishPowerKeyPress(); } private void finishPowerKeyPress() { mPowerKeyHandled = false; if (mPowerKeyWakeLock.isHeld()) { mPowerKeyWakeLock.release(); }}Copy the code

2. PMS processing

This section describes how to process the screen light request in the PMS and how to notify the DPC to process the screen light request.

2.1 wakeUp

In the last section, the PhoneWindowManager notifies the PowerManager module to perform the screen light action through the wakeUp method on the PowerManager interface, where the final response is the wakeUp method in PowerManagerService:

frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

@Override // Binder call
public void wakeUp(long eventTime, @WakeReason int reason, String details,
        String opPackageName) {
    if (eventTime > mClock.uptimeMillis()) {
        throw new IllegalArgumentException("event time must not be in the future");
    }

    mContext.enforceCallingOrSelfPermission(
            android.Manifest.permission.DEVICE_POWER, null);

    final int uid = Binder.getCallingUid();
    final long ident = Binder.clearCallingIdentity();
    try {
        wakeDisplayGroup(Display.DEFAULT_DISPLAY_GROUP, eventTime, reason, details, uid,
                opPackageName, uid);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}
Copy the code

Here are the following two points:

1. A tiny parameters

As you can see, there is a reason argument in this function. PMS defines several reasons:

WAKE_REASON_UNKNOWN // unknown cause WAKE_REASON_POWER_BUTTON // Press the power key to wake up WAKE_REASON_APPLICATION // Apply WAKE_REASON_PLUGGED_IN // Insert WAKE_REASON_GESTURE WAKE_REASON_CAMERA_LAUNCH // Camera starts WAKE_REASON_WAKE_KEY // Wakeup key WAKE_REASON_WAKE_MOTION // Wakeup operation triggers WAKE_REASON_HDMI // HDMI wakeup WAKE_REASON_DISPLAY_GROUP_ADDED // Other Display areas are added WAKE_REASON_DISPLAY_GROUP_TURNED_ON // The other Display area turned_onCopy the code

2. Changes in R and S

On R, wakeUp of PMS is wakeUpInternal, while on S it is changed to wakeDisplayGroup. The specific change is to add the parameter DisplayGroupId, which is to increase the processing of screen lighting on multi-screen devices.

2.2 wakeDisplayGroup

In wakeUp, the PMS calls the internal method wakeDisplayGroup:

frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java private void wakeDisplayGroup(int groupId, long eventTime, @WakeReason int reason, String details, int uid, String opPackageName, Int opUid) {synchronized (mLock) {// Check whether the screen can be bright, set Wakefulness and related parameters, Send light screens broadcasting the if (wakeDisplayGroupNoUpdateLocked (groupId, eventTime, reason, details, uid, opPackageName, UpdatePowerStateLocked (); }}}Copy the code

There are two very important methods: wakeDisplayGroupNoUpdateLocked and updatePowerStateLocked.

2.3 Broadcast on screen

“Android. Intent. Action. SCREEN_ON” that many developers will surely not strange, this is when the screen is lit from the radio, the bright screen broadcast send a sequence diagram is as follows:

2.3.1 wakeDisplayGroupNoUpdateLocked

This function is mainly used to judge whether the screen is bright or not, and to set Wakefulness and related parameters:

frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java private boolean wakeDisplayGroupNoUpdateLocked(int groupId, long eventTime, @WakeReason int reason, String details, int uid, String opPackageName, int opUid) { ... / / the triggering time judgment is correct, whether the system hangs or is still on the if (eventTime < mLastSleepTime | | mForceSuspendActive | |! mSystemReady) { return false; }... / / according to Wakefulness, determine whether has bright screen final int currentState = mDisplayGroupPowerStateMapper. GetWakefulnessLocked (groupId); If (currentState == WAKEFULNESS_AWAKE) {// Special processing for non-system startup scenarios if (! mBootCompleted && sQuiescent) { mDirty |= DIRTY_QUIESCENT; return true; } return false; }... try { Slog.i(TAG, "Powering on display group from" + PowerManagerInternal.wakefulnessToString(currentState) + " (groupId=" + groupId + ", uid=" + uid + ", reason=" + PowerManager.wakeReasonToString(reason) + ", details=" + details + ")..." ); Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId); // Set Wakefulness setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid, opPackageName, details); / / set the light screen time and status to mDisplayGroupPowerStateMapper, DisplayGroupPowerStateMapper S new solution for the much screen Power management mDisplayGroupPowerStateMapper.setLastPowerOnTimeLocked(groupId, eventTime); mDisplayGroupPowerStateMapper.setPoweringOnLocked(groupId, true); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; }Copy the code

Wakefulness is an important variable in this function. PMS defines four states:

WAKEFULNESS_ASLEEP // WAKEFULNESS_AWAKE // WAKEFULNESS_DREAMING // WAKEFULNESS_DOZING // DoZE modeCopy the code

2.3.2 setWakefulnessLocked

frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java @VisibleForTesting void setWakefulnessLocked(int groupId, int wakefulness, long eventTime, int uid, int reason, int opUid, String opPackageName, String details) { if (mDisplayGroupPowerStateMapper.setWakefulnessLocked(groupId, Wakefulness)) {/ / introduce mDirty parameters, is used to determine whether the current Power State changes mDirty | = DIRTY_DISPLAY_GROUP_WAKEFULNESS; // Set the global parameter status, And the light screen work to prepare setGlobalWakefulnessLocked (mDisplayGroupPowerStateMapper. GetGlobalWakefulnessLocked (), eventTime, reason, uid, opUid, opPackageName, details); if (wakefulness == WAKEFULNESS_AWAKE) { // Kick user activity to prevent newly awake group from timing out instantly. userActivityNoUpdateLocked( groupId, eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); }}}Copy the code

The most important parameter in this function is the parameter mDirty, which is a very important flag bit in PMS. Through bit operation on it, we can know whether the current power state has changed, and corresponding business operations can be carried out according to the changed position.

The following dirtyStates are defined in the PMS:

// Dirty bit: mWakeLocks changed private static final int DIRTY_WAKE_LOCKS = 1 << 0; // Dirty bit: mWakefulness changed private static final int DIRTY_WAKEFULNESS = 1 << 1; // Dirty bit: user activity was poked or may have timed out private static final int DIRTY_USER_ACTIVITY = 1 << 2; // Dirty bit: actual display power state was updated asynchronously private static final int DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED = 1 < < 3; // Dirty bit: mBootCompleted changed private static final int DIRTY_BOOT_COMPLETED = 1 << 4; // Dirty bit: settings changed private static final int DIRTY_SETTINGS = 1 << 5; // Dirty bit: mIsPowered changed private static final int DIRTY_IS_POWERED = 1 << 6; // Dirty bit: mStayOn changed private static final int DIRTY_STAY_ON = 1 << 7; // Dirty bit: battery state changed private static final int DIRTY_BATTERY_STATE = 1 << 8; // Dirty bit: proximity state changed private static final int DIRTY_PROXIMITY_POSITIVE = 1 << 9; // Dirty bit: dock state changed private static final int DIRTY_DOCK_STATE = 1 << 10; // Dirty bit: brightness boost changed private static final int DIRTY_SCREEN_BRIGHTNESS_BOOST = 1 << 11; // Dirty bit: sQuiescent changed private static final int DIRTY_QUIESCENT = 1 << 12; // Dirty bit: VR Mode enabled changed private static final int DIRTY_VR_MODE_CHANGED = 1 << 13; // Dirty bit: attentive timer may have timed out private static final int DIRTY_ATTENTIVE = 1 << 14; // Dirty bit: display group wakefulness has changed private static final int DIRTY_DISPLAY_GROUP_WAKEFULNESS = 1 << 16;Copy the code

2.3.3 setGlobalWakefulnessLocked

frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java private void setGlobalWakefulnessLocked(int wakefulness, long eventTime, int reason, int uid, int opUid, String opPackageName, String details) { ... If (mNotifier! = null) { mNotifier.onWakefulnessChangeStarted(wakefulness, reason, eventTime); }... }Copy the code

In this method, mNotifier, another important member variable in PMS, is used. Its main function is to inform other core services or modules of the system of the current Power State.

2.3.4 Notifier. OnWakefulnessChangeStarted

frameworks/base/services/core/java/com/android/server/power/Notifier.java /** * Notifies that the device is changing wakefulness. * This function may be called even if the previous change hasn't finished in * which case it will assume that the state did not fully converge before the * next transition began and will recover accordingly. */ public void onWakefulnessChangeStarted(final int wakefulness, int reason, long eventTime) { final boolean interactive = PowerManagerInternal.isInteractive(wakefulness); if (DEBUG) { Slog.d(TAG, "onWakefulnessChangeStarted: wakefulness=" + wakefulness + ", reason=" + reason + ", interactive=" + interactive); } // Tell the activity manager about changes in wakefulness, // It needs more granularity than other components. // AMS Wakefulness change mHandler.post(new Runnable() { @Override public void run() { mActivityManagerInternal.onWakefulnessChanged(wakefulness); }}); . / / trigger radio messages handleEarlyInteractiveChange (); } } /** * Handle early interactive state changes such as getting applications or the lock * screen running and ready for  the user to see (such as when turning on the screen). */ private void handleEarlyInteractiveChange() { synchronized (mLock) { if (mInteractive) { // Waking up... Mhandler.post (() -> mpolicy.startedWakingUp (mInteractiveChangeReason)); // Send interactive broadcast. mPendingInteractiveState = INTERACTIVE_STATE_AWAKE; mPendingWakeUpBroadcast = true; / / send broadcast updatePendingBroadcastLocked (); } else { // Going to sleep... // Tell the policy that we started going to sleep. mHandler.post(() -> mPolicy.startedGoingToSleep(mInteractiveChangeReason)); } } } private void updatePendingBroadcastLocked() { if (! mBroadcastInProgress && mPendingInteractiveState ! = INTERACTIVE_STATE_UNKNOWN && (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast || mPendingInteractiveState ! = mBroadcastedInteractiveState)) { mBroadcastInProgress = true; // Apply SuspendBlocker msusPendblocker.acquire (); Message msg = mHandler.obtainMessage(MSG_BROADCAST); msg.setAsynchronous(true); mHandler.sendMessage(msg); }}Copy the code

Here finally through the message mechanism triggered the transmission of the bright screen broadcast, because the business is single, here will not post the code.

3. On the DPC side

3.1 the PMS. UpdatePowerStateLocked

In the previous section, in the PMS. WakeDisplayGroup after executing the wakeDisplayGroupNoUpdateLocked, in updatePowerStateLocked DPC side of business processing by:

frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java /** * Updates the global power state based on dirty bits recorded in mDirty. * * This is the main function that performs power state transitions. * We centralize them here so that we can recompute the power state completely * each time something important changes, and ensure that we do it the same * way each time. The point is to gather all of the transition logic here. */ private void updatePowerStateLocked() { ... // Phase 3: Update display power state. final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2); . } /** * Updates the display power state asynchronously. * When the update is finished, the ready state of the displays will be updated. The display * controllers post a message to tell us when the actual display power state * has been updated so we come back here to double-check and finish up. * * This function recalculates the display power state each time. * * @return { @code true} if all displays became ready; { @code false} otherwise */ private boolean updateDisplayPowerStateLocked(int dirty) { ... final boolean ready = mDisplayManagerInternal.requestPowerState(groupId, displayPowerRequest, mRequestWaitForNegativeProximity); . }Copy the code

This finally calls the requestPowerState method of mDisplayManagerInternal, which is an abstract class that implements DisplayManagerService, A call to the DPC is made in its overridden method. Interested can go to see the source code, here is not posted.

3.2 Blocked Screen on

In THE DPC, one of the most important services is the final preparation before the screen lights up. Why is the screen lights up? Because there is a very important link is blocking the bright screen, imagine if direct light screen, what may happen, first of all possible window has not drawn, drawing process, the user can watch the whole window or lock screen without drawing, so that the user experience is very poor, so in the light screen, we need to conduct a blocking light screen process.

frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java /** * Requests a new power state. * The controller makes a copy of the provided object and then * begins adjusting the power state to match what was requested. * * @param request The requested power state. * @param waitForNegativeProximity If true, issues a request to wait for * negative proximity before turning the screen back on, assuming the screen * was turned off by the proximity sensor. * @return True if display is ready, false if there are important changes that must * be made asynchronously (such as turning the screen on), in which case the caller * should grab a wake lock, watch for { @link DisplayPowerCallbacks#onStateChanged()} * then try the request again later until the state converges. */ public boolean requestPowerState(DisplayPowerRequest request, boolean waitForNegativeProximity) { ... if (changed) { mDisplayReadyLocked = false; if (! mPendingRequestChangedLocked) { mPendingRequestChangedLocked = true; sendUpdatePowerStateLocked(); } } return mDisplayReadyLocked; } } private void sendUpdatePowerStateLocked() { if (! mStopped && ! mPendingUpdatePowerStateLocked) { mPendingUpdatePowerStateLocked = true; MSG_UPDATE_POWER_STATE Message MSG = mhandler. obtainMessage(MSG_UPDATE_POWER_STATE); mHandler.sendMessage(msg); } } private void updatePowerState() { ... // Animate the screen state change unless already animating. // The transition may be deferred, so after this point we will use the // actual state instead of the desired one. final int oldState = mPowerState.getScreenState(); / / ready to perform the screen changes in animation, here also can be considered to be a bright screen animation animateScreenStateChange (state, performScreenOffTransition); state = mPowerState.getScreenState(); } private void animateScreenStateChange(int target, boolean performScreenOffTransition) { ... if (target == Display.STATE_ON) { // Want screen on. The contents of the screen may not yet // be visible if the color Fade has not been dismissed because // its last frame of animation is solid black. setScreenState(Display.STATE_ON)) { return; // screen on blocked } if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) { // Perform screen on animation. If (mPowerState getColorFadeLevel () = = 1.0 f) {mPowerState. DismissColorFade (); } else if (mPowerState.prepareColorFade(mContext, mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) { mColorFadeOnAnimator.start(); } else { mColorFadeOnAnimator.end(); }} else {/ / Skip screen on animation. MPowerState. SetColorFadeLevel (1.0 f); mPowerState.dismissColorFade(); }}... }Copy the code

As you can see from the last animateScreenStateChange, if setScreenState does not meet the requirements, it will return directly. This does not indicate the end of the process, but blocks the beginning of the screen:

frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java private boolean setScreenState(int state, boolean reportOnly) { ... if (! isOff && (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) { setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON); If (mPowerState getColorFadeLevel () = = 0.0 f) {/ / blocking light screen blockScreenOn (); } else { unblockScreenOn(); } / / to PhoneWindowManager incoming mPendingScreenOnUnblocker mWindowManagerPolicy. ScreenTurningOn (mDisplayId, mPendingScreenOnUnblocker); }... } private void blockScreenOn() { if (mPendingScreenOnUnblocker == null) { Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); mPendingScreenOnUnblocker = new ScreenOnUnblocker(); mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime(); Slog.i(TAG, "Blocking screen on until initial contents have been drawn."); } } private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener { @Override public void onScreenOn() { Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this); mHandler.sendMessage(msg); }}Copy the code

As you can see, in blockScreenOn, creates an instance of ScreenUnblocker mPendingScreenOnUnblocker, and passed to the WindowManagerPolicy PhoneWindowMangaer:

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java // Called on the DisplayManager's DisplayPowerController thread. @Override public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) { if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turning on..." ); if (displayId == DEFAULT_DISPLAY) { Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */); updateScreenOffSleepToken(false); mDefaultDisplayPolicy.screenTurnedOn(screenOnListener); mBootAnimationDismissable = false; synchronized (mLock) { if (mKeyguardDelegate ! = null && mKeyguardDelegate.hasKeyguard()) { mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT); / / send a message, and notify the lock screen finished drawing mHandler. SendEmptyMessageDelayed (MSG_KEYGUARD_DRAWN_TIMEOUT, getKeyguardDrawnTimeout ()); mKeyguardDelegate.onScreenTurningOn(mKeyguardDrawnCallback); } else { if (DEBUG_WAKEUP) Slog.d(TAG, "null mKeyguardDelegate: setting mKeyguardDrawComplete."); mHandler.sendEmptyMessage(MSG_KEYGUARD_DRAWN_COMPLETE); } } } else { mScreenOnListeners.put(displayId, screenOnListener); mWindowManagerInternal.waitForAllWindowsDrawn(() -> { if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId); mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, displayId, 0)); }, WAITING_FOR_DRAWN_TIMEOUT, displayId); }}Copy the code

After Keyguard is drawn, PhoneWindowManager calls onScreenOn Callback to notify the DPC. When the DPC receives onScreenOn, it sends a message unblock, which executes two functions unblockScreenOn and updatePowerState:

frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java private void unblockScreenOn()  { if (mPendingScreenOnUnblocker ! = null) {/ / here will mPendingScreenOnUnblocker buy become null mPendingScreenOnUnblocker = null; long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime; Slog.i(TAG, "Unblocked screen on after " + delay + " ms"); Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); }}Copy the code

Then again updatePowerState – animateScreenStateChange – setScreenState, finally, because in setScreenState mPendingScreenOnUnblocker = null, So true is returned, animateScreenStateChange can continue, and the process of blocking the bright screen is complete.

3.3 Screen Animation

As mentioned at the end of the previous section, when the blocking bright-screen process ends, the animateScreenStateChange in the original DPC can continue:

frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java private void animateScreenStateChange(int target, boolean performScreenOffTransition) { ... if (target == Display.STATE_ON) { // Want screen on. The contents of the screen may not yet // be visible if the color Fade has not been dismissed because // Its last frame of animation is solid black. setScreenState(Display.STATE_ON)) { return; // screen on blocked } if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) { // Perform screen on animation. If (mPowerState getColorFadeLevel () = = 1.0 f) {mPowerState. DismissColorFade (); } else if (mPowerState.prepareColorFade(mContext, mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) { mColorFadeOnAnimator.start(); } else { mColorFadeOnAnimator.end(); }} else {/ / Skip screen on animation. MPowerState. SetColorFadeLevel (1.0 f); mPowerState.dismissColorFade(); }}... }Copy the code

There’s an object called ColorFade, what is that? On high-end machines, in order to make the user’s on-off screen experience better, there is a mask that covers the top of the screen when the screen is on and off. By setting its alpha value, the gradual change effect can be realized when the screen is on and off.

Dpc.updatepowerstate block the brightness screen and perform the ColorFade fade. The last part of the brightness screen is to set the brightness of the screen. In updatePowerState the animateScreenBrightness screen was called.

frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java private void animateScreenBrightness(float target, float sdrTarget, float rate) { if (DEBUG) { Slog.d(TAG, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget + ", rate=" + rate); } if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) { Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target); // TODO(b/153319140) remove when we can get this from the above trace invocation SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target)); noteScreenBrightness(target); }}Copy the code

A brightness animation, through mScreenBrightnessRampAnimator mScreenBrightnessRampAnimator consists of DualRampAnimator instantiation, created in DPC initialization:

frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java private void initialize(int displayState) { ... mScreenBrightnessRampAnimator = new DualRampAnimator<>(mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT, DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT); mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener); . }Copy the code

There are two important arguments: displayPowerState.screen_brightness_float, displayPowerState.screen_sdr_brightness_float. This is one of the two arguments that will set the brightness through its setValue method. Here is from mScreenBrightnessRampAnimator animateTo for entrance to further analysis:

frameworks/base/services/core/java/com/android/server/display/RampAnimator.java /** * Starts animating towards the specified values. * * If this is the first time the property is being set or if the rate is 0, * the value jumps directly to the target. * * @param firstTarget The first target value. * @param secondTarget The second target value. * @param rate The convergence rate in units per second, or 0 to set the value immediately. * @return True if either target differs from the previous target. */ public boolean animateTo(float firstTarget, float secondTarget, float rate) { final boolean firstRetval = mFirst.animateTo(firstTarget, rate); final boolean secondRetval = mSecond.animateTo(secondTarget, rate); return firstRetval && secondRetval; } /** * Starts animating towards the specified value. * * If this is the first time the property is being set or if the rate is 0, * the value jumps directly to the target. * * @param target The target value. * @param rate The convergence rate in units per second, or 0 to set the value immediately. * @return True if the target differs from the previous target. */ public boolean animateTo(float target, float rate) { // Immediately jump to the target the first time. if (mFirstTime || rate <= 0) { if (mFirstTime || target ! = mCurrentValue) { mFirstTime = false; mRate = 0; mTargetValue = target; mCurrentValue = target; mProperty.setValue(mObject, target); if (mAnimating) { mAnimating = false; cancelAnimationCallback(); } if (mListener ! = null) { mListener.onAnimationEnd(); } return true; } return false; } // Adjust the rate based on the closest target. // If a faster rate is specified, then use the new rate so that we converge // more rapidly based on the new request. // If a slower rate is specified, then use the new rate only if the current // value is somewhere in between the new and the old target meaning that // we  will be ramping in a different direction to get there. // Otherwise, continue at the previous rate. if (! mAnimating || rate > mRate || (target <= mCurrentValue && mCurrentValue <= mTargetValue) || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) { mRate = rate; } final boolean changed = (mTargetValue ! = target); mTargetValue = target; // Start animating. if (! mAnimating && target ! = mCurrentValue) { mAnimating = true; mAnimatedValue = mCurrentValue; mLastFrameTimeNanos = System.nanoTime(); postAnimationCallback(); } return changed; }Copy the code

MFirst and mSecond are displayPowerState. SCREEN_BRIGHTNESS_FLOAT, displayPowerState. SCREEN_SDR_BRIGHTNESS_FLOAT. According to the comments in the code, the postAnimationCallback is eventually called:

frameworks/base/services/core/java/com/android/server/display/RampAnimator.java private void postAnimationCallback() { mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null); } private final Runnable mAnimationCallback = new Runnable() { @Override // Choreographer callback public void run() { final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); Final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos) * 0.000000001f; mLastFrameTimeNanos = frameTimeNanos; // Advance the animated value towards the target at the specified rate // and clamp to the target. This gives us the new  current value but // we keep the animated value around to allow for fractional increments // towards the target. final float scale = ValueAnimator.getDurationScale(); if (scale == 0) { // Animation off. mAnimatedValue = mTargetValue; } else { final float amount = timeDelta * mRate / scale; if (mTargetValue > mCurrentValue) { mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); } else { mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); } } final float oldCurrentValue = mCurrentValue; mCurrentValue = mAnimatedValue; If not, set the brightness again until the position if (oldCurrentValue! = mCurrentValue) { mProperty.setValue(mObject, mCurrentValue); } if (mTargetValue ! = mCurrentValue) { postAnimationCallback(); } else { mAnimating = false; if (mListener ! = null) { mListener.onAnimationEnd(); }}}};Copy the code

This section ends with mScreenUpdateRunnable, which introduces the mPhotonicModulator, an internal class in DisplayPowerState that inherits Thread. The implementation of mPhotonicModulator can be found in the run method:

frameworks/base/services/core/java/com/android/server/display/DisplayPowerState.java /** * Updates the state of the screen and backlight asynchronously on a separate thread. */ private final class PhotonicModulator extends Thread { ... public boolean setState(int state, float brightnessState, float sdrBrightnessState) { synchronized (mLock) { ... boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress; mStateChangeInProgress = stateChanged || mStateChangeInProgress; mBacklightChangeInProgress = backlightChanged || mBacklightChangeInProgress; // Wait if (! changeInProgress) { mLock.notifyAll(); } } return ! mStateChangeInProgress; }}... @Override public void run() { for (;;) { // Get pending change. final int state; final boolean stateChanged; final float brightnessState; final float sdrBrightnessState; final boolean backlightChanged; synchronized (mLock) { ... // wait for change if (! stateChanged && ! backlightChanged) { try { mLock.wait(); } catch (InterruptedException ex) { if (mStopped) { return; } } continue; }... }... mBlanker.requestDisplayState(mDisplayId, state, brightnessState, sdrBrightnessState); }}}Copy the code

Can see the end in the thread’s run method calls the mBlanker. RequestDisplayState, this mBlanker actually is the interface DisplayBlanker instance, in its final callback to DisplayManagerService:

frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java /** { @link DisplayBlanker} used by all { @link DisplayPowerController}s. */ private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() { // Synchronized to avoid race conditions when updating multiple display states. @Override public synchronized void requestDisplayState(int displayId, int state, float brightness, float sdrBrightness) { ... if (state ! = Display.STATE_OFF) { requestDisplayStateInternal(displayId, state, brightness, sdrBrightness); }}}; private void requestDisplayStateInternal(int displayId, int state, float brightnessState, float sdrBrightnessState) { ... // Update the display state within the lock. // Note that we do not need to schedule traversals here although it // may happen as a side-effect of displays changing state. final Runnable runnable; final String traceMessage; synchronized (mSyncRoot) { ... runnable = updateDisplayStateLocked(mLogicalDisplayMapper.getDisplayLocked(displayId) .getPrimaryDisplayDeviceLocked());  } // Setting the display power state can take hundreds of milliseconds // to complete so we defer the most expensive part of the work until // after we have exited the critical section to avoid blocking other // threads for a long time. if (runnable ! = null) { runnable.run(); }... } private Runnable updateDisplayStateLocked(DisplayDevice device) { ... // Only send a request for display state if display state has already been initialized. if (state ! = Display.STATE_UNKNOWN) { final BrightnessPair brightnessPair = mDisplayBrightnesses.get(displayId); return device.requestDisplayStateLocked(state, brightnessPair.brightness, brightnessPair.sdrBrightness); }}... }Copy the code

At the very end, we’re actually creating a runnable with updateDisplayStateLocked, so that’s where we’re going to set the brightness of the screen, Through to create a runnable requestDisplayStateLocked DisplayDevice, but DisplayDevice is an abstract class, the real implementation is in LocalDisplayAdapter:

frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java @Override public Runnable requestDisplayStateLocked(final int state, final float brightnessState, final float sdrBrightnessState) { ... // Defer actually setting the display state until after we have exited // the critical section since it can take hundreds of milliseconds // to complete. return new Runnable() { @Override public void run() { ... // Set the setDisplayBrightness(brightnessState, sdrBrightnessState); . } private void setDisplayBrightness(float brightnessState, float sdrBrightnessState) { ... mBacklightAdapter.setBacklight(sdrBacklight, sdrNits, backlight, nits); . } // Set backlight within min and max backlight values void setBacklight(float sdrBacklight, float sdrNits, float backlight, float nits) { if (mUseSurfaceControlBrightness || mForceSurfaceControl) { if (BrightnessSynchronizer.floatEquals( sdrBacklight, PowerManager.BRIGHTNESS_INVALID_FLOAT)) { mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, backlight); } else { mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, sdrBacklight, sdrNits, backlight, nits); } } else if (mBacklight ! = null) { mBacklight.setBrightness(backlight); }}Copy the code

In the DisplayManagerService, the last runnable was executed. According to the code read, the last Runnable was executed in the LocalDisplayAdapter mbacklight.setBrightness. The final response is in LightsService:

frameworks/base/services/core/java/com/android/server/lights/LightsService.java @Override public void setBrightness(float brightness) { setBrightness(brightness, BRIGHTNESS_MODE_USER); } @Override public void setBrightness(float brightness, int brightnessMode) { ... setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode); . } private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) { ... setLightUnchecked(color, mode, onMS, offMS, brightnessMode); . } private void setLightUnchecked(int color, int mode, int onMS, int offMS, int brightnessMode) { Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLightState(" + mHwLight.id + ", 0x" + Integer.toHexString(color) + ")"); try { if (mVintfLights ! = null) { HwLightState lightState = new HwLightState(); lightState.color = color; lightState.flashMode = (byte) mode; lightState.flashOnMs = onMS; lightState.flashOffMs = offMS; lightState.brightnessMode = (byte) brightnessMode; mVintfLights.get().setLightState(mHwLight.id, lightState); } else { setLight_native(mHwLight.id, color, mode, onMS, offMS, brightnessMode); } } catch (RemoteException | UnsupportedOperationException ex) { Slog.e(TAG, "Failed issuing setLightState", ex); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); }}Copy the code

As you can see, in LightsService, the JNI call to the underlying brightening screen is finally done. The Power button on the top of the screen has been cleared.