Android 8.1.0 源码探究之 - 为啥给设备设置锁屏密码以后,重启设备会看到屏幕会变得比关机之前亮(解锁后亮度恢复为重启之前的亮度)

        今天测试给提了一个issue:刷机 - 设置设备的锁屏密码(pattern)- 重启设备,设备重启完成之后,首先看到的当然是解锁界面,会看到解锁界面的亮度比重启之前在 Settings 中设置的亮度亮了不少(即使在系统的 Settings 中设置了系统亮度是 0 ,重启之后首先看到的解锁界面也是很亮,解锁之后,设备屏幕的亮度恢复为重启之前亮度)。测试认为这是有问题的,把这个问题扔了出来。

        首先,作为开发的直觉来说,我觉得这其实应该不是一个issue,而是Google有意为之,就像是平时我们给商户展示支付宝二维码的时候,会发现支付宝的二维码界面亮度亮了不少,等到扫码结束退到其他界面的时候,会看到亮度恢复为之前。但是空说没有什么证据,得看代码到底是怎么处理这个地方的逻辑的。

        首先找到这个重启之后的输入密码界面是啥,通过打印堆栈信息,看到这个界面是:

realActivity=com.android.settings/.CryptKeeper

        恩恩,这个界面是 Settings 中的某一个 Activity 界面,具体位置是:

folder\vendor\rockchip\hxxx\gxx\apps\Settings\src\com\android\settings\CryptKeeper.java

 首先看一下这个 Activity 在初始化的时候做了什么,可以看到在它的 onCreate()里面没有做关于界面展示的逻辑,他把逻辑放到了onStart() 方法中。如下:

    /**
     * Note, we defer the state check and screen setup to onStart() because this will be
     * re-run if the user clicks the power button (sleeping/waking the screen), and this is
     * especially important if we were to lose the wakelock for any reason.
     */
    @Override
    public void onStart() {
        super.onStart();
        //关键代码
        setupUi();
    }

具体为啥把UI相关的逻辑放到 onStart() 方法,Google 工程师做了解释。那在 setupUi(); 中做了什么操作?如下:

    /**
     * Initializes the UI based on the current state of encryption.
     * This is idempotent - calling repeatedly will simply re-initialize the UI.
     */
    private void setupUi() {
        if (mEncryptionGoneBad || isDebugView(FORCE_VIEW_ERROR)) {
            setContentView(R.layout.crypt_keeper_progress);
            showFactoryReset(mCorrupt);
            return;
        }

        final String progress = SystemProperties.get("vold.encrypt_progress");
        if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
            setContentView(R.layout.crypt_keeper_progress);
            encryptionProgressInit();
        } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
            new AsyncTask<Void, Void, Void>() {
                int passwordType = StorageManager.CRYPT_TYPE_PASSWORD;
                String owner_info;
                boolean pattern_visible;
                boolean password_visible;

                @Override
                public Void doInBackground(Void... v) {
                    try {
                        final IStorageManager service = getStorageManager();
                        passwordType = service.getPasswordType();
                        owner_info = service.getField(StorageManager.OWNER_INFO_KEY);
                        pattern_visible = !("0".equals(service.getField(StorageManager.PATTERN_VISIBLE_KEY)));
                        password_visible = !("0".equals(service.getField(StorageManager.PASSWORD_VISIBLE_KEY)));
                    } catch (Exception e) {
                        Log.e(TAG, "Error calling mount service " + e);
                    }

                    return null;
                }

                @Override
                public void onPostExecute(java.lang.Void v) {
                    Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,
                                  password_visible ? 1 : 0);

                    //code...
                    
                    //关键代码

                    passwordEntryInit();

                    //code...

                }
            }.execute();
        } else if (!mValidationRequested) {
            // We're supposed to be encrypted, but no validation has been done.
            new ValidationTask().execute((Void[]) null);
            mValidationRequested = true;
        }
    }

        可以看到当系统检查过错误之后,会创建一个 AsyncTask,并在doInBackground()中获取到当前设备密码的类型,在onPostExecute()中调用 passwordEntryInit()方法进行密码实体化界面的初始化。看一下 passwordEntryInit()做了什么操作:

     private void passwordEntryInit() {
        // Password/pin case
        // code...

        // Pattern case
        mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
        if (mLockPatternView != null) {
            mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
        }

        // Disable the Emergency call button if the device has no voice telephone capability
        // code...

        // We want to keep the screen on while waiting for input. In minimal boot mode, the device
        // is completely non-functional, and we want the user to notice the device and enter a
        // password.
        if (mWakeLock == null) {
            Log.d(TAG, "Acquiring wakelock.");
            final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            if (pm != null) {
                mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
                //关键代码!
                mWakeLock.acquire();
                // Keep awake for 10 minutes - if the user hasn't been alerted by then
                // best not to just drain their battery
                mReleaseWakeLockCountdown = 96; // 96 * 5 secs per click + 120 secs before we show this = 600
            }
        }

        // Make sure that the IME is shown when everything becomes ready.
        // code...

        // Notify the user in 120 seconds that we are waiting for him to enter the password.
        mHandler.removeMessages(MESSAGE_NOTIFY);
        mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 120 * 1000);

        // Dismiss secure & non-secure keyguards while this screen is showing.
        // code...
    }

        这个方法很长,但是看起来其实一目了然,系统做了判断,对用户可能设置密码的三种可能做了初始化。我们关心一个方法就行:mWakeLock.acquire(); 我们可以看到这里是调用了 PowerManager 的 acquire();方法,这个方法是为了获取唤醒锁定。另外还有注释如下:

