Android 电源键事件流程分析

Android 电源键事件流程分析

电源按键流程处理逻辑在 PhoneWindowManager.java类中的 dispatchUnhandledKey 方法中

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

从dispatchUnhandledKey方法开始分析

    @Override
    public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
        // Note: This method is only called if the initial down was unhandled.
        if (DEBUG_INPUT) {
            final KeyInterceptionInfo info =
                    mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
            final String title = info == null ? "<unknown>" : info.windowTitle;
            Slog.d(TAG, "Unhandled key: inputToken=" + focusedToken
                    + ", title=" + title
                    + ", action=" + event.getAction()
                    + ", flags=" + event.getFlags()
                    + ", keyCode=" + event.getKeyCode()
                    + ", scanCode=" + event.getScanCode()
                    + ", metaState=" + event.getMetaState()
                    + ", repeatCount=" + event.getRepeatCount()
                    + ", policyFlags=" + policyFlags);
        }

        KeyEvent fallbackEvent = null;
        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
            final KeyCharacterMap kcm = event.getKeyCharacterMap();
            final int keyCode = event.getKeyCode();
            final int metaState = event.getMetaState();
            final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
                    && event.getRepeatCount() == 0;

            // Check for fallback actions specified by the key character map.
            final FallbackAction fallbackAction;
            if (initialDown) {
                fallbackAction = kcm.getFallbackAction(keyCode, metaState);
            } else {
                fallbackAction = mFallbackActions.get(keyCode);
            }

            if (fallbackAction != null) {
                if (DEBUG_INPUT) {
                    Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode
                            + " metaState=" + Integer.toHexString(fallbackAction.metaState));
                }

                final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
                fallbackEvent = KeyEvent.obtain(
                        event.getDownTime(), event.getEventTime(),
                        event.getAction(), fallbackAction.keyCode,
                        event.getRepeatCount(), fallbackAction.metaState,
                        event.getDeviceId(), event.getScanCode(),
                        flags, event.getSource(), event.getDisplayId(), null);

                //核心代码
                if (!interceptFallback(focusedToken, fallbackEvent, policyFlags)) {
                    fallbackEvent.recycle();
                    fallbackEvent = null;
                }

                if (initialDown) {
                    mFallbackActions.put(keyCode, fallbackAction);
                } else if (event.getAction() == KeyEvent.ACTION_UP) {
                    mFallbackActions.remove(keyCode);
                    fallbackAction.recycle();
                }
            }
        }

        if (DEBUG_INPUT) {
            if (fallbackEvent == null) {
                Slog.d(TAG, "No fallback.");
            } else {
                Slog.d(TAG, "Performing fallback: " + fallbackEvent);
            }
        }
        return fallbackEvent;
    }

关于电源按键的核心逻辑在interceptFallback方法里的interceptKeyBeforeQueueing方法里

   @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        ...

        if (!mSystemBooted) {
            // If we have not yet booted, don't let key events do anything.
            // Exception: Wake and power key events are forwarded to PowerManager to allow it to
            // wake from quiescent mode during boot.
            if (down && (keyCode == KeyEvent.KEYCODE_POWER
                    || keyCode == KeyEvent.KEYCODE_TV_POWER)) {
                wakeUpFromPowerKey(event.getDownTime());
            } else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP)
                    && isWakeKeyWhenScreenOff(keyCode)) {
                wakeUpFromWakeKey(event);
            }
            return 0;
        }

        ....

        // Handle special keys.
        switch (keyCode) {
            ...

            case KeyEvent.KEYCODE_POWER: {
                EventLogTags.writeInterceptPower(
                        KeyEvent.actionToString(event.getAction()),
                        mPowerKeyHandled ? 1 : 0,
                        mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
                // Any activity on the power button stops the accessibility shortcut
                result &= ~ACTION_PASS_TO_USER;
                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    interceptPowerKeyDown(event, interactiveAndOn);
                } else {
                    interceptPowerKeyUp(event, canceled);
                }
                break;
            }
        	}
    	}
        return result;
    }

当按下的时候,执行interceptPowerKeyDown方法

private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
        // Hold a wake lock until the power key is released.
        if (!mPowerKeyWakeLock.isHeld()) {
            mPowerKeyWakeLock.acquire();
        }

        mWindowManagerFuncs.onPowerKeyDown(interactive);

        // Stop ringing or end call if configured to do so when power is pressed.
        TelecomManager telecomManager = getTelecommService();
        boolean hungUp = false;
        if (telecomManager != null) {
            if (telecomManager.isRinging()) {
                // Pressing Power while there's a ringing incoming
                // call should silence the ringer.
                telecomManager.silenceRinger();
            } else if ((mIncallPowerBehavior
                    & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                    && telecomManager.isInCall() && interactive) {
                // Otherwise, if "Power button ends call" is enabled,
                // the Power button will hang up any current active call.
                hungUp = telecomManager.endCall();
            }
        }

        final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);

        // Inform the StatusBar; but do not allow it to consume the event.
        sendSystemKeyToStatusBarAsync(event.getKeyCode());

        // If the power key has still not yet been handled, then detect short
        // press, long press, or multi press and decide what to do.
        mPowerKeyHandled = mPowerKeyHandled || hungUp
                || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
        if (!mPowerKeyHandled) {
            if (!interactive) {
                wakeUpFromPowerKey(event.getDownTime());
            }
        } else {
            // handled by another power key policy.
            if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
                mSingleKeyGestureDetector.reset();
            }
        }
    }

1、Android 按电源键亮屏/息屏流程

1、亮屏

1.1、PhoneWindowManager.java 中的 interceptKeyBeforeQueueing 方法
    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        // Handle special keys.
        switch (keyCode) {
            case KeyEvent.KEYCODE_POWER: {
                EventLogTags.writeInterceptPower(
                        KeyEvent.actionToString(event.getAction()),
                        mPowerKeyHandled ? 1 : 0,
                        mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
                // Any activity on the power button stops the accessibility shortcut
                result &= ~ACTION_PASS_TO_USER;
                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    // 按下power键调用
                    interceptPowerKeyDown(event, interactiveAndOn);
                } else {
                    interceptPowerKeyUp(event, canceled);
                }
                break;
            }
        }
    }
