1. Brief description of off-screen
First of all, there are several ways to extinguish the screen:
1. The user presses the Power key to destroy the screen;
2. The mobile phone will go out of the screen after being automatically placed for a period of time;
3.Sensor goes off screen.
The reasons for the screen failure are common in the sleepReasonToString method in the PowerManager class:
/** * @hide */ public static String sleepReasonToString(int sleepReason) { switch (sleepReason) { case GO_TO_SLEEP_REASON_APPLICATION: return "application"; case GO_TO_SLEEP_REASON_DEVICE_ADMIN: return "device_admin"; case GO_TO_SLEEP_REASON_TIMEOUT: return "timeout"; case GO_TO_SLEEP_REASON_LID_SWITCH: return "lid_switch"; case GO_TO_SLEEP_REASON_POWER_BUTTON: return "power_button"; case GO_TO_SLEEP_REASON_HDMI: return "hdmi"; case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button"; case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility"; case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend"; default: return Integer.toString(sleepReason); } }
When the screen goes out, it is actually the goToSleep method in PowerManager that is called.
There are two ways to do this: one is to only pass on the time at which the sleep request was made; the other is to pass on three parameters: the time at which the sleep request was made, the reason why the screen went out, and the flag of WakeLock:
public void goToSleep(long time) { goToSleep(time, GO_TO_SLEEP_REASON_APPLICATION, 0); } @UnsupportedAppUsage public void goToSleep(long time, int reason, int flags) { try { mService.goToSleep(time, reason, flags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
The final call to the goToSleep method of PowerManagerSevice is:
@Override // Binder call public void goToSleep(long eventTime, int reason, int flags) { if (eventTime > SystemClock.uptimeMillis()) { throw new IllegalArgumentException("event time must not be in the future"); } // Check DEVICE_POWER permissions mContext.enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); // Thread id final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { goToSleepInternal(eventTime, reason, flags, uid); } finally { Binder.restoreCallingIdentity(ident); } }
Next, look at the implementation of goToSleepInternal:
private void goToSleepInternal(long eventTime, int reason, int flags, int uid) { // Add Lock synchronized (mLock) { // Determine the return value of goToSleepNoUpdateLocked if (goToSleepNoUpdateLocked(eventTime, reason, flags, uid)) { // Update Powerstate updatePowerStateLocked(); } } }
First look at goToSleepNoUpdateLocked:
@SuppressWarnings("deprecation") private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) { if (DEBUG_SPEW) { Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime + ", reason=" + reason + ", flags=" + flags + ", uid=" + uid); } if (eventTime < mLastWakeTime || mWakefulness == WAKEFULNESS_ASLEEP || mWakefulness == WAKEFULNESS_DOZING || !mBootCompleted || !mSystemReady) { return false; } Trace.traceBegin(Trace.TRACE_TAG_POWER, "goToSleep"); try { // Reasons for Getting Screen Out reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX, Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN)); Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason) + " (uid " + uid + ")..."); mLastSleepTime = eventTime; mLastSleepReason = reason; mSandmanSummoned = true; // Notify device to change wake-up state setWakefulnessLocked(WAKEFULNESS_DOZING, reason, eventTime); // Report the number of wake locks that will be cleared by going to sleep. // Report the number of wake-up locks that will be cleared by sleep int numWakeLocksCleared = 0; final int numWakeLocks = mWakeLocks.size(); for (int i = 0; i < numWakeLocks; i++) { final WakeLock wakeLock = mWakeLocks.get(i); switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.FULL_WAKE_LOCK: case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: case PowerManager.SCREEN_DIM_WAKE_LOCK: numWakeLocksCleared += 1; break; } } EventLogTags.writePowerSleepRequested(numWakeLocksCleared); // Skip dozing if requested. // Skip dozing status if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) { // Call reallyGoToSleepNoUpdateLocked function reallyGoToSleepNoUpdateLocked(eventTime, uid); } } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; }
Finally, look at the updatePowerStateLocked method, which is the core method of PowerManagerService. Key places where Google has a commentary explaining the source implementation:
private void updatePowerStateLocked() { if (!mSystemReady || mDirty == 0) { return; } if (!Thread.holdsLock(mLock)) { Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked"); } Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState"); try { // Phase 0: Basic state updates. // Stage 0: Update of underlying status // Update the value of mIsPowered, ture indicates the device is plugged in updateIsPoweredLocked(mDirty); // Update the value of mStayOn, true means the device should remain open updateStayOnLocked(mDirty); // Update screen brightness updateScreenBrightnessBoostLocked(mDirty); // Phase 1: Update wakefulness. // Loop because the wake lock and user activity computations are influenced // by changes in wakefulness. // Stage 1: Update wake-up status final long now = SystemClock.uptimeMillis(); int dirtyPhase2 = 0; // Dead cycle for (;;) { int dirtyPhase1 = mDirty; dirtyPhase2 |= dirtyPhase1; mDirty = 0; // Update the value of mWakeLockSummary to summarize the status of all active wake-up locks updateWakeLockSummaryLocked(dirtyPhase1); // Update the value of mUserActivitySummary to summarize the requested user updateUserActivitySummaryLocked(now, dirtyPhase1); // Update the wakefulness status of the device if true is returned indicating a change in wakefulness if (!updateWakefulnessLocked(dirtyPhase1)) { break; } } // Phase 2: Lock profiles that became inactive/not kept awake. // Stage 2: Lock disabled/unawakened profiles // Check profile timeout and notify profile that should be locked updateProfilesLocked(now); // Phase 3: Update display power state. // Stage 3: Update display status // Update display status asynchronously // After the update is complete, mDisplayReady will be set to true.Method returns true, indicating that the display is ready final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2); // Phase 4: Update dream state (depends on display ready signal). // Stage 4: Update the dream status (depending on the ready signal being displayed) // Asynchronous Update updateDreamLocked(dirtyPhase2, displayBecameReady); // Phase 5: Send notifications, if needed. // Stage 5: Send notifications if necessary finishWakefulnessChangeIfNeededLocked(); // Phase 6: Update suspend blocker. // Because we might release the last suspend blocker here, we need to make sure // we finished everything else first! // Update Pause Blocker // Update the pause blocker that keeps the CPU active updateSuspendBlockerLocked(); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } }
2. Common ways to extinguish screens
1.Power key goes off screen
Power keys implement the entire process from the native layer to the java layer, which we will follow.This starts from above the java layer.
The flowchart is as follows:
Specific code implementation analysis is as follows:
IntereptKeyBeforeQueueing method in InputManagerService class:
// Native callback. private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags); }
Call the interceptKeyBeforeQueueing method of the WindowManagerCallbacks interface to see that the interface implements the class InputMonitor:
@Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags); }
Call the interceptKeyBeforeQueueing method of the interface WindowManagerPolicy to see that the implementation class is the PhoneWindowManager class:
@Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { if (!mSystemBooted) { // If we have not yet booted, don't let key events do anything. return 0; } ...... // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_BACK: { if (down) { interceptBackKeyDown(); } else { boolean handled = interceptBackKeyUp(event); // Don't pass back press to app if we've already handled it via long press if (handled) { result &= ~ACTION_PASS_TO_USER; } } break; } ...... case KeyEvent.KEYCODE_POWER: { EventLogTags.writeInterceptPower( KeyEvent.actionToString(event.getAction()), mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter); // Any activity on the power button stops the accessibility shortcut cancelPendingAccessibilityShortcutAction(); result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately if (down) { interceptPowerKeyDown(event, interactive); } else { interceptPowerKeyUp(event, interactive, canceled); } break; } ...... return result; }
Call the interceptPowerKeyUp method based on the key value of the key key:
private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) { ...... if (!handled) { ...... // No other actions. Handle it immediately. powerPress(eventTime, interactive, mPowerKeyPressCounter); } // Done. Reset our state. finishPowerKeyPress(); }
The powerPress method is called here:
private void powerPress(long eventTime, boolean interactive, int count) { if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) { Slog.i(TAG, "Suppressed redundant power key press while " + "already in the process of turning the screen on."); return; } Slog.d(TAG, "powerPress: eventTime=" + eventTime + " interactive=" + interactive + " count=" + count + " beganFromNonInteractive=" + mBeganFromNonInteractive + " mShortPressOnPowerBehavior=" + mShortPressOnPowerBehavior); if (count == 2) { powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior); } else if (count == 3) { powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior); } else if (interactive && !mBeganFromNonInteractive) { switch (mShortPressOnPowerBehavior) { case SHORT_PRESS_POWER_NOTHING: break; case SHORT_PRESS_POWER_GO_TO_SLEEP: goToSleepFromPowerButton(eventTime, 0); break; case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP: goToSleepFromPowerButton(eventTime, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); break; case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME: if (goToSleepFromPowerButton(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(); } } else { shortPressPowerGoHome(); } break; } } } }
Judge the value of mShortPressOnPowerBehavior, which is taken from config_shortPressOnPowerBehavior in config.xml (framework/base/core/res/values/config.xml):
<!-- Control the behavior when the user short presses the power button. 0 - Nothing 1 - Go to sleep (doze) 2 - Really go to sleep (don't doze) 3 - Really go to sleep and go home (don't doze) 4 - Go to home 5 - Dismiss IME if shown. Otherwise go to home --> <integer name="config_shortPressOnPowerBehavior">1</integer>
So with a value of 1, the SHORT_PRESS_POWER_GO_TO_SLEEP is used in the switch statement to call the goToSleepFromPowerButton method:
private boolean goToSleepFromPowerButton(long eventTime, int flags) { // Before we actually go to sleep, we check the last wakeup reason. // If the device very recently woke up from a gesture (like user lifting their device) // then ignore the sleep instruction. This is because users have developed // a tendency to hit the power button immediately when they pick up their device, and we // don't want to put the device back to sleep in those cases. // Check the reason for the last wake-up final PowerManager.WakeData lastWakeUp = mPowerManagerInternal.getLastWakeup(); if (lastWakeUp != null && lastWakeUp.wakeReason == PowerManager.WAKE_REASON_GESTURE) { final int gestureDelayMillis = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.POWER_BUTTON_SUPPRESSION_DELAY_AFTER_GESTURE_WAKE, POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS); final long now = SystemClock.uptimeMillis(); if (mPowerButtonSuppressionDelayMillis > 0 && (now < lastWakeUp.wakeTime + mPowerButtonSuppressionDelayMillis)) { Slog.i(TAG, "Sleep from power button suppressed. Time since gesture: " + (now - lastWakeUp.wakeTime) + "ms"); return false; } } goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, flags); return true; }
Next, call the goToSleep method:
private void goToSleep(long eventTime, int reason, int flags) { mRequestedOrGoingToSleep = true; mPowerManager.goToSleep(eventTime, reason, flags); }
Here you can see that the reason passed in is GO_TO_SLEEP_REASON_POWER_BUTTON, which eventually calls the goToSleep method in PowerManager.The subsequent call process is the same as above.
2. Time-out
The system has a Screen Timeout option in the settings to set the system's out-of-screen time.When the extinction time arrives, the lower level will go to dim the screen.We won't discuss this process for now, but look at the process after the screen is dimmed:
As you can see from the description above, when PowerManager Service is started, it will eventually be called on the core function updatePowerStateLocked, which monitors information such as screen brightness in real time.A key function, updateUserActivitySummaryLocked, is called here (which updates the value of mUserActivitySummary to summarize the status of user requests, such as whether the screen is bright or dark).
There are four screen states: Awake, Dream, Doze, Asleep
The flowchart is as follows:
The code is implemented as follows:
private void updateUserActivitySummaryLocked(long now, int dirty) { // Update the status of the user activity timeout timer. if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) { mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); long nextTimeout = 0; // Determine mWakefulness is in AWAKE or DREAMING or DOZING if (mWakefulness == WAKEFULNESS_AWAKE || mWakefulness == WAKEFULNESS_DREAMING || mWakefulness == WAKEFULNESS_DOZING) { // Maximum timeout and config_minimumScreenOffTimeout(10s) is -1 final long sleepTimeout = getSleepTimeoutLocked(); // Gets the screen timeout value, which is the set screen timeout value final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout); // Get screen dimming time, 6000ms final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager; final long nextProfileTimeout = getNextProfileTimeoutLocked(now); mUserActivitySummary = 0; // Assign the corresponding mUserActivitySummary value (0/2/4) if (mLastUserActivityTime >= mLastWakeTime) { nextTimeout = mLastUserActivityTime + screenOffTimeout - screenDimDuration; if (now < nextTimeout) { mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; } else { nextTimeout = mLastUserActivityTime + screenOffTimeout; if (now < nextTimeout) { mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM; } } } if (mUserActivitySummary == 0 && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) { nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout; if (now < nextTimeout) { if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT || mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) { mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; } else if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) { mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM; } } } if (mUserActivitySummary == 0) { if (sleepTimeout >= 0) { final long anyUserActivity = Math.max(mLastUserActivityTime, mLastUserActivityTimeNoChangeLights); if (anyUserActivity >= mLastWakeTime) { nextTimeout = anyUserActivity + sleepTimeout; if (now < nextTimeout) { mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; } } } else { mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; nextTimeout = -1; } } if (mUserActivitySummary != USER_ACTIVITY_SCREEN_DREAM && userInactiveOverride) { if ((mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0) { // Device is being kept awake by recent user activity if (nextTimeout >= now && mOverriddenTimeout == -1) { // Save when the next timeout would have occurred mOverriddenTimeout = nextTimeout; } } mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; nextTimeout = -1; } if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 && (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) == 0) { nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout); } if (nextProfileTimeout > 0) { nextTimeout = Math.min(nextTimeout, nextProfileTimeout); } if (mUserActivitySummary != 0 && nextTimeout >= 0) { scheduleUserInactivityTimeout(nextTimeout); } } else { mUserActivitySummary = 0; } if (DEBUG_SPEW) { Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness=" + PowerManagerInternal.wakefulnessToString(mWakefulness) + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout)); } } }
The scheduleUserInactivityTimeout method is called here:
private void scheduleUserInactivityTimeout(long timeMs) { final Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timeMs); }
Send an MSG_USER_ACTIVITY_TIMEOUT broadcast:
private final class PowerManagerHandler extends Handler { public PowerManagerHandler(Looper looper) { super(looper, null, true /*async*/); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_USER_ACTIVITY_TIMEOUT: handleUserActivityTimeout(); break; .... } }
The handleUserActivityTimeout method is then called:
private void handleUserActivityTimeout() { // runs on handler thread synchronized (mLock) { if (DEBUG_SPEW) { Slog.d(TAG, "handleUserActivityTimeout"); } mDirty |= DIRTY_USER_ACTIVITY; updatePowerStateLocked(); } }
The updatePowerStateLocked method is then called, looking directly at the key code:
// Phase 1: Update wakefulness. // Loop because the wake lock and user activity computations are influenced // by changes in wakefulness. final long now = SystemClock.uptimeMillis(); int dirtyPhase2 = 0; for (;;) { int dirtyPhase1 = mDirty; dirtyPhase2 |= dirtyPhase1; mDirty = 0; updateWakeLockSummaryLocked(dirtyPhase1); updateUserActivitySummaryLocked(now, dirtyPhase1); if (!updateWakefulnessLocked(dirtyPhase1)) { break; } }
See the implementation of updateWakefulnessLocked:
private boolean updateWakefulnessLocked(int dirty) { boolean changed = false; if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE | DIRTY_DOCK_STATE)) != 0) { if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) { if (DEBUG_SPEW) { Slog.d(TAG, "updateWakefulnessLocked: Bed time..."); } final long time = SystemClock.uptimeMillis(); if (shouldNapAtBedTimeLocked()) { changed = napNoUpdateLocked(time, Process.SYSTEM_UID); } else { changed = goToSleepNoUpdateLocked(time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID); } } } return changed; }
Here you will look at the return value of the shouldNapAtBedTimeLocked method, which is related to the screen saver equivalent set in your phone.Then we go back to false and go to goToSleepNoUpdateLocked:
@SuppressWarnings("deprecation") private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) { if (DEBUG_SPEW) { Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime + ", reason=" + reason + ", flags=" + flags + ", uid=" + uid); } if (eventTime < mLastWakeTime || mWakefulness == WAKEFULNESS_ASLEEP || mWakefulness == WAKEFULNESS_DOZING || !mBootCompleted || !mSystemReady) { return false; } Trace.traceBegin(Trace.TRACE_TAG_POWER, "goToSleep"); try { reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX, Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN)); Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason) + " (uid " + uid + ")..."); mLastSleepTime = eventTime; mLastSleepReason = reason; mSandmanSummoned = true; setWakefulnessLocked(WAKEFULNESS_DOZING, reason, eventTime); // Report the number of wake locks that will be cleared by going to sleep. int numWakeLocksCleared = 0; final int numWakeLocks = mWakeLocks.size(); for (int i = 0; i < numWakeLocks; i++) { final WakeLock wakeLock = mWakeLocks.get(i); switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.FULL_WAKE_LOCK: case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: case PowerManager.SCREEN_DIM_WAKE_LOCK: numWakeLocksCleared += 1; break; } } EventLogTags.writePowerSleepRequested(numWakeLocksCleared); // Skip dozing if requested. if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) { reallyGoToSleepNoUpdateLocked(eventTime, uid); } } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; }
reallyGoToSleepNoUpdateLocked will be called later here.The logic is similar to the power key being off-screen.
3.Psensor goes off screen
Proximity Sensor.During a mobile phone conversation (such as QQ, WeChat voice, etc.), when the mobile phone is close to a face, the screen will go off, so that users can not touch the screen causing misoperation, but also can save power.This requires interaction between the kernel and java layers, mainly involving kernel sensor, PowerManagerService, DisplayManagerService, and so on.
PhotonicModulator is the internal class of DisplayPowerState and is a thread used to update screen and backlight asynchronously
private final class PhotonicModulator extends Thread { private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off private static final int INITIAL_BACKLIGHT = -1; // unknown private final Object mLock = new Object(); ....... @Override public void run() { for (;;) { // Get pending change. final int state; final boolean stateChanged; final int backlight; final boolean backlightChanged; synchronized (mLock) { state = mPendingState; stateChanged = (state != mActualState); backlight = mPendingBacklight; backlightChanged = (backlight != mActualBacklight); if (!stateChanged) { // State changed applied, notify outer class. postScreenUpdateThreadSafe(); mStateChangeInProgress = false; } if (!backlightChanged) { mBacklightChangeInProgress = false; } if (!stateChanged && !backlightChanged) { try { mLock.wait(); } catch (InterruptedException ex) { } continue; } mActualState = state; mActualBacklight = backlight; } // Apply pending change. if (DEBUG) { Slog.d(TAG, "Updating screen state: state=" + Display.stateToString(state) + ", backlight=" + backlight); } mBlanker.requestDisplayState(state, backlight); } } }
At the end of the method, the mBlanker.requestDisplayState function is called, DisplayBlanker is an interface, requestDisplayState interface method.The implementation is in DisplayManagerService:
private final class LocalService extends DisplayManagerInternal { @Override public void initPowerManagement(final DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager) { synchronized (mSyncRoot) { DisplayBlanker blanker = new DisplayBlanker() { @Override public void requestDisplayState(int state, int brightness) { // The order of operations is important for legacy reasons. if (state == Display.STATE_OFF) { requestGlobalDisplayStateInternal(state, brightness); } callbacks.onDisplayStateChange(state); if (state != Display.STATE_OFF) { requestGlobalDisplayStateInternal(state, brightness); } } }; mDisplayPowerController = new DisplayPowerController( mContext, callbacks, handler, sensorManager, blanker); } mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATION); } }
Next, look at the definition of DisplayManagerInternal:
/** * Asynchronous callbacks from the power controller to the power manager service. */ public interface DisplayPowerCallbacks { void onStateChanged(); void onProximityPositive(); void onProximityNegative(); void onDisplayStateChange(int state); // one of the Display state constants void acquireSuspendBlocker(); void releaseSuspendBlocker(); }
This interface is implemented in PowerManager Service:
private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks = new DisplayManagerInternal.DisplayPowerCallbacks() { private int mDisplayState = Display.STATE_UNKNOWN; @Override public void onStateChanged() { synchronized (mLock) { mDirty |= DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED; updatePowerStateLocked(); } } @Override public void onProximityPositive() { synchronized (mLock) { mProximityPositive = true; mDirty |= DIRTY_PROXIMITY_POSITIVE; updatePowerStateLocked(); } } @Override public void onProximityNegative() { synchronized (mLock) { mProximityPositive = false; mDirty |= DIRTY_PROXIMITY_POSITIVE; userActivityNoUpdateLocked(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); updatePowerStateLocked(); } } @Override public void onDisplayStateChange(int state) { // This method is only needed to support legacy display blanking behavior // where the display's power state is coupled to suspend or to the power HAL. // The order of operations matters here. synchronized (mLock) { if (mDisplayState != state) { mDisplayState = state; if (state == Display.STATE_OFF) { if (!mDecoupleHalInteractiveModeFromDisplayConfig) { setHalInteractiveModeLocked(false); } if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) { setHalAutoSuspendModeLocked(true); } } else { if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) { setHalAutoSuspendModeLocked(false); } if (!mDecoupleHalInteractiveModeFromDisplayConfig) { setHalInteractiveModeLocked(true); } } } } } ...... };
onDisplayStateChange method:
@Override public void onDisplayStateChange(int state) { // This method is only needed to support legacy display blanking behavior // where the display's power state is coupled to suspend or to the power HAL. // The order of operations matters here. synchronized (mLock) { if (mDisplayState != state) { mDisplayState = state; if (state == Display.STATE_OFF) { if (!mDecoupleHalInteractiveModeFromDisplayConfig) { setHalInteractiveModeLocked(false); } if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) { setHalAutoSuspendModeLocked(true); } } else { if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) { setHalAutoSuspendModeLocked(false); } if (!mDecoupleHalInteractiveModeFromDisplayConfig) { setHalInteractiveModeLocked(true); } } } } }
setHalInteractiveModeLocked is a native method.
The control of Psensor on and off in Dialer is through the ProximitySensor class.
Location is: /packages/apps/Dialer/java/com/android/incallui/ProximitySensor.java, initialization and other methods are not of particular concern, just look at his key method of controlling Psensor, updateProximitySensorMode:
private synchronized void updateProximitySensorMode() { Trace.beginSection("ProximitySensor.updateProximitySensorMode"); final int audioRoute = audioModeProvider.getAudioState().getRoute(); boolean screenOnImmediately = (CallAudioState.ROUTE_WIRED_HEADSET == audioRoute || CallAudioState.ROUTE_SPEAKER == audioRoute || CallAudioState.ROUTE_BLUETOOTH == audioRoute || isAttemptingVideoCall || isVideoCall || isRttCall); // We do not keep the screen off when the user is outside in-call screen and we are // horizontal, but we do not force it on when we become horizontal until the // proximity sensor goes negative. final boolean horizontal = (orientation == AccelerometerListener.ORIENTATION_HORIZONTAL); screenOnImmediately |= !uiShowing && horizontal; // We do not keep the screen off when dialpad is visible, we are horizontal, and // the in-call screen is being shown. // At that moment we're pretty sure users want to use it, instead of letting the // proximity sensor turn off the screen by their hands. screenOnImmediately |= dialpadVisible && horizontal; LogUtil.i( "ProximitySensor.updateProximitySensorMode", "screenOnImmediately: %b, dialPadVisible: %b, " + "offHook: %b, horizontal: %b, uiShowing: %b, audioRoute: %s", screenOnImmediately, dialpadVisible, isPhoneOffhook, orientation == AccelerometerListener.ORIENTATION_HORIZONTAL, uiShowing, CallAudioState.audioRouteToString(audioRoute)); if (isPhoneOffhook && !screenOnImmediately) { LogUtil.v("ProximitySensor.updateProximitySensorMode", "turning on proximity sensor"); // Phone is in use! Arrange for the screen to turn off // automatically when the sensor detects a close object. turnOnProximitySensor(); } else { LogUtil.v("ProximitySensor.updateProximitySensorMode", "turning off proximity sensor"); // Phone is either idle, or ringing. We don't want any special proximity sensor // behavior in either case. turnOffProximitySensor(screenOnImmediately); } Trace.endSection(); }
The corresponding method to control Psensor is turnOnProximitySensor/turnOffProximitySensor:
private void turnOnProximitySensor() { if (proximityWakeLock != null) { if (!proximityWakeLock.isHeld()) { LogUtil.i("ProximitySensor.turnOnProximitySensor", "acquiring wake lock"); proximityWakeLock.acquire(); } else { LogUtil.i("ProximitySensor.turnOnProximitySensor", "wake lock already acquired"); } } } private void turnOffProximitySensor(boolean screenOnImmediately) { if (proximityWakeLock != null) { if (proximityWakeLock.isHeld()) { LogUtil.i("ProximitySensor.turnOffProximitySensor", "releasing wake lock"); int flags = (screenOnImmediately ? 0 : PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY); proximityWakeLock.release(flags); } else { LogUtil.i("ProximitySensor.turnOffProximitySensor", "wake lock already released"); } } }
These two functions are based on the return value of the isHeld method to determine if a process currently holds a wake lock, and if it does not, an acquire/release lock will be applied for.
Another initial assignment for proximityWakeLock is in the initialization of ProximitySensor, which requests a wakeLock of type PROXIMITY_SCREEN_OFF_WAKE_LOCK (described in the wakeLock chapter that this lock closes the screen when the sensor is activated; the screen opens again shortly after the object is moved):
public ProximitySensor( @NonNull Context context, @NonNull AudioModeProvider audioModeProvider, @NonNull AccelerometerListener accelerometerListener) { Trace.beginSection("ProximitySensor.Constructor"); powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { proximityWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); } else { LogUtil.i("ProximitySensor.constructor", "Device does not support proximity wake lock."); proximityWakeLock = null; }
It then calls into the PowerManager Service, and when wakelock is requested and released, it eventually calls the core function updatePowerStateLocked, where the third step is to update the display state:
// Phase 3: Update display power state. final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
To see the implementation of updateDisplayPowerStateLocked:
private boolean updateDisplayPowerStateLocked(int dirty) { final boolean oldDisplayReady = mDisplayReady; if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED | DIRTY_QUIESCENT)) != 0) { mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(); // Determine appropriate screen brightness and auto-brightness adjustments. ....... // Update display power request. ....... mDisplayReady = mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest, mRequestWaitForNegativeProximity); mRequestWaitForNegativeProximity = false; ...... } return mDisplayReady && !oldDisplayReady; }
mDisplayPowerController object, view the requestPowerState method of DisplayPowerController:
public boolean requestPowerState(DisplayPowerRequest request, boolean waitForNegativeProximity) { if (DEBUG) { Slog.d(TAG, "requestPowerState: " + request + ", waitForNegativeProximity=" + waitForNegativeProximity); } synchronized (mLock) { boolean changed = false; ...... if (changed && !mPendingRequestChangedLocked) { mPendingRequestChangedLocked = true; sendUpdatePowerStateLocked(); } return mDisplayReadyLocked; } }
The sendUpdatePowerStateLocked method is executed here:
private void sendUpdatePowerStateLocked() { if (!mPendingUpdatePowerStateLocked) { mPendingUpdatePowerStateLocked = true; Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE); mHandler.sendMessage(msg); } }
By sending an MSG_UPDATE_POWER_STATE message:
private final class DisplayControllerHandler extends Handler { public DisplayControllerHandler(Looper looper) { super(looper, null, true /*async*/); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_POWER_STATE: updatePowerState(); break;
Execute the updatePowerState method:
private void updatePowerState() { // Update the power state request. final boolean mustNotify; final int previousPolicy; boolean mustInitialize = false; int brightnessAdjustmentFlags = 0; mBrightnessReasonTemp.set(null); ....... // Apply the proximity sensor. if (mProximitySensor != null) { // If it is not currently off screen if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) { // Register PSensor monitoring, Enable PSensor setProximitySensorEnabled(true); // Default mScreenOffBecauseOfProximity is false and PSensor is currently obscured if (!mScreenOffBecauseOfProximity && mProximity == PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = true; // Callback on onProximityPositive method of PowerManagerService sendOnProximityPositiveWithWakelock(); } } else if (mWaitingForNegativeProximity && mScreenOffBecauseOfProximity && mProximity == PROXIMITY_POSITIVE && state != Display.STATE_OFF) { // Device PSensor is blocked and screen is off during a call setProximitySensorEnabled(true); } else { setProximitySensorEnabled(false); mWaitingForNegativeProximity = false; } if (mScreenOffBecauseOfProximity && mProximity != PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = false; sendOnProximityNegativeWithWakelock(); } } else { mWaitingForNegativeProximity = false; } if (mScreenOffBecauseOfProximity) { state = Display.STATE_OFF; } ....... }
The part of the intercept method that involves Psensor, you can see that the setProximitySensorEnabled method is called:
private void setProximitySensorEnabled(boolean enable) { if (enable) { if (!mProximitySensorEnabled) { // Register the listener. // Proximity sensor state already cleared initially. mProximitySensorEnabled = true; mSensorManager.registerListener(mProximitySensorListener, mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler); } } else { if (mProximitySensorEnabled) { // Unregister the listener. // Clear the proximity sensor state for next time. mProximitySensorEnabled = false; mProximity = PROXIMITY_UNKNOWN; mPendingProximity = PROXIMITY_UNKNOWN; mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); mSensorManager.unregisterListener(mProximitySensorListener); clearPendingProximityDebounceTime(); // release wake lock (must be last) } } }
This is where you register an mProximitySensorListener listener.When the state of Psensor changes, the onSensorChanged method of Listener is called back:
private final SensorEventListener mProximitySensorListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (mProximitySensorEnabled) { final long time = SystemClock.uptimeMillis(); // Get the distance value from the Event, which is related to the driver report and may be different for different devices // distance = 1 is close; distance = 2 is far away final float distance = event.values[0]; boolean positive = distance >= 0.0f && distance < mProximityThreshold; handleProximitySensorEvent(time, positive); } } ...... }
Execute to handleProximitySensorEvent method:
private void handleProximitySensorEvent(long time, boolean positive) { if (mProximitySensorEnabled) { if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) { return; // no change } if (mPendingProximity == PROXIMITY_POSITIVE && positive) { return; // no change } // Only accept a proximity sensor reading if it remains // stable for the entire debounce delay. We hold a wake lock while // debouncing the sensor. mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); if (positive) { mPendingProximity = PROXIMITY_POSITIVE; setPendingProximityDebounceTime( time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock } else { mPendingProximity = PROXIMITY_NEGATIVE; setPendingProximityDebounceTime( time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock } // Debounce the new sensor reading. debounceProximitySensor(); } }
The debounceProximitySensor method returns to the call to the updatePowerState method:
private void debounceProximitySensor() { if (mProximitySensorEnabled && mPendingProximity != PROXIMITY_UNKNOWN && mPendingProximityDebounceTime >= 0) { final long now = SystemClock.uptimeMillis(); if (mPendingProximityDebounceTime <= now) { // Sensor reading accepted. Apply the change then release the wake lock. mProximity = mPendingProximity; updatePowerState(); clearPendingProximityDebounceTime(); // release wake lock (must be last) } else { // Need to wait a little longer. // Debounce again later. We continue holding a wake lock while waiting. Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED); mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime); } } }
Now back to the updatePowerState method, focus on the sendOnProximityPositiveWithWakelock method:
private void sendOnProximityPositiveWithWakelock() { mCallbacks.acquireSuspendBlocker(); mHandler.post(mOnProximityPositiveRunnable); } private final Runnable mOnProximityPositiveRunnable = new Runnable() { @Override public void run() { mCallbacks.onProximityPositive(); mCallbacks.releaseSuspendBlocker(); } };
This callback method will be implemented in PowerManager Service.The final call is to the updatePowerStateLocked method, the core function of PowerManagerService.
The updateSuspendBlockerLocked method is executed when PSensor has finished closing/lighting the screen:
private void updateSuspendBlockerLocked() { final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0); final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked(); final boolean autoSuspend = !needDisplaySuspendBlocker; final boolean interactive = mDisplayPowerRequest.isBrightOrDim(); // Disable auto-suspend if needed. // FIXME We should consider just leaving auto-suspend enabled forever since // we already hold the necessary wakelocks. if (!autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) { setHalAutoSuspendModeLocked(false); } // First acquire suspend blockers if needed. // acquire WakeLock, no deep sleep if WakeLock is not released in the system if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) { mWakeLockSuspendBlocker.acquire(); mHoldingWakeLockSuspendBlocker = true; } if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) { mDisplaySuspendBlocker.acquire(); mHoldingDisplaySuspendBlocker = true; } // Inform the power HAL about interactive mode. // Although we could set interactive strictly based on the wakefulness // as reported by isInteractive(), it is actually more desirable to track // the display policy state instead so that the interactive state observed // by the HAL more accurately tracks transitions between AWAKE and DOZING. // Refer to getDesiredScreenPolicyLocked() for details. if (mDecoupleHalInteractiveModeFromDisplayConfig) { // When becoming non-interactive, we want to defer sending this signal // until the display is actually ready so that all transitions have // completed. This is probably a good sign that things have gotten // too tangled over here... if (interactive || mDisplayReady) { setHalInteractiveModeLocked(interactive); } } // Then release suspend blockers if needed. // release WakeLock, which releases the WakeLock of the system so that it can hibernate normally if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) { mWakeLockSuspendBlocker.release(); mHoldingWakeLockSuspendBlocker = false; } if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) { mDisplaySuspendBlocker.release(); mHoldingDisplaySuspendBlocker = false; } // Enable auto-suspend if needed. if (autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) { setHalAutoSuspendModeLocked(true); } }
acquire/release calls methods in native:
@Override public void acquire() { synchronized (this) { mReferenceCount += 1; if (mReferenceCount == 1) { if (DEBUG_SPEW) { Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\"."); } Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0); mNativeWrapper.nativeAcquireSuspendBlocker(mName); } } } @Override public void release() { synchronized (this) { mReferenceCount -= 1; if (mReferenceCount == 0) { if (DEBUG_SPEW) { Slog.d(TAG, "Releasing suspend blocker \"" + mName + "\"."); } mNativeWrapper.nativeReleaseSuspendBlocker(mName); Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0); } else if (mReferenceCount < 0) { Slog.wtf(TAG, "Suspend blocker \"" + mName + "\" was released without being acquired!", new Throwable()); mReferenceCount = 0; } } }
jni layer: implementation in com_android_server_power_PowerManagerService.cpp:
static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) { ScopedUtfChars name(env, nameStr); acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str()); } static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) { ScopedUtfChars name(env, nameStr); release_wake_lock(name.c_str()); }
To sum up, this is what PowerManager Service knows about screen extinction.