We want to keep the screen on while waiting for input. In minimal boot mode, the device is completely non-functional, and we want the user to notice the device and enter a password.

 

       这里可以解释为,当设备重启完成之后,设备要保持 Screen on等待用户输入,并且保持屏幕是亮着的,用以提醒用户赶快输入密码解锁。

        继续跟代码,在目录:

folder\XX\frameworks\base\core\java\android\os\

下有一个 PowerManager.java 类,刚刚调用的 acquire()就是调用的这个类中的方法。在这个类中可以看到:

        /**
         * Acquires the wake lock.
         * <p>
         * Ensures that the device is on at the level requested when
         * the wake lock was created.
         * </p>
         */
        public void acquire() {
            synchronized (mToken) {
                //关键代码!
                acquireLocked();
            }
        }

       

        private void acquireLocked() {
	    mInternalCount++;
            mExternalCount++;
            if (!mRefCounted || mInternalCount == 1) {
                for(int i=0;i<packageList.size();i++){
                    String pckname = packageList.get(i);
                    //Slog.d("lvjinhua","--------------------pckname111="+pckname+",mPackageName="+mPackageName);
                    if(mPackageName.equals(pckname) || mTag.equals(pckname)){
                        return;
                    }
                }

                // Do this even if the wake lock is already thought to be held (mHeld == true)
                // because non-reference counted wake locks are not always properly released.
                // For example, the keyguard's wake lock might be forcibly released by the
                // power manager without the keyguard knowing.  A subsequent call to acquire
                // should immediately acquire the wake lock once again despite never having
                // been explicitly released by the keyguard.
                mHandler.removeCallbacks(mReleaser);
                Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
                try {
                    //关键代码!
                    mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
                            mHistoryTag);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
                mHeld = true;
            }
        }

        可以看到这里通过 IPowerManager 这个类型的代理对象  mService ,调用了 PowerManagerService 的acquireWakeLock();方法,很明显,这里是AIDL,最终执行的逻辑是Server 端的逻辑。

        跟代码,找到 PowerManagerService.java 路径如下:

folder\XX\frameworks\base\services\core\java\com\android\server\power\