1.2、息屏时按下power键调用 interceptPowerKeyDown() 方法
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
        // Hold a wake lock until the power key is released.
        if (!mPowerKeyWakeLock.isHeld()) {
            mPowerKeyWakeLock.acquire();
        }

        mWindowManagerFuncs.onPowerKeyDown(interactive);

        // Stop ringing or end call if configured to do so when power is pressed.
        TelecomManager telecomManager = getTelecommService();
        boolean hungUp = false;
        if (telecomManager != null) {
            if (telecomManager.isRinging()) {
                // Pressing Power while there's a ringing incoming
                // call should silence the ringer.
                telecomManager.silenceRinger();
            } else if ((mIncallPowerBehavior
                    & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                    && telecomManager.isInCall() && interactive) {
                // Otherwise, if "Power button ends call" is enabled,
                // the Power button will hang up any current active call.
                hungUp = telecomManager.endCall();
            }
        }

        final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);

        // Inform the StatusBar; but do not allow it to consume the event.
        sendSystemKeyToStatusBarAsync(event.getKeyCode());

        // If the power key has still not yet been handled, then detect short
        // press, long press, or multi press and decide what to do.
        mPowerKeyHandled = mPowerKeyHandled || hungUp
                || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
        if (!mPowerKeyHandled) {
            if (!interactive) {
                // 表示当前未交互或者息屏状态下
                wakeUpFromPowerKey(event.getDownTime());
            }
        } else {
            // handled by another power key policy.
            if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
                mSingleKeyGestureDetector.reset();
            }
        }
    }

1.3、wakeUpFromPowerKey()

可以看到,在按下电源键的时候,如果判断,当前是息屏状态,则执行wakeUpFromPowerKey()方法,

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));
        }
    }
}

调用 wakeUp() 方法

private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason,
        String details) {
    final boolean theaterModeEnabled = isTheaterModeEnabled();
    if (!wakeInTheaterMode && theaterModeEnabled) {
        return false;
    }

    if (theaterModeEnabled) {
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.THEATER_MODE_ON, 0);
    }
	
    //核心代码
    mPowerManager.wakeUp(wakeTime, reason, details);
    return true;
}
1.4、PowerManager 中的 WakeUp() 方法
// android.os.PowerManager
public void wakeUp(long time, @WakeReason int reason, String details) {
    try {
        mService.wakeUp(time, reason, details, mContext.getOpPackageName());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
1.5、PowerManagerServicewakeUp() 方法

通过跨进程通信,我们知道真正的操作是在PowerManagerService类里的wakeUp()方法

@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);
    }
}

调用 wakeDisplayGroup() 方法

private void wakeDisplayGroup(int groupId, long eventTime, @WakeReason int reason,
        String details, int uid, String opPackageName, int opUid) {
    synchronized (mLock) {
        if (wakeDisplayGroupNoUpdateLocked(groupId, eventTime, reason, details, uid,
                opPackageName, opUid)) {
            updatePowerStateLocked();
        }
    }
}
1.6、先分析 wakeDisplayGroupNoUpdateLocked() 方法,如果它返回true,则执行updatePowerStateLocked方法
private boolean wakeDisplayGroupNoUpdateLocked(int groupId, long eventTime,
        @WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
    if (DEBUG_SPEW) {
        Slog.d(TAG, "wakeDisplayGroupNoUpdateLocked: eventTime=" + eventTime
                + ", groupId=" + groupId + ", uid=" + uid);
    }

    if (eventTime < mLastSleepTime || mForceSuspendActive || !mSystemReady) {
        return false;
    }

    final int currentState = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
    if (currentState == WAKEFULNESS_AWAKE) {
        if (!mBootCompleted && sQuiescent) {
            mDirty |= DIRTY_QUIESCENT;
            return true;
        }
        return false;
    }

    Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOnDisplay");
    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);

        //更新屏幕亮屏超时时间
        setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
                opPackageName, details);
        mDisplayGroupPowerStateMapper.setLastPowerOnTimeLocked(groupId, eventTime);
        mDisplayGroupPowerStateMapper.setPoweringOnLocked(groupId, true);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_POWER);
    }

    return true;
}

先来看看setWakefulnessLocked方法

@VisibleForTesting
void setWakefulnessLocked(int groupId, int wakefulness, long eventTime, int uid, int reason,
        int opUid, String opPackageName, String details) {
    if (mDisplayGroupPowerStateMapper.setWakefulnessLocked(groupId, wakefulness)) {
        mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
        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);
        }
    }
}

setGlobalWakefulnessLocked 方法

private void setGlobalWakefulnessLocked(int wakefulness, long eventTime, int reason, int uid,
        int opUid, String opPackageName, String details) {
    if (getWakefulnessLocked() == wakefulness) {
        return;
    }

    // Phase 1: Handle pre-wakefulness change bookkeeping.
    final String traceMethodName;
    switch (wakefulness) {
        case WAKEFULNESS_ASLEEP:
            traceMethodName = "reallyGoToSleep";
            Slog.i(TAG, "Sleeping (uid " + uid + ")...");
            break;

        case WAKEFULNESS_AWAKE:
            traceMethodName = "wakeUp";
            Slog.i(TAG, "Waking up from "
                    + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked())
                    + " (uid=" + uid
                    + ", reason=" + PowerManager.wakeReasonToString(reason)
                    + ", details=" + details
                    + ")...");
            mLastWakeTime = eventTime;
            mLastWakeReason = reason;
            break;

        case WAKEFULNESS_DREAMING:
            traceMethodName = "nap";
            Slog.i(TAG, "Nap time (uid " + uid + ")...");
            break;

        case WAKEFULNESS_DOZING:
            traceMethodName = "goToSleep";
            Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
                    + " (uid " + uid + ")...");

            mLastSleepTime = eventTime;
            mLastSleepReason = reason;
            mDozeStartInProgress = true;
            break;

        default:
            throw new IllegalArgumentException("Unexpected wakefulness: " + wakefulness);
    }

    Trace.traceBegin(Trace.TRACE_TAG_POWER, traceMethodName);
    try {
        // Phase 2: Handle wakefulness change and bookkeeping.
        // Under lock, invalidate before set ensures caches won't return stale values.
        mInjector.invalidateIsInteractiveCaches();
        mWakefulnessRaw = wakefulness;
        mWakefulnessChanging = true;
        mDirty |= DIRTY_WAKEFULNESS;

        // This is only valid while we are in wakefulness dozing. Set to false otherwise.
        mDozeStartInProgress &= (getWakefulnessLocked() == WAKEFULNESS_DOZING);

        if (mNotifier != null) {
            mNotifier.onWakefulnessChangeStarted(wakefulness, reason, eventTime);
        }
        mAttentionDetector.onWakefulnessChangeStarted(wakefulness);

        // Phase 3: Handle post-wakefulness change bookkeeping.
        switch (wakefulness) {
            case WAKEFULNESS_AWAKE:
                mNotifier.onWakeUp(reason, details, uid, opPackageName, opUid);
                if (sQuiescent) {
                    mDirty |= DIRTY_QUIESCENT;
                }
                break;

            case WAKEFULNESS_DOZING:
                // 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);
                break;
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_POWER);
    }
}

继续往下追,查看Notifier类里的onWakefulnessChangeStarted方法

//com.android.server.power.Notifier
/**
 * 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, not just interactivity.
    // It needs more granularity than other components.
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            mActivityManagerInternal.onWakefulnessChanged(wakefulness);
        }
    });

    // Handle any early interactive state changes.
    // Finish pending incomplete ones from a previous cycle.
    if (mInteractive != interactive) {
        // Finish up late behaviors if needed.
        if (mInteractiveChanging) {
            handleLateInteractiveChange();
        }

        // Start input as soon as we start waking up or going to sleep.
        mInputManagerInternal.setInteractive(interactive);
        mInputMethodManagerInternal.setInteractive(interactive);

        // Notify battery stats.
        try {
            mBatteryStats.noteInteractive(interactive);
        } catch (RemoteException ex) { }
        FrameworkStatsLog.write(FrameworkStatsLog.INTERACTIVE_STATE_CHANGED,
                interactive ? FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON :
                        FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__OFF);

        // Handle early behaviors.
        mInteractive = interactive;
        mInteractiveChangeReason = reason;
        mInteractiveChangeStartTime = eventTime;
        mInteractiveChanging = true;
        handleEarlyInteractiveChange();
    }
}

继续看 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;
            updatePendingBroadcastLocked();
        } else {
            // Going to sleep...
            // Tell the policy that we started going to sleep.
            mHandler.post(() -> mPolicy.startedGoingToSleep(mInteractiveChangeReason));
        }
    }
}

继续看 updatePendingBroadcastLocked 方法

private void updatePendingBroadcastLocked() {
    if (!mBroadcastInProgress
            && mPendingInteractiveState != INTERACTIVE_STATE_UNKNOWN
            && (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
                    || mPendingInteractiveState != mBroadcastedInteractiveState)) {
        mBroadcastInProgress = true;
        mSuspendBlocker.acquire();
        //发送广播
        Message msg = mHandler.obtainMessage(MSG_BROADCAST);
        msg.setAsynchronous(true);
        mHandler.sendMessage(msg);
    }
}

继续看 Handler 调用的方法

private final class NotifierHandler extends Handler {

    public NotifierHandler(Looper looper) {
        super(looper, null, true /*async*/);
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ...
            case MSG_BROADCAST:
                sendNextBroadcast();
                break;
            ....
        }
    }
}

在 sendNextBroadcast() 方法中 调用了 sendWakeUpBroadcast() 方法

private void sendWakeUpBroadcast() {
    if (DEBUG) {
        Slog.d(TAG, "Sending wake up broadcast.");
    }

    if (mActivityManagerInternal.isSystemReady()) {
        mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
                mWakeUpBroadcastDone, mHandler, 0, null, null);
    } else {
        EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
        sendNextBroadcast();
    }
}
1.7、在 分析 updatePowerStateLocked 方法,此方法目的是更新PackageManagerService全局状态
/**
 * 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() {
    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.
        updateIsPoweredLocked(mDirty);
        updateStayOnLocked(mDirty);
        updateScreenBrightnessBoostLocked(mDirty);

        // Phase 1: Update wakefulness.
        // Loop because the wake lock and user activity computations are influenced
        // by changes in wakefulness.
        final long now = mClock.uptimeMillis();
        int dirtyPhase2 = 0;
        for (;;) {
            int dirtyPhase1 = mDirty;
            dirtyPhase2 |= dirtyPhase1;
            mDirty = 0;

            updateWakeLockSummaryLocked(dirtyPhase1);
            updateUserActivitySummaryLocked(now, dirtyPhase1);
            updateAttentiveStateLocked(now, dirtyPhase1);
            if (!updateWakefulnessLocked(dirtyPhase1)) {
                break;
            }
        }

        // Phase 2: Lock profiles that became inactive/not kept awake.
        updateProfilesLocked(now);

        // Phase 3: Update display power state.
        /**
         * 异步更新显示器电源状态。更新完成后,mDisplayReady 将设置为 true。显示控制器会发布一条消息,告诉我们实际显示电源状态何时		 * 更新,因此我们回到这里仔细检查并完成。此功能每次重新计算显示器电源状态。返回:如果显示已准备好,则为true。
         * 经过这一步后,会将系统的亮度、显示状态等全部设置完毕,此时屏幕已经亮了
         */
        final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);

        // Phase 4: Update dream state (depends on display ready signal).
        updateDreamLocked(dirtyPhase2, displayBecameReady);

        // Phase 5: Send notifications, if needed.
        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!
        updateSuspendBlockerLocked();
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_POWER);
    }
}

再分析finishWakefulnessChangeIfNeededLocked() 方法,看看亮屏收尾工作都做了那些

private void finishWakefulnessChangeIfNeededLocked() {
    if (mWakefulnessChanging && mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
        if (getWakefulnessLocked() == WAKEFULNESS_DOZING
                && (mWakeLockSummary & WAKE_LOCK_DOZE) == 0) {
            return; // wait until dream has enabled dozing
        } else {
            // Doze wakelock acquired (doze started) or device is no longer dozing.
            mDozeStartInProgress = false;
        }
        if (getWakefulnessLocked() == WAKEFULNESS_DOZING
                || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
            logSleepTimeoutRecapturedLocked();
        }
        mWakefulnessChanging = false;
        mNotifier.onWakefulnessChangeFinished();
    }
}

继续看 Notifier类里的onWakefulnessChangeFinished() 方法

//com.android.server.power.Notifier
/**
 * Notifies that the device has finished changing wakefulness.
 */
public void onWakefulnessChangeFinished() {
    if (DEBUG) {
        Slog.d(TAG, "onWakefulnessChangeFinished");
    }

    if (mInteractiveChanging) {
        mInteractiveChanging = false;
        handleLateInteractiveChange();
    }
}

看调用的handleLateInteractiveChange() 方法

/**
 * Handle late interactive state changes once they are finished so that the system can
 * finish pending transitions (such as turning the screen off) before causing
 * applications to change state visibly.
 */
private void handleLateInteractiveChange() {
    synchronized (mLock) {
        final int interactiveChangeLatency =
                (int) (SystemClock.uptimeMillis() - mInteractiveChangeStartTime);
        if (mInteractive) {
            // Finished waking up...
            mHandler.post(() -> {
                LogMaker log = new LogMaker(MetricsEvent.SCREEN);
                log.setType(MetricsEvent.TYPE_OPEN);
                log.setSubtype(WindowManagerPolicyConstants.translateWakeReasonToOnReason(
                        mInteractiveChangeReason));
                log.setLatency(interactiveChangeLatency);
                log.addTaggedData(
                        MetricsEvent.FIELD_SCREEN_WAKE_REASON, mInteractiveChangeReason);
                MetricsLogger.action(log);
                EventLogTags.writePowerScreenState(1, 0, 0, 0, interactiveChangeLatency);
                //通知PhoneWindowManager唤醒操作结束
                mPolicy.finishedWakingUp(mInteractiveChangeReason);
            });
        } else {
            .....
        }
    }
}