最终执行的 acquireWakeLock();方法是调用的 PowerManagerService.java 中的 acquireWakeLock();查看这个方法:

        @Override // Binder call
        public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
                WorkSource ws, String historyTag) {
            if (lock == null) {
                throw new IllegalArgumentException("lock must not be null");
            }
            if (packageName == null) {
                throw new IllegalArgumentException("packageName must not be null");
            }
            PowerManager.validateWakeLockParameters(flags, tag);
     
            try {
                //关键代码!
                acquireWakeLockInternal(lock, flags, tag, packageName, ws, historyTag, uid, pid);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        可以看到这个关键代码调用了封装之后的方法  acquireWakeLockInternal(),即调用了获取唤醒锁的内部封装的逻辑,代码比较多,这里只展示关键代码:

 

    private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
            WorkSource ws, String historyTag, int uid, int pid) {
        synchronized (mLock) {
            // code...

            WakeLock wakeLock;
            int index = findWakeLockIndexLocked(lock);
            boolean notifyAcquire;
            if (index >= 0) {
                // code...
            } else {
                // code...
            }

            applyWakeLockFlagsOnAcquireLocked(wakeLock, uid);
            mDirty |= DIRTY_WAKE_LOCKS;
            
            // 关键代码!
            updatePowerStateLocked();
            // code...
        }
    }

 

可以看到这里又通过调用方法 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() {
        // code check system status
        // code...

        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 = SystemClock.uptimeMillis();
            int dirtyPhase2 = 0;
            for (;;) {
                int dirtyPhase1 = mDirty;
                dirtyPhase2 |= dirtyPhase1;
                mDirty = 0;

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

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

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

            // Phase 4: Send notifications, if needed.
            finishWakefulnessChangeIfNeededLocked();

            // Phase 5: 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);
        }
    }

        Google 工程师对这个方法的注释很明白了:方法中,根据mDirty中记录的数据更新全局电源状态。并且这是执行电源状态转换的主要功能。 我们将它们集中在这里,以便我们可以在每次重要变化时完全重新计算功率状态,并确保每次都以相同的方式进行。 关键是要在这里收集所有过渡逻辑。在这个方法中,分了5个阶段来更新处理全局电源状态 ,具体每个阶段做了什么我们就不一一看了,这不是我们追踪代码的目的,感兴趣的话,大家可以自己跟一下各个方法做了什么。我们只要关注第 0 个阶段和第 2 个阶段就可以了。

        看第 0 个阶段中的标注出来的关键代码:updateScreenBrightnessBoostLocked(mDirty);  如下:

    private void updateScreenBrightnessBoostLocked(int dirty) {
        if ((dirty & DIRTY_SCREEN_BRIGHTNESS_BOOST) != 0) {
            if (mScreenBrightnessBoostInProgress) {
                final long now = SystemClock.uptimeMillis();
                mHandler.removeMessages(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                if (mLastScreenBrightnessBoostTime > mLastSleepTime) {
                    final long boostTimeout = mLastScreenBrightnessBoostTime +
                            SCREEN_BRIGHTNESS_BOOST_TIMEOUT;
                    if (boostTimeout > now) {
                        Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                        msg.setAsynchronous(true);
                        mHandler.sendMessageAtTime(msg, boostTimeout);
                        return;
                    }
                }
                mScreenBrightnessBoostInProgress = false;
                mNotifier.onScreenBrightnessBoostChanged();
                userActivityNoUpdateLocked(now,
                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
            }
        }
    }

        这个方法中做的事情很简单,就是监测并处理屏幕亮度增加超时情况,如果在默认的时间内,屏幕的亮度一直没有增加,会通过mHandler 发送一个“屏幕亮度增加超时” 的广播出去。

        看第 2 个阶段中的标注出来的关键代码:updateDisplayPowerStateLocked(dirtyPhase2);  如下:

    /**
     * Updates the display power state asynchronously.
     * When the update is finished, mDisplayReady will be set to true.  The display
     * controller posts a message to tell us when the actual display power state
     * has been updated so we come back here to double-check and finish up.
     *
     * This function recalculates the display power state each time.
     *
     * @return True if the display became ready.
     */
    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) {

            // code...


            // Update display power request.
            mDisplayPowerRequest.screenBrightness = screenBrightness;
            mDisplayPowerRequest.screenAutoBrightnessAdjustment =
                    screenAutoBrightnessAdjustment;
            mDisplayPowerRequest.brightnessSetByUser = brightnessSetByUser;
            mDisplayPowerRequest.useAutoBrightness = autoBrightness;
            mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
            mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();

            updatePowerRequestFromBatterySaverPolicy(mDisplayPowerRequest);

            // code...

            // 关键代码!!!
            mDisplayReady = mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest,
                    mRequestWaitForNegativeProximity);
            mRequestWaitForNegativeProximity = false;


            // code...

        }
        return mDisplayReady && !oldDisplayReady;
    }

       可以看到在代码中,把一些参数(其中就包含是否需要自动增加屏幕亮度 :boostScreenBrightness 这个参数)封装到 mDisplayPowerRequest 对象中,并通过调用 mDisplayManagerInternal 对象的 requestPowerState()方法将这些参数设置到 mDisplayManagerInternal 对象中。跟一下 mDisplayManagerInternal 对象的 requestPowerState()方法做了什么。