2、息屏

同唤醒操作,首先执行PhoneWindowManager.java中的 interceptKeyBeforeQueueing() 方法中 KEYCODE_POWER

@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    ....

    // Handle special keys.
    switch (keyCode) {
        ....

        case KeyEvent.KEYCODE_POWER: {
            EventLogTags.writeInterceptPower(
                    KeyEvent.actionToString(event.getAction()),
                    mPowerKeyHandled ? 1 : 0,
                    mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
            // Any activity on the power button stops the accessibility shortcut
            result &= ~ACTION_PASS_TO_USER;
            isWakeKey = false; // wake-up will be handled separately
            if (down) {
                interceptPowerKeyDown(event, interactiveAndOn);
            } else {
                //手指抬起
                interceptPowerKeyUp(event, canceled);
            }
            break;
        }
    }

    ....

    return result;
}

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();
}

再 PhoneWindowManager 类中 init() 方法中调用了 initSingleKeyGestureRules() -> PowerKeyRule() -> powerPress()

private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
    if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
        Slog.i(TAG, "Suppressed redundant power key press while "
                + "already in the process of turning the screen on.");
        return;
    }

    final boolean interactive = Display.isOnState(mDefaultDisplay.getState());

    Slog.d(TAG, "powerPress: eventTime=" + eventTime + " interactive=" + interactive
            + " count=" + count + " beganFromNonInteractive=" + beganFromNonInteractive
            + " mShortPressOnPowerBehavior=" + mShortPressOnPowerBehavior);

    if (count == 2) {
        powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
    } else if (count == 3) {
        powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
    } else if (interactive && !beganFromNonInteractive) {
        if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) {
            Slog.i(TAG, "Suppressing power key because the user is interacting with the "
                    + "fingerprint sensor");
            return;
        }
        switch (mShortPressOnPowerBehavior) {
            case SHORT_PRESS_POWER_NOTHING:
                break;
            case SHORT_PRESS_POWER_GO_TO_SLEEP:
                sleepDefaultDisplayFromPowerButton(eventTime, 0);
                break;
            case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
                sleepDefaultDisplayFromPowerButton(eventTime,
                        PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
                break;
            
            .....
        }
    }
}

查看 mShortPressOnPowerBehavior 赋值

mShortPressOnPowerBehavior = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_shortPressOnPowerBehavior);

//查看配置文件
<!-- 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>

执行 case SHORT_PRESS_POWER_GO_TO_SLEEP 方法,即 sleepDefaultDisplayFromPowerButton()

/**
 * Sends the default display to sleep as a result of a power button press.
 *
 * @return {@code true} if the device was sent to sleep, {@code false} if the device did not
 * sleep.
 */
//由于按下电源按钮,设备进入睡眠状态。返回:如果设备被发送到睡眠状态,则返回 true,如果睡眠被抑制,则返回 false
private boolean sleepDefaultDisplayFromPowerButton(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.
    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;
        }
    }

    sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, flags);
    return true;
}
private void sleepDefaultDisplay(long eventTime, int reason, int flags) {
    mRequestedOrSleepingDefaultDisplay = true;
    mPowerManager.goToSleep(eventTime, reason, flags);
}

继续往下追PowerManagerService 的goToSleep()

@Override // Binder call
public void goToSleep(long eventTime, int reason, int flags) {
    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 {
        sleepDisplayGroup(Display.DEFAULT_DISPLAY_GROUP, eventTime, reason, flags, uid);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}

private void sleepDisplayGroup(int groupId, long eventTime, int reason, int flags,
        int uid) {
    synchronized (mLock) {
        if (sleepDisplayGroupNoUpdateLocked(groupId, eventTime, reason, flags, uid)) {
            updatePowerStateLocked();	
        }
    }
}

先分析 sleepDisplayGroupNoUpdateLocked()

private boolean sleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int reason,
        int flags, int uid) {
    if (DEBUG_SPEW) {
        Slog.d(TAG, "sleepDisplayGroupNoUpdateLocked: eventTime=" + eventTime
                + ", groupId=" + groupId + ", reason=" + reason + ", flags=" + flags
                + ", uid=" + uid);
    }

    if (eventTime < mLastWakeTime
            || !PowerManagerInternal.isInteractive(getWakefulnessLocked())
            || !mSystemReady
            || !mBootCompleted) {
        return false;
    }

    final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
    if (!PowerManagerInternal.isInteractive(wakefulness)) {
        return false;
    }

    Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOffDisplay");
    try {
        reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
                Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
        Slog.i(TAG, "Powering off display group due to "
                + PowerManager.sleepReasonToString(reason) + " (groupId= " + groupId
                + ", uid= " + uid + ")...");

        mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true);
        //核心代码
        setWakefulnessLocked(groupId, WAKEFULNESS_DOZING, eventTime, uid, reason,
                /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
        if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
            reallySleepDisplayGroupNoUpdateLocked(groupId, eventTime, uid);
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_POWER);
    }
    return true;
}

由setWakefulnessLocked() -> setGlobalWakefulnessLocked() -> mNotifier.onWakefulnessChangeStarted()

同wakeup一样,会走到Notifer类的onWakefulnessChangeStarted()方法