这个对象是 DisplayManagerInternal.java 的实例对象,查看代码发现这是一个抽象类,具体逻辑还是得看具体实现类(猜测实现类是 DMS,一会儿再跟),还是先看一下这个抽象类中抽象方法是怎么定义这个方法的吧:

    /**
     * Called by the power manager to request a new power state.
     * <p>
     * The display power controller makes a copy of the provided object and then
     * begins adjusting the power state to match what was requested.
     * </p>
     *
     * @param request The requested power state.
     * @param waitForNegativeProximity If true, issues a request to wait for
     * negative proximity before turning the screen back on, assuming the screen
     * was turned off by the proximity sensor.
     * @return True if display is ready, false if there are important changes that must
     * be made asynchronously (such as turning the screen on), in which case the caller
     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
     * then try the request again later until the state converges.
     */
    public abstract boolean requestPowerState(DisplayPowerRequest request,
            boolean waitForNegativeProximity);

        可以看到 DMS 对这个方法是这么描述的:这个方法被 PowerManagerService 调用,并根据传递进来的封装之后的 request 对象更新状态。(不太想继续往下跟了。。)(刚刚项目上有事情需要做,先暂停一下,等下午有空了再继续跟)

        抽空跟一下。。

        上面说到这个地方是在 DMS,也就是 DisplayManagerService.java 中调用的 requestPowerState()方法。那看一下这个DisplayManagerService.java 类,目录:

folder\XX\frameworks\base\services\core\java\com\android\server\display\DisplayManagerService.java

        看到在这个类中, requestPowerState()方法:

        @Override
        public boolean requestPowerState(DisplayPowerRequest request,
                boolean waitForNegativeProximity) {
            return mDisplayPowerController.requestPowerState(request,
                    waitForNegativeProximity);
        }

        其实是调用了 DisplayPowerController 代理对象 mDisplayPowerController 的 requestPowerState()方法。同级目录下找到 DisplayPowerController.java 这个类,查看这个 requestPowerState()方法:

    /**
     * Requests a new power state.
     * The controller makes a copy of the provided object and then
     * begins adjusting the power state to match what was requested.
     *
     * @param request The requested power state.
     * @param waitForNegativeProximity If true, issues a request to wait for
     * negative proximity before turning the screen back on, assuming the screen
     * was turned off by the proximity sensor.
     * @return True if display is ready, false if there are important changes that must
     * be made asynchronously (such as turning the screen on), in which case the caller
     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
     * then try the request again later until the state converges.
     */
    public boolean requestPowerState(DisplayPowerRequest request,
            boolean waitForNegativeProximity) {
        if (DEBUG) {
            Slog.d(TAG, "requestPowerState: "
                    + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
        }

        synchronized (mLock) {
            boolean changed = false;

            if (waitForNegativeProximity
                    && !mPendingWaitForNegativeProximityLocked) {
                mPendingWaitForNegativeProximityLocked = true;
                changed = true;
            }

            if (mPendingRequestLocked == null) {
                mPendingRequestLocked = new DisplayPowerRequest(request);
                changed = true;
            } else if (!mPendingRequestLocked.equals(request)) {
                mPendingRequestLocked.copyFrom(request);
                changed = true;
            }

            if (changed) {
                mDisplayReadyLocked = false;
            }

            if (changed && !mPendingRequestChangedLocked) {
                mPendingRequestChangedLocked = true;
                sendUpdatePowerStateLocked();
            }

            return mDisplayReadyLocked;
        }
    }

        可以看到这个方法的描述:

        请求新的 Power 状态。 控制器复制所提供的 request 对象,然后开始调整设备的 Power 状态以匹配请求的内容。

        以上就是给设备设置了锁屏密码并开机之后,设备首次展示给用户解锁界面时,设备屏幕显示以及 Power 状态配置的大概流程。

 

   基于以上流程跟踪,当调用 updateDisplayPowerStateLocked()时,PMS 会封装一个 DisplayPowerRequest  对象传递给 PMS,最终这个 DisplayPowerRequest  对象会被传递给 DMS,DMS 收到这个更新请求的时候,会按照  Request  对象中携带的数据进行状态以及 Power 的更新。

   需要注意一点,DisplayPowerRequest  对象有一个boolean类型德尔属性:boostScreenBrightness,这个属性就是导致屏幕亮度变亮的关键。

       

   
    // code...

    mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();

    // code...


    private boolean shouldBoostScreenBrightness() {
        return !mIsVrModeEnabled && mScreenBrightnessBoostInProgress;
    }

        PMS 是怎么给boolean类型的  mIsVrModeEnabled 和 mScreenBrightnessBoostInProgress 进行赋值的呢?

对于变量 mIsVrModeEnabled  来说:True if we are currently in VR Mode.(所以这里一般为 false

对于变量 mScreenBrightnessBoostInProgress  来说,当 设置屏幕亮度超时的时候,会置为 false,当调用内部逻辑提升屏幕亮度的时候,会置为 true。(变量变化如下两个方法所示

    private void updateScreenBrightnessBoostLocked(int dirty) {
        if ((dirty & DIRTY_SCREEN_BRIGHTNESS_BOOST) != 0) {
            if (mScreenBrightnessBoostInProgress) {
                final long now = SystemClock.uptimeMillis();
                mHandler.removeMessages(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                if (mLastScreenBrightnessBoostTime > mLastSleepTime) {
                    final long boostTimeout = mLastScreenBrightnessBoostTime +
                            SCREEN_BRIGHTNESS_BOOST_TIMEOUT;
                    if (boostTimeout > now) {
                        Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                        msg.setAsynchronous(true);
                        mHandler.sendMessageAtTime(msg, boostTimeout);
                        return;
                    }
                }
                //关键代码!!
                mScreenBrightnessBoostInProgress = false;
                mNotifier.onScreenBrightnessBoostChanged();
                userActivityNoUpdateLocked(now,
                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
            }
        }
    }
   private void boostScreenBrightnessInternal(long eventTime, int uid) {
        synchronized (mLock) {
            if (!mSystemReady || mWakefulness == WAKEFULNESS_ASLEEP
                    || eventTime < mLastScreenBrightnessBoostTime) {
                return;
            }

            Slog.i(TAG, "Brightness boost activated (uid " + uid +")...");
            mLastScreenBrightnessBoostTime = eventTime;
            if (!mScreenBrightnessBoostInProgress) {
                // 关键代码!!
                mScreenBrightnessBoostInProgress = true;
                mNotifier.onScreenBrightnessBoostChanged();
            }
            mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST;

            userActivityNoUpdateLocked(eventTime,
                    PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
            updatePowerStateLocked();
        }
    }

        并且通过跟踪代码看到,首先会调用 updateScreenBrightnessBoostLocked()方法,再调用 boostScreenBrightnessInternal()方法。所以只要不出意外情况,mScreenBrightnessBoostInProgress   最终会被置为 true。

        所以,DisplayPowerRequest  对象中的 boolean 属性 boostScreenBrightness 不出意外情况,会是 true,这样当PMS 拿到 这个属性并传递给 DMS 的时候,DMS 就会去增加屏幕的亮度了。

        所以基于代码跟踪,测试人员提出的这个不应该属于是issue,不需要修改。

 

 

 

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值