/**
 * 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, not just interactivity.
    // It needs more granularity than other components.
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            mActivityManagerInternal.onWakefulnessChanged(wakefulness);
        }
    });

    // Handle any early interactive state changes.
    // Finish pending incomplete ones from a previous cycle.
    if (mInteractive != interactive) {
        // Finish up late behaviors if needed.
        if (mInteractiveChanging) {
            handleLateInteractiveChange();
        }

        // Start input as soon as we start waking up or going to sleep.
        mInputManagerInternal.setInteractive(interactive);
        mInputMethodManagerInternal.setInteractive(interactive);

        // Notify battery stats.
        try {
            mBatteryStats.noteInteractive(interactive);
        } catch (RemoteException ex) { }
        FrameworkStatsLog.write(FrameworkStatsLog.INTERACTIVE_STATE_CHANGED,
                interactive ? FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON :
                        FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__OFF);

        // Handle early behaviors.
        mInteractive = interactive;
        mInteractiveChangeReason = reason;
        mInteractiveChangeStartTime = eventTime;
        mInteractiveChanging = true;
        handleEarlyInteractiveChange();
    }
}

继续看 handleErarlyInteractiveChange 方法

/**
 * 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;
            updatePendingBroadcastLocked();
        } else {
            // Going to sleep...
            // Tell the policy that we started going to sleep.
            mHandler.post(() -> mPolicy.startedGoingToSleep(mInteractiveChangeReason));
        }
    }
}

再分析 updatePowerStateLocked(), 唤醒的时候,也会走到这里

/**
 * 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() {
    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.
        updateIsPoweredLocked(mDirty);
        updateStayOnLocked(mDirty);
        updateScreenBrightnessBoostLocked(mDirty);

        // Phase 1: Update wakefulness.
        // Loop because the wake lock and user activity computations are influenced
        // by changes in wakefulness.
        final long now = mClock.uptimeMillis();
        int dirtyPhase2 = 0;
        for (;;) {
            int dirtyPhase1 = mDirty;
            dirtyPhase2 |= dirtyPhase1;
            mDirty = 0;

            updateWakeLockSummaryLocked(dirtyPhase1);
            updateUserActivitySummaryLocked(now, dirtyPhase1);
            updateAttentiveStateLocked(now, dirtyPhase1);
            if (!updateWakefulnessLocked(dirtyPhase1)) {
                break;
            }
        }

        // Phase 2: Lock profiles that became inactive/not kept awake.
        updateProfilesLocked(now);

        // Phase 3: Update display power state.
        final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);

        // Phase 4: Update dream state (depends on display ready signal).
        updateDreamLocked(dirtyPhase2, displayBecameReady);

        // Phase 5: Send notifications, if needed.
        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!
        updateSuspendBlockerLocked();
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_POWER);
    }
}

当息屏执行结束,我们分析finishWakefulnessChangeIfNeededLocked方法

private void finishWakefulnessChangeIfNeededLocked() {
    if (mWakefulnessChanging && mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
        if (getWakefulnessLocked() == WAKEFULNESS_DOZING
                && (mWakeLockSummary & WAKE_LOCK_DOZE) == 0) {
            return; // wait until dream has enabled dozing
        } else {
            // Doze wakelock acquired (doze started) or device is no longer dozing.
            mDozeStartInProgress = false;
        }
        if (getWakefulnessLocked() == WAKEFULNESS_DOZING
                || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
            logSleepTimeoutRecapturedLocked();
        }
        mWakefulnessChanging = false;
        mNotifier.onWakefulnessChangeFinished();
    }
}

我们继续来分析Notifier类的onWakefulnessChangeFinished方法

/**
 * Notifies that the device has finished changing wakefulness.
 */
public void onWakefulnessChangeFinished() {
    if (DEBUG) {
        Slog.d(TAG, "onWakefulnessChangeFinished");
    }

    if (mInteractiveChanging) {
        mInteractiveChanging = false;
        handleLateInteractiveChange();
    }
}
/**
 * Handle late interactive state changes once they are finished so that the system can
 * finish pending transitions (such as turning the screen off) before causing
 * applications to change state visibly.
 */
private void handleLateInteractiveChange() {
    synchronized (mLock) {
        final int interactiveChangeLatency =
                (int) (SystemClock.uptimeMillis() - mInteractiveChangeStartTime);
        if (mInteractive) {
            // Finished waking up...
            mHandler.post(() -> {
                LogMaker log = new LogMaker(MetricsEvent.SCREEN);
                log.setType(MetricsEvent.TYPE_OPEN);
                log.setSubtype(WindowManagerPolicyConstants.translateWakeReasonToOnReason(
                        mInteractiveChangeReason));
                log.setLatency(interactiveChangeLatency);
                log.addTaggedData(
                        MetricsEvent.FIELD_SCREEN_WAKE_REASON, mInteractiveChangeReason);
                MetricsLogger.action(log);
                EventLogTags.writePowerScreenState(1, 0, 0, 0, interactiveChangeLatency);
                mPolicy.finishedWakingUp(mInteractiveChangeReason);
            });
        } else {
            // Finished going to sleep...
            // This is a good time to make transitions that we don't want the user to see,
            // such as bringing the key guard to focus.  There's no guarantee for this
            // however because the user could turn the device on again at any time.
            // Some things may need to be protected by other mechanisms that defer screen on.

            // Cancel pending user activity.
            if (mUserActivityPending) {
                mUserActivityPending = false;
                mHandler.removeMessages(MSG_USER_ACTIVITY);
            }

            // Tell the policy we finished going to sleep.
            final int offReason = WindowManagerPolicyConstants.translateSleepReasonToOffReason(
                    mInteractiveChangeReason);
            mHandler.post(() -> {
                LogMaker log = new LogMaker(MetricsEvent.SCREEN);
                log.setType(MetricsEvent.TYPE_CLOSE);
                log.setSubtype(offReason);
                log.setLatency(interactiveChangeLatency);
                log.addTaggedData(
                        MetricsEvent.FIELD_SCREEN_SLEEP_REASON, mInteractiveChangeReason);
                MetricsLogger.action(log);
                EventLogTags.writePowerScreenState(
                        0, offReason, 0, 0, interactiveChangeLatency);
                mPolicy.finishedGoingToSleep(mInteractiveChangeReason);
            });

            // Send non-interactive broadcast.
            mPendingInteractiveState = INTERACTIVE_STATE_ASLEEP;
            mPendingGoToSleepBroadcast = true;
            //发送息屏广播,和唤醒类似,就不再赘述了
            updatePendingBroadcastLocked();
        }
    }
}

好了,从我们分析源码得知
唤醒的时候,唤醒广播是在handleEarlyInteractiveChange方法,就是在通知PhoneWindowManager 去执行startedWakingUp方法,紧跟着就执行updatePendingBroadcastLocked方法,去发送唤醒广播
而在息屏的时候,在handleEarlyInteractiveChange里,我们只是通知PhoneWindowManager去执行startedGoingToSleep方法,最后在updatePowerStateLocked==>finishWakefulnessChangeIfNeededLocked==>Notifier==>onWakefulnessChangeFinished==》handleLateInteractiveChange方法里,在通知PhoneWindowManager执行finishedGoingToSleep()之后,才执行updatePendingBroadcastLocked()方法去发送息屏广播(Intent.ACTION_SCREEN_OFF),这个时机需要特别注意一下。

参考 : https://blog.csdn.net/jwg1988/article/details/123899706

2、长按Power事件

2.1、PhoneWindowManager.java -> powerLongPress() 方法
private void powerLongPress(long eventTime) {
    final int behavior = getResolvedLongPressOnPowerBehavior();
    Slog.d(TAG, "powerLongPress: eventTime=" + eventTime
            + " mLongPressOnPowerBehavior=" + mLongPressOnPowerBehavior);

    switch (behavior) {
        case LONG_PRESS_POWER_NOTHING:
            break;
        case LONG_PRESS_POWER_GLOBAL_ACTIONS:
            mPowerKeyHandled = true;
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                    "Power - Long Press - Global Actions");
            showGlobalActions();
            break;
        case LONG_PRESS_POWER_SHUT_OFF:
        case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
            mPowerKeyHandled = true;
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                    "Power - Long Press - Shut Off");
            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
            mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
            break;
        case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
            mPowerKeyHandled = true;
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                    "Power - Long Press - Go To Voice Assist");
            // Some devices allow the voice assistant intent during setup (and use that intent
            // to launch something else, like Settings). So we explicitly allow that via the
            // config_allowStartActivityForLongPressOnPowerInSetup resource in config.xml.
            launchVoiceAssist(mAllowStartActivityForLongPressOnPowerDuringSetup);
            break;
        case LONG_PRESS_POWER_ASSISTANT:
            mPowerKeyHandled = true;
            performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON, false,
                    "Power - Long Press - Go To Assistant");
            final int powerKeyDeviceId = Integer.MIN_VALUE;
            launchAssistAction(null, powerKeyDeviceId, eventTime,
                    AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS);
            break;
    }
}
2.2、getResolvedLongPressOnPowerBehavior()

解析长按电源键行为的方法

private int getResolvedLongPressOnPowerBehavior() {
    if (FactoryTest.isLongPressOnPowerOffEnabled()) {
        return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
    }

    // If the config indicates the assistant behavior but the device isn't yet provisioned, show
    // global actions instead.
    if (mLongPressOnPowerBehavior == LONG_PRESS_POWER_ASSISTANT && !isDeviceProvisioned()) {
        return LONG_PRESS_POWER_GLOBAL_ACTIONS;
    }

    return mLongPressOnPowerBehavior; //最后返回这个行为
}

mLongPressOnPowerBehavior 在init()方法中调用的是资源文件

mLongPressOnPowerBehavior = mContext.getResources().getInteger(
        com.android.internal.R.integer.config_longPressOnPowerBehavior);

默认配置文件中

    <!-- Control the behavior when the user long presses the power button.
            0 - Nothing
            1 - Global actions menu
            2 - Power off (with confirmation)
            3 - Power off (without confirmation)
            4 - Go to voice assist
            5 - Go to assistant (Settings.Secure.ASSISTANT)
    -->
    <integer name="config_longPressOnPowerBehavior">5</integer>  //可以看到默认值为5

但在overlay中设置的内容默认值为1

 /os/la.qssi12/vendor/qcom/proprietary/commonsys/resource-overlay/common/Frameworks/res/values/config.xml 
<integer name="config_longPressOnPowerBehavior">1</integer>
2.3、showGlobalActions()

设置的 config_longPressOnPowerBehavior = 1;在powerLongPress方法中走 LONG_PRESS_POWER_GLOBAL_ACTIONS,调用 showGlobalActions()

@Override
public void showGlobalActions() {
    mHandler.removeMessages(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS);
    mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS);
}
2.4、showGlobalActionsInternal()

MSG_DISPATCH_SHOW_GLOBAL_ACTIONS -> showGlobalActionsInternal()

void showGlobalActionsInternal() {
    if (mGlobalActions == null) {
        mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
    }
    final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
    mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
    // since it took two seconds of long press to bring this up,
    // poke the wake lock so they have some time to see the dialog.
    mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
}
2.5、showDialog()
//com.android.server.policy.GlobalActions
public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
    if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
    if (mGlobalActionsProvider != null && mGlobalActionsProvider.isGlobalActionsDisabled()) {
        return;
    }
    mKeyguardShowing = keyguardShowing;
    mDeviceProvisioned = deviceProvisioned;
    mShowing = true;
    if (mGlobalActionsAvailable) {
        mHandler.postDelayed(mShowTimeout, 5000);
        mGlobalActionsProvider.showGlobalActions();
    } else {
        // SysUI isn't alive, show legacy menu.
        ensureLegacyCreated();
        mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
    }
}
2.6、mLegacyGlobalActions.showDialog()
//com.android.server.policy.LegacyGlobalActions
/**
 * Show the global actions dialog (creating if necessary)
 * @param keyguardShowing True if keyguard is showing
 */
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
    mKeyguardShowing = keyguardShowing;
    mDeviceProvisioned = isDeviceProvisioned;
    if (mDialog != null) {
        mDialog.dismiss();
        mDialog = null;
        // Show delayed, so that the dismiss of the previous dialog completes
        mHandler.sendEmptyMessage(MESSAGE_SHOW);
    } else {
        handleShow();
    }
}
private void handleShow() {
    awakenIfNecessary();
    mDialog = createDialog();
    prepareDialog();

    // If we only have 1 item and it's a simple press action, just do this action.
    if (mAdapter.getCount() == 1
            && mAdapter.getItem(0) instanceof SinglePressAction
            && !(mAdapter.getItem(0) instanceof LongPressAction)) {
        ((SinglePressAction) mAdapter.getItem(0)).onPress();
    } else {
        if (mDialog != null) {
            WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
            attrs.setTitle("LegacyGlobalActions");
            mDialog.getWindow().setAttributes(attrs);
            mDialog.show();
            mDialog.getWindow().getDecorView().setSystemUiVisibility(
                    View.STATUS_BAR_DISABLE_EXPAND);
        }
    }
}
2.7、createDialog() 创建dialog
/**
 * Create the global actions dialog.
 * @return A new dialog.
 */
private ActionsDialog createDialog() {
    // Simple toggle style if there's no vibrator, otherwise use a tri-state
    if (!mHasVibrator) {
        mSilentModeAction = new SilentModeToggleAction();
    } else {
        mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
    }
    mAirplaneModeOn = new ToggleAction(
            R.drawable.ic_lock_airplane_mode,
            R.drawable.ic_lock_airplane_mode_off,
            R.string.global_actions_toggle_airplane_mode,
            R.string.global_actions_airplane_mode_on_status,
            R.string.global_actions_airplane_mode_off_status) {

        @Override
        public void onToggle(boolean on) {
            if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
                mIsWaitingForEcmExit = true;
                // Launch ECM exit dialog
                Intent ecmDialogIntent =
                        new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
                ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                mContext.startActivity(ecmDialogIntent);
            } else {
                changeAirplaneModeSystemSetting(on);
            }
        }

        @Override
        protected void changeStateFromPress(boolean buttonOn) {
            if (!mHasTelephony) return;

            // In ECM mode airplane state cannot be changed
            if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
                mState = buttonOn ? State.TurningOn : State.TurningOff;
                mAirplaneState = mState;
            }
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return false;
        }
    };
    onAirplaneModeChanged();

    mItems = new ArrayList<Action>();
    String[] defaultActions = mContext.getResources().getStringArray(
            com.android.internal.R.array.config_globalActionsList);

    ArraySet<String> addedKeys = new ArraySet<String>();
    for (int i = 0; i < defaultActions.length; i++) {
        String actionKey = defaultActions[i];
        if (addedKeys.contains(actionKey)) {
            // If we already have added this, don't add it again.
            continue;
        }
        if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
            mItems.add(new PowerAction(mContext, mWindowManagerFuncs));
        } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
            mItems.add(mAirplaneModeOn);
        } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
            if (Settings.Global.getInt(mContext.getContentResolver(),
                    Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
                mItems.add(new BugReportAction());
            }
        } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
            if (mShowSilentToggle) {
                mItems.add(mSilentModeAction);
            }
        } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
            if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
                addUsersToMenu(mItems);
            }
        } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
            mItems.add(getSettingsAction());
        } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
            mItems.add(getLockdownAction());
        } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
            mItems.add(getVoiceAssistAction());
        } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
            mItems.add(getAssistAction());
        } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
            mItems.add(new RestartAction(mContext, mWindowManagerFuncs));
        } else {
            Log.e(TAG, "Invalid global action key " + actionKey);
        }
        // Add here so we don't add more than one.
        addedKeys.add(actionKey);
    }

    if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
        mItems.add(getEmergencyAction());
    }

    mAdapter = new ActionsAdapter(mContext, mItems,
            () -> mDeviceProvisioned, () -> mKeyguardShowing);

    AlertController.AlertParams params = new AlertController.AlertParams(mContext);
    params.mAdapter = mAdapter;
    params.mOnClickListener = this;
    params.mForceInverseBackground = true;

    ActionsDialog dialog = new ActionsDialog(mContext, params);
    dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.

    dialog.getListView().setItemsCanFocus(true);
    dialog.getListView().setLongClickable(true);
    dialog.getListView().setOnItemLongClickListener(
            new AdapterView.OnItemLongClickListener() {
                @Override
                public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
                        long id) {
                    final Action action = mAdapter.getItem(position);
                    if (action instanceof LongPressAction) {
                        return ((LongPressAction) action).onLongPress();
                    }
                    return false;
                }
    });
    dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
    dialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);

    dialog.setOnDismissListener(this);

    return dialog;
}

通过 String[] defaultActions = mContext.getResources().getStringArray(
com.android.internal.R.array.config_globalActionsList); 来查看配置文件

   <!-- Defines the default set of global actions. Actions may still be disabled or hidden based
         on the current state of the device.
         Each item must be one of the following strings:
         "power" = Power off
         "settings" = An action to launch settings
         "airplane" = Airplane mode toggle
         "bugreport" = Take bug report, if available
         "silent" = silent mode
         "users" = list of users
         "restart" = restart device
         "emergency" = Launch emergency dialer
         "lockdown" = Lock down device until the user authenticates
         "logout" =  Logout the current user
         -->
    <string-array translatable="false" name="config_globalActionsList">
        <item>emergency</item>
        <item>lockdown</item>
        <item>power</item>
        <item>restart</item>
        <item>logout</item>
        <item>screenshot</item>
        <item>bugreport</item>
    </string-array>

最后展示出来,那么既然得到了关机界面,假如我们点击了其中的关机键,那么会如何执行呢

我们发现在构造弹框的时候

if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
                mItems.add(new PowerAction(mContext, mWindowManagerFuncs));
            }

那么点击item,就会执行PowerAction 的onPress()方法

@Override
public void onPress() {
    // shutdown by making sure radio and power are handled accordingly.
    mWindowManagerFuncs.shutdown(false /* confirm */);
}

mWindowManagerFuncs 由 PowerAction构造传进来的,往上追 ==> LegacyGlobalActions构造传入的 ==> GlobalActions构造传入 ==> PhoneWindowManager init()方法初始化的,最终我们是在WindowManagerService里找到PhoneWindowManager初始化的地方

//com.android.server.wm.WindowManagerService
private void initPolicy() {
    UiThread.getHandler().runWithScissors(new Runnable() {
        @Override
        public void run() {
            WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
            mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
        }
    }, 0);
}

我们也知道其实mWindowManagerFuncs就是一个WindowManagerService对象,那么上面的shutdown其实是执行的是WindowManagerService的shutdown

// Called by window manager policy.  Not exposed externally.
@Override
public void shutdown(boolean confirm) {
    // Pass in the UI context, since ShutdownThread requires it (to show UI).
    ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),
            PowerManager.SHUTDOWN_USER_REQUESTED, confirm);
}

继续往下追 ShutdownThread 类里的shutdown方法

/**
 * Request a clean shutdown, waiting for subsystems to clean up their
 * state etc.  Must be called from a Looper thread in which its UI
 * is shown.
 *
 * @param context Context used to display the shutdown progress dialog. This must be a context
 *                suitable for displaying UI (aka Themable).
 * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
 * @param confirm true if user confirmation is needed before shutting down.
 */
public static void shutdown(final Context context, String reason, boolean confirm) {
    mReboot = false;
    mRebootSafeMode = false;
    mReason = reason;
    shutdownInner(context, confirm);
}

继续执行shutdownInner 方法

 private static void shutdownInner(final Context context, boolean confirm) {
     // ShutdownThread is called from many places, so best to verify here that the context passed
     // in is themed.
     context.assertRuntimeOverlayThemable();

     // ensure that only one thread is trying to power down.
     // any additional calls are just returned
     synchronized (sIsStartedGuard) {
         if (sIsStarted) {
             Log.d(TAG, "Request to shutdown already running, returning.");
             return;
         }
     }

     // Add checkpoint for this shutdown attempt. The user might still cancel the dialog, but
     // this point preserves the system trace of the trigger point of the ShutdownThread.
     ShutdownCheckPoints.recordCheckPoint(/* reason= */ null);

     final int longPressBehavior = context.getResources().getInteger(
                     com.android.internal.R.integer.config_longPressOnPowerBehavior);
     final int resourceId = mRebootSafeMode
             ? com.android.internal.R.string.reboot_safemode_confirm
             : (longPressBehavior == 2
                     ? com.android.internal.R.string.shutdown_confirm_question
                     : com.android.internal.R.string.shutdown_confirm);

     Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);

     if (confirm) {
         final CloseDialogReceiver closer = new CloseDialogReceiver(context);
         if (sConfirmDialog != null) {
             sConfirmDialog.dismiss();
         }
         sConfirmDialog = new AlertDialog.Builder(context)
                 .setTitle(mRebootSafeMode
                         ? com.android.internal.R.string.reboot_safemode_title
                         : com.android.internal.R.string.power_off)
                 .setMessage(resourceId)
                 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int which) {
                         beginShutdownSequence(context);
                     }
                 })
                 .setNegativeButton(com.android.internal.R.string.no, null)
                 .create();
         closer.dialog = sConfirmDialog;
         sConfirmDialog.setOnDismissListener(closer);
         sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         sConfirmDialog.show();
     } else {
         beginShutdownSequence(context);
     }
 }

是否需要弹出关机确认框,如果需要则弹出,通过上面我们传入的是false,所以直接执行

2.8、beginShutdownSequence(context);
private static void beginShutdownSequence(Context context) {
    synchronized (sIsStartedGuard) {
        if (sIsStarted) {
            Log.d(TAG, "Shutdown sequence already running, returning.");
            return;
        }
        sIsStarted = true;
    }

    sInstance.mProgressDialog = showShutdownDialog(context);
    sInstance.mContext = context;
    sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

    // make sure we never fall asleep again
    sInstance.mCpuWakeLock = null;
    try {
        sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
        sInstance.mCpuWakeLock.setReferenceCounted(false);
        sInstance.mCpuWakeLock.acquire();
    } catch (SecurityException e) {
        Log.w(TAG, "No permission to acquire wake lock", e);
        sInstance.mCpuWakeLock = null;
    }

    // also make sure the screen stays on for better user experience
    sInstance.mScreenWakeLock = null;
    if (sInstance.mPowerManager.isScreenOn()) {
        try {
            sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
                    PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
            sInstance.mScreenWakeLock.setReferenceCounted(false);
            sInstance.mScreenWakeLock.acquire();
        } catch (SecurityException e) {
            Log.w(TAG, "No permission to acquire wake lock", e);
            sInstance.mScreenWakeLock = null;
        }
    }

    if (SecurityLog.isLoggingEnabled()) {
        SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
    }

    // start the thread that initiates shutdown
    sInstance.mHandler = new Handler() {
    };
    sInstance.start();
}

首先初始化一个关机页面showShutdownDialog,弹出页面,然后sInstance.start();执行真正的关机操作

/**
 * Makes sure we handle the shutdown gracefully.
 * Shuts off power regardless of radio state if the allotted time has passed.
 */
public void run() {
    TimingsTraceLog shutdownTimingLog = newTimingsLog();
    shutdownTimingLog.traceBegin("SystemServerShutdown");
    metricShutdownStart();
    metricStarted(METRIC_SYSTEM_SERVER);

    // Start dumping check points for this shutdown in a separate thread.
    Thread dumpCheckPointsThread = ShutdownCheckPoints.newDumpThread(
            new File(CHECK_POINTS_FILE_BASENAME));
    dumpCheckPointsThread.start();

    BroadcastReceiver br = new BroadcastReceiver() {
        @Override public void onReceive(Context context, Intent intent) {
            // We don't allow apps to cancel this, so ignore the result.
            actionDone();
        }
    };

    /*
     * Write a system property in case the system_server reboots before we
     * get to the actual hardware restart. If that happens, we'll retry at
     * the beginning of the SystemServer startup.
     */
    {
        String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
        SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
    }

    /*
     * If we are rebooting into safe mode, write a system property
     * indicating so.
     */
    if (mRebootSafeMode) {
        SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
    }

    shutdownTimingLog.traceBegin("DumpPreRebootInfo");
    try {
        Slog.i(TAG, "Logging pre-reboot information...");
        PreRebootLogger.log(mContext);
    } catch (Exception e) {
        Slog.e(TAG, "Failed to log pre-reboot information", e);
    }
    shutdownTimingLog.traceEnd(); // DumpPreRebootInfo

    metricStarted(METRIC_SEND_BROADCAST);
    shutdownTimingLog.traceBegin("SendShutdownBroadcast");
    Log.i(TAG, "Sending shutdown broadcast...");

    // First send the high-level shut down broadcast.
    mActionDone = false;
    Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    mContext.sendOrderedBroadcastAsUser(intent,
            UserHandle.ALL, null, br, mHandler, 0, null, null);

    final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
    synchronized (mActionDoneSync) {
        while (!mActionDone) {
            long delay = endTime - SystemClock.elapsedRealtime();
            if (delay <= 0) {
                Log.w(TAG, "Shutdown broadcast timed out");
                break;
            } else if (mRebootHasProgressBar) {
                int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
                        BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
                sInstance.setRebootProgress(status, null);
            }
            try {
                mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
            } catch (InterruptedException e) {
            }
        }
    }
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
    }
    shutdownTimingLog.traceEnd(); // SendShutdownBroadcast
    metricEnded(METRIC_SEND_BROADCAST);

    Log.i(TAG, "Shutting down activity manager...");
    shutdownTimingLog.traceBegin("ShutdownActivityManager");
    metricStarted(METRIC_AM);

    final IActivityManager am =
            IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
    if (am != null) {
        try {
            am.shutdown(MAX_BROADCAST_TIME);
        } catch (RemoteException e) {
        }
    }
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
    }
    shutdownTimingLog.traceEnd();// ShutdownActivityManager
    metricEnded(METRIC_AM);

    Log.i(TAG, "Shutting down package manager...");
    shutdownTimingLog.traceBegin("ShutdownPackageManager");
    metricStarted(METRIC_PM);

    final PackageManagerService pm = (PackageManagerService)
        ServiceManager.getService("package");
    if (pm != null) {
        pm.shutdown();
    }
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
    }
    shutdownTimingLog.traceEnd(); // ShutdownPackageManager
    metricEnded(METRIC_PM);

    // Shutdown radios.
    shutdownTimingLog.traceBegin("ShutdownRadios");
    metricStarted(METRIC_RADIOS);
    shutdownRadios(MAX_RADIO_WAIT_TIME);
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
    }
    shutdownTimingLog.traceEnd(); // ShutdownRadios
    metricEnded(METRIC_RADIOS);

    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);

        // If it's to reboot to install an update and uncrypt hasn't been
        // done yet, trigger it now.
        uncrypt();
    }

    // Wait for the check points dump thread to finish, or kill it if not finished in time.
    shutdownTimingLog.traceBegin("ShutdownCheckPointsDumpWait");
    try {
        dumpCheckPointsThread.join(MAX_CHECK_POINTS_DUMP_WAIT_TIME);
    } catch (InterruptedException ex) {
    }
    shutdownTimingLog.traceEnd(); // ShutdownCheckPointsDumpWait

    shutdownTimingLog.traceEnd(); // SystemServerShutdown
    metricEnded(METRIC_SYSTEM_SERVER);
    saveMetrics(mReboot, mReason);
    // Remaining work will be done by init, including vold shutdown
    rebootOrShutdown(mContext, mReboot, mReason);
}

可以看到发出Intent.ACTION_SHUTDOWN广播,并且执行关机操作。

总结:

点击电源键,执行PhoneWindowManager里的dispatchUnhandledKey方法

然后在dispatchUnhandledKey进行处理,当判断是长按事件时,会执行powerLongPress方法

然后在执行showGlobalActionsInternal()弹出(飞行模式,关机,重启等)

在我们点击关机的时候,会执行PowerAction的onPress方法,执行WindowManagerService的shutdown方法,进而执行ShutdownThread的shutdown方法,在里面会执行真正的关机操作,并且可以看到,系统在关机之前会发出一个Intent.ACTION_SHUTDOWN广播后执行关机。

链接:https://blog.csdn.net/jwg1988/article/details/123631476?spm=1001.2014.3001.5501

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值