8 展讯Sprd设置-电池-关联自启动-judgeAppLaunchAllowedInternal 函数

1. 关联唤醒机制

关联唤醒中的原理在于在应用启动的各种通道中进行拦截

  1. 在ActivityStarter 进行 start-activity 监听,judgeAppLaunchAllowedInternal 判断是否拦截
  2. 在ActivityManagerService 进行 contentprovider 监听,judgeAppLaunchAllowedInternal 判断是否拦截
  3. 在BroadcastQueue 进行 send-broadcast,judgeAppLaunchAllowedInternal 判断是否拦截
  4. 在ActiveServices 进行 start-service 和 bind-service监听,judgeAppLaunchAllowedInternal 判断是否拦截

2. judgeAppLaunchAllowedInternal 函数

  • vendor/sprd/platform/frameworks/base/services/core/java/com/android/server/power/BackgroundCleanHelper.java

该函数主要进行是否拦截的判断,这里的判断条件主要根据activty\contentprovid\broadcast\servie\UID范围\进程状态\设置界面的黑白名单\用户是否触摸事件\特殊场景(输入法、壁纸服务、小部件、语音输入、CTS场景等)\用户主动点击行为,等等多个维度进行条件判断,决定是否运行进程启动。

judgeAppLaunchAllowedInternal 函数

    /*
     * Third app can not self started:
     * 1. not allow system broadcast from "systemserver" or "com.android.systemui"
     * 2. not allow start service from ""com.android.shell"
     * 3. not allow background app to launch other third party app
     * 4. not allow self started third party app to start other third party app
     * 5. not allow third party app to start other third party app during standby
     *
     * Note: This api is call from AMS in other threads and may be in variable calling context
     *  SO BE CAREFULL : avoid call other system API that may hold lock. Otherwise, a deadlock may HAPPEN
     */
     boolean judgeAppLaunchAllowedInternal(Intent intent, String targetApp, int targetUid,
            int callerUid, String callerApp, String reason) {

        // 总开关是否关闭
        // if this function is disabled, just return true
        if (!mEnabled) return true;

        if (DEBUG_MORE) Slog.d(TAG,"judgeAppLaunchAllowed : "+targetApp+"(uid:" + targetUid
            + "), callingPackage = "+callerApp+"(uid:" + callerUid + "), reason = "+reason);
        if (DEBUG_MORE && intent != null) Slog.d(TAG,"judgeAppLaunchAllowed : intent action:" + intent);

        // 目标进程是否为未知
        if (targetApp == null) return true;

        // 是否允许 start-service 类型启动
        if (handleStartServiceStartAction(intent, targetApp, targetUid, callerApp, callerUid, reason)) {
            return true;
        }

        // 是否允许 bind-service 类型启动
        if (handleBindServiceStartAction(intent, targetApp, targetUid, callerApp, callerUid, reason)) {
            return true;
        }

        // 是否允许contentprovider 类型启动
        if (handleContentProviderStartAction(intent, targetApp, targetUid, callerApp, callerUid, reason)) {
            return true;
        }

        // 是否允许输入法类型启动
        if (isLaunchingIMEApp(intent, targetApp, targetUid, callerApp, reason)) {
            return true;
        }

        // 是否允许 send-broadcast 类型启动
        if (handleBroadcastAction(intent, targetApp, targetUid, callerApp, callerUid, reason)) {
            return true;
        }

        // 是否允许语音识别
        //handle speech recognition
        if (isRecognizerIntent(intent, targetApp, targetUid, callerApp, callerUid, reason)) {
            return true;
        }

        // 是否允许 CTS 或者adb shell 测试运行
        // allow cts app to start any other app
        // allow autotest app
        if (Util.isCts(callerApp) || isAutoTest(callerUid, callerApp, targetApp)) {
            return true;
        }

        // check deny for ultra saving mode
        //if (denyBecauseOfUltraSavingMode(intent, targetApp, targetUid, callerApp, callerUid, reason)) {
        //    return false;
        //}

        // 获取target进程 UID
        int targetUserId = UserHandle.getUserId(targetUid);
        // 获取callUser进程的 UID
        int callUserId = UserHandle.getUserId(callerUid);
        // 获取 caller 进程信息
        AppState callerAppState = mAppStateInfoCollector.getAppState(callerApp, callUserId);
        // 获取 target 进程信息
        AppState targetAppState = mAppStateInfoCollector.getAppState(targetApp, targetUserId);

        // if target app already exist
        /*if (targetAppState != null
            && targetAppState.mProcState < ActivityManager.PROCESS_STATE_CACHED_EMPTY
            && targetAppState.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT) {*/
        // 进程已经启动
        if (isAlreadyStarted(targetAppState)) {
            if (DEBUG_MORE) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": already started!!"
                + " (ProcState:" +  Util.ProcState2Str(targetAppState.mProcState)
                + " mState:" + Util.AppState2Str(targetAppState.mState)
                + ")");
            return true;
        }

        
        // check system app
        // allow to launch system app
        int launchState = checkLaunchStateByPreset(intent, targetApp, targetUid, callerApp, callerUid, reason);
        if (launchState == LAUNCH_STATE_ALLOW)
            // 允许启动
            return true;
        else if (launchState == LAUNCH_STATE_DENY)
            // 禁止启动
            return false;

        // check user setting for third-party app
        // 检查预设值白名单和省电管理界面白名单进行判断是否需要允许启动,再次进行launchState设置
        launchState = checkLaunchStateByUserSetting(intent, targetApp, targetUid, callerApp, callerUid, reason);
        if (DEBUG_MORE) Slog.d(TAG, "launchState: " + launchState);
        if (launchState == LAUNCH_STATE_ALLOW)
            return true;
        else if (launchState == LAUNCH_STATE_DENY)
            return false;

        // 待机模式下禁止待机动态的黑名单应用启动
        // not allow third party app that has been force stopped during standby
        // to be started again
        if (mStandbyStartTime > 0 && isForceStoppedAppDuringStandby(targetApp, targetUid)) {
            if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed (has been force stopped)!!");
            return false;
        }

        // 禁止系统广播启动第三方应用
        // not allow system broadcast to launch third party app
        if ((callerAppState == null ||(callerApp != null && callerApp.equals("android")))
            && REASON_BROADCAST.equals(reason)) {
            if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed!!");
            return false;
        }

        // 禁止"system ui"广播启动第三方应用
        // not allow "com.android.systemui" broadcast to launch third party app
        if (callerApp != null && callerApp.equals("com.android.systemui")
            && REASON_BROADCAST.equals(reason)) {
            if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed!!");
            return false;
        }

        // allow app to launch itself
        // 允许应用自启动
        if (targetApp.equals(callerApp)) {
            return true;
        }

        // not allow non-top app to launch other app, except launched by UserActivity
        // 允许进程本身启动自有activity,但不允许启动其他应用
        // 条件 非用户主动点击
        if (!launchedByUserActivity(intent, targetApp, targetUid, callerApp, callerUid, reason, true)) {
            if (DEBUG) {
                Slog.d(TAG,"Start Proc : "+targetApp
                    +", callingPackage = "+callerApp
                    + " (ProcState:" + (callerAppState != null ? Util.ProcState2Str(callerAppState.mProcState):"none")
                    +"), reason = "+reason
                    + ": non-UserActivity denyed!!");
            }
            return false;
        }

        // not allow background app to launch other third party app
        // 不允许后台应用关联唤醒其他应用
        // 1. caller 不用空
        // 2. caller 状态为非前台进程,mProcState 数值越大进程重要度越低
        // 3. 启动类型不为 start-acitivty
        if (callerAppState != null && callerAppState.mProcState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
            && !REASON_START_ACTIVITY.equals(reason)) {
            if (DEBUG) {
                Slog.d(TAG,"Start Proc : "+targetApp
                    +", callingPackage = "+callerApp
                    + " (ProcState:" + (callerAppState != null ? Util.ProcState2Str(callerAppState.mProcState):"none")
                    +"), reason = "+reason
                    + ": denyed!!");
            }
            return false;
        }

        // 不允许第三方应用之间进行关联唤醒
        // not allow self started third party app to start other third party app
        // 1. caller 状态不为空
        // 2. caller 启动次数为0
        // 3. 非系统应用
        // 4. 启动类型非start-activity
        if (callerAppState != null && callerAppState.mLaunchCount == 0 && !isSystemApp(callerAppState)
            && !REASON_START_ACTIVITY.equals(reason)) {
            if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed!!");
            return false;
        }

        // 不允许长时间运行的后台应用唤醒其他应用
        // not allow long idle third party app to start other third party app
        // 1. caller 状态不为空
        // 2. caller 非第三应用
        // 3. caller 不为前台进程属性
        // 4. 启动类型非start-activity
        if (callerAppState != null && !isSystemApp(callerAppState)
            && callerAppState.mState != Event.MOVE_TO_FOREGROUND
            && callerAppState.mProcState != ActivityManager.PROCESS_STATE_TOP
            && !REASON_START_ACTIVITY.equals(reason)) {

            // 当前流逝时刻
            long nowELAPSED = SystemClock.elapsedRealtime();
            // 待机场景持续时长 
            long idleDuration = 0;
            // 待机场景持续时长 = 当前流逝时刻 - caller 最近一次运行时长
            idleDuration = (callerAppState.mLastTimeUsed > 0 ? (nowELAPSED -callerAppState.mLastTimeUsed) : -1);
            // 第三方应用在后台运行超过 DENY_START_APP_THRESHOLD,则不允许第三方进程
            // 即不允许长时间运行的后台应用唤醒其他应用
            if (idleDuration > DENY_START_APP_THRESHOLD) {
                if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed!! long idle");
                return false;
            }
        }

        // 待机模式下,不允许第三方应用进行相互关联唤醒
        // not allow third party app to start other third party app during standby
        if (mStandbyStartTime > 0
            && !REASON_START_ACTIVITY.equals(reason)) {
            if (DEBUG) {
                Slog.d(TAG,"Start Proc : "+targetApp
                    +", callingPackage = "+callerApp
                    + " (ProcState:" + (callerAppState != null ? Util.ProcState2Str(callerAppState.mProcState):"none")
                    +"), reason = "+reason
                    + ": denyed during standby!!");
            }
            return false;
        }


        return true;

    }

2.1 handleStartServiceStartAction

是否允许 start-service 类型启动

是否允许 start-service 类型启动

    private static final String REASON_START_SERVICE = "start-service";


    // 是否允许 start-service 类型启动
    // return true: for should be allow to be started
    //   false: for others
    private boolean handleStartServiceStartAction(Intent intent, String targetApp, int targetUid,
            String callerApp, int callerUid, String reason) {

        // 是否为 start-service 类型启动
        if (!REASON_START_SERVICE.equals(reason)) return false;

        // 是否在安装列表中
        if (mInstalledServiceList.contains(targetApp)
            // 是否可以被其他应用启动
            && autoLaunchByOtherApp(targetApp, targetUid, callerApp, callerUid, reason)) {
            if (DEBUG) Slog.d(TAG, "start installed service :" + targetApp + " from " + callerApp);
            return true;
        }

        return false;
    }
2.1.0 是否可被关联启动 autoLaunchByOtherApp

判断这是系统启动还是被第三方应用关联启动,主要通过UID进行判断

是否为关联启动.jpg

    // 判断这是系统启动还是被第三方应用关联启动,主要通过UID进行判断
    // return true, if app is start by system broadcast
    private boolean autoLaunchByOtherApp(String targetApp, int targetUid,
                String callerApp, int callerUid, String reason) {
        // 启动发起方是否为null或android或systemui,如果是该条件,则绝对是系统进行唤醒
        if (callerApp == null
            ||callerApp.equals("android")
            || callerApp.equals("com.android.systemui"))
            return false;

        AppState callerAppState = mAppStateInfoCollector.getAppState(callerApp, UserHandle.getUserId(callerUid));
        // 唤醒者是否为系统进程调用
        if (callerAppState != null && callerAppState.mUid < Process.FIRST_APPLICATION_UID)
            return false;

        return true;
    }

2.2 handleBindServiceStartAction

是否允许 bind-service 务类型启动

是否允许 bind-service 类型启动

    private static final String REASON_BIND_SERVICE = "bind-service";

    // return true: for should be allow to be started
    //   false: for others
    private boolean handleBindServiceStartAction(Intent intent, String targetApp, int targetUid,
            String callerApp, int callerUid, String reason) {

        // 是否为 bind-service 类型启动
        if (!REASON_BIND_SERVICE.equals(reason)) return false;

        // 是否为壁纸服务
        // check start wallpaper service
        if (intent != null && WallpaperService.SERVICE_INTERFACE.equals(intent.getAction())) {

            // save the defalut wallpaper service
            if ("android".equals(callerApp))  mDefaultWallpaperService = targetApp;

            if (DEBUG) Slog.d(TAG, "start wallpaper service, allowed");
            return true;
        }

        // 是否为可用模拟辅助服务
        if ("android".equals(callerApp)
            && intent != null && mSystemPreferredConfig.isEnabledAccessibilityService(intent.getComponent())
            ) {
            if (DEBUG) Slog.d(TAG, "start enabled accessibility service" + intent.getComponent());
            return true;
        }

        // 是否为安装完成的模拟辅助服务
        // check start accessibility service
        if ("android".equals(callerApp)
            && intent != null && mSystemPreferredConfig.isInstalledAccessibilityService(intent.getComponent())
            && isUserTouchActive()) {

            if (DEBUG) Slog.d(TAG, "start installed accessibility service" + intent.getComponent());
            return true;
        }

        // 是否为语音服务
        // check start voice synthesize service
        if (intent != null && mSystemPreferredConfig.isTTSAction(intent.getAction())
            && isUserTouchActive()) {

            if (DEBUG) Slog.d(TAG, "start voice synthesize service" + intent.getAction());
            return true;
        }

        // 是否为打印服务
        // check start print service
        if ("android".equals(callerApp)
            && intent != null && mSystemPreferredConfig.isInstalledPrintService(intent.getComponent())
            && isUserTouchActive()) {

            if (DEBUG) Slog.d(TAG, "start installed print service" + intent.getComponent());
            return true;
        }

        // for sina
        if ( intent != null  && "com.sina.weibo.remotessoservice".equals(intent.getAction())
            && "com.sina.weibo".equals(targetApp)) {
            if (DEBUG) Slog.d(TAG, "allow Start:using sina to login:" +callerApp);
            return true;
        }

        // 是否为通讯服务
        // check start default phone service
        if (intent != null && "android.telecom.InCallService".equals(intent.getAction())) {

            if (DEBUG) Slog.d(TAG, "start default phone app:" + targetApp);
            return true;
        }

        // 是否为没有启动入口的程序且为关联唤醒
        if (mInstalledServiceList.contains(targetApp)
            && autoLaunchByOtherApp(targetApp, targetUid, callerApp, callerUid, reason)) {
            if (DEBUG) Slog.d(TAG, "bind installed service :" + targetApp + " from " + callerApp);
            return true;
        }

        // 是否为用户账户服务且用户当前正在触摸屏幕
        if ("android".equals(callerApp)
            && intent != null  && ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())
            && isUserTouchActive()) {
            if (DEBUG) Slog.d(TAG, "bind Account service :" + targetApp + " from " + callerApp);
            return true;
        }

        return false;
    }
    
    // 非常有创新的接口,用户当前是否有触摸屏幕操作
    private boolean isUserTouchActive() {

        long now = SystemClock.elapsedRealtime();

        long lastTouchTime = 0;
        if (mPowerControllerInternal != null)
            lastTouchTime = mPowerControllerInternal.getLastTouchEventTimeStamp();

        return ((now - lastTouchTime) <= 1000);
    }

2.3 handleContentProviderStartAction

是否为 contentprovider 类型启动

是否允许 contentprovider 类型启动

    private static final String REASON_CONTENT_PROVIDER = "contentprovider";

    // return true: for should be allow to be started
    //   false: for others
    private boolean handleContentProviderStartAction(Intent intent, String targetApp, int targetUid,
            String callerApp, int callerUid, String reason) {
            
        // 是否为 contentprovider 类型启动
        if (!REASON_CONTENT_PROVIDER.equals(reason)) return false;

        int targetAppType = mPowerControllerInternal.getAppCategoryType(targetApp);;
        AppState callerAppState = mAppStateInfoCollector.getAppState(callerApp, UserHandle.getUserId(callerUid));

        // 唤醒者是否为前台进程
        boolean isCallerTopApp  =  false;
        if (callerAppState != null
            &&((callerAppState.mProcState == ActivityManager.PROCESS_STATE_TOP)
                || (callerAppState.mState == Event.MOVE_TO_FOREGROUND))
        ) {

            isCallerTopApp = true;
        }

        // 唤醒者是否为前台进程且是否用户正在点击且是否为白名单或未知类型
        if (isCallerTopApp && isUserTouchActive()
            && (inAssociateLaunchExceptionAppList(targetApp) || PowerDataBaseControl.UNKNOWN == targetAppType)) {
            if (DEBUG) Slog.d(TAG,"allow Start: "+targetApp+", callingPackage = "+callerApp+ " reason = "+reason);
            return true;
        }

        return false;
    }

2.4 是否为输入法进程 isLaunchingIMEApp

    private boolean isLaunchingIMEApp(Intent intent, String targetApp, int targetUid,
        String callerApp, String reason) {

        //handle inputmethod
        // 是否为系统调用且为bind-service类型且为InputMethod的信号
        if ("android".equals(callerApp)
            && REASON_BIND_SERVICE.equals(reason)
            && intent != null && "android.view.InputMethod".equals(intent.getAction())) {

            if (targetApp != null) { // a input method
                // 获取当前正在使用的输入法
                int index = mEnabledInputMethodAppList.indexOf(targetApp);
                // 若当前为正在使用输入法,则添加本输入法
                if (index < 0) {
                    mEnabledInputMethodAppList.add(targetApp);
                    int userId = UserHandle.getUserId(targetUid);
                    mAppStateInfoCollector.updateAppInputMethodState(targetApp, true, userId);
                    mAppStateInfoCollector.setDefaultInputMethodApp(targetApp, userId);
                }
            }
            return true;
        }

        // allow to start input Method
        // 是否为系统调用且为bind-service类型且为当前使用输入法
        if (REASON_BIND_SERVICE.equals(reason) && "android".equals(callerApp) && isInputMethodApp(targetApp)) {
            if (DEBUG) Slog.d(TAG, "isLaunchingIMEApp: "+targetApp
                + ", callingPackage = "+callerApp+", reason = "+reason +" allow for input method");
            return true;
        }

        return false;
    }
    
    // input method app
    private boolean isInputMethodApp(String pkgName) {
        int index = mEnabledInputMethodAppList.indexOf(pkgName);
        if (index >= 0) {
            return true;
        }
        return false;
    }

2.5 是否允许 send-broadcast 类型启动-handleBroadcastAction

是否允许 send-broadcast 类型启动


    private static final String REASON_BROADCAST = "send-broadcast";

    // 是否允许 send-broadcast 类型启动
    if (handleBroadcastAction(intent, targetApp, targetUid, callerApp, callerUid, reason)) {
        return true;
    }
    
    private boolean handleBroadcastAction(Intent intent, String targetApp, int targetUid,
            String callerApp, int callerUid, String reason) {

        // 是否为send-broadcast类型校验
        if (!REASON_BROADCAST.equals(reason) || intent == null) return false;

        // 是否为系统调用且当前是桌面小部件广播
        if (intent != null
            && "android".equals(callerApp)) {
            String action = intent.getAction();
            if (action != null && action.startsWith("android.appwidget.action.")) //AppWidget action
                return true;
        }

        // 是否系统调用且为电话广播
        // check out call for phone app
        if ("android".equals(callerApp)
            && intent != null && Intent.ACTION_NEW_OUTGOING_CALL.equals(intent.getAction())
            && mSystemPreferredConfig.isDefaultPhoneApp(targetApp)) {

            if (DEBUG) Slog.d(TAG, "start phone app for out call:" + targetApp);
            return true;
        }

        // 是否为短信广播
        if (SMS_DELIVER_ACTION.equals(intent.getAction())) {
            if (DEBUG) Slog.d(TAG, "start default sms app:" + targetApp);
            return true;
        }

        // 是否为GMS推送服务广播
        // receive GMS C2DM message for bug#787547
        if ("com.google.android.gms".equals(callerApp)
            && "com.google.android.c2dm.intent.RECEIVE".equals(intent.getAction())) {
            if (DEBUG) Slog.d(TAG, "start app:" + targetApp + " for receiving message");
            return true;
        }

        return false;
    }

2.6 是否允许语音识别 isRecognizerIntent

    private boolean isRecognizerIntent(Intent intent, String targetApp, int targetUid,
            String callerApp, int callerUid, String reason) {
        AppState callerAppState = mAppStateInfoCollector.getAppState(callerApp, UserHandle.getUserId(callerUid));
        if (isSystemApp(callerAppState)
            && REASON_START_ACTIVITY.equals(reason)
            && intent != null && RecognizerIntent.ACTION_WEB_SEARCH.equals(intent.getAction())) {
            return true;
        }

        return false;
    }

2.7 是否允许 CTS 或者adb shell 测试运行

vendor/sprd/platform/frameworks/base/services/core/java/com/android/server/power/Util.java

    public static boolean isCts(String pkgName) {
        if (pkgName == null) return false;


        /*check if in internal white app list, like CTS app*/
        for(String s : mCtsWhiteAppList) {
            if(pkgName.contains(s)) {
                return true;
            }
        }

        // is cts app
        if ((pkgName.startsWith("android.") && pkgName.contains(".cts."))
            || (pkgName.startsWith("android.") && pkgName.endsWith(".cts"))
            || (pkgName.startsWith("com.android.") && pkgName.contains(".cts."))
            || (pkgName.startsWith("com.android.") && pkgName.endsWith(".cts"))) {
            return true;
        }

        // is gts app
        if ((pkgName.startsWith("com.google.") && pkgName.contains(".gts."))
            || (pkgName.startsWith("com.google.") && pkgName.endsWith(".gts"))
            || (pkgName.startsWith("com.android.") && pkgName.contains(".gts."))
            || (pkgName.startsWith("com.android.") && pkgName.endsWith(".gts"))
            || (pkgName.startsWith("com.android.compatibility.") )
            ) {
            return true;
        }

        return false;
    }


    private boolean isAutoTest(int callerUid, String callerApp, String targetApp) {
        if (callerUid == Process.SHELL_UID) {
            if (DEBUG) Slog.d(TAG, "calling from shell, see as doing auto test: " + targetApp);
            return true;
        }
        return false;
    }

2.8 判断进程是否已经启动

    private boolean isAlreadyStarted(AppState appState) {
        // if target app already exist
        if ((appState != null
                && appState.mProcState < ActivityManager.PROCESS_STATE_CACHED_EMPTY // 不为 cache 或者 空进程
                && appState.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT) // 不为不存在状态
            || (appState != null
                && appState.mProcState == ActivityManager.PROCESS_STATE_CACHED_EMPTY // cache 或者 空进程
                && Event.NONE != appState.mState)) { // 不为空
            return true;
        }
        return false;
    }

2.9 标记当前启动状态

标记当前启动状态

    private static final int LAUNCH_STATE_AUTO = 0;
    private static final int LAUNCH_STATE_ALLOW = 1;
    private static final int LAUNCH_STATE_DENY = 2;


    /**
     * Check the preset, including:
     * (1) system app should be allow to be self started
     * (2) system app that is the launcher black app list should not be self started
     */
    private int  checkLaunchStateByPreset(Intent intent, String targetApp, int targetUid,
            String callerApp, int callerUid, String reason) {
        // 获取应用安装列表
        ArrayMap<String, PackageInfo> mInstalledAppList = getInstalledAppList(UserHandle.getUserId(targetUid));

        PackageInfo targetPkg = mInstalledAppList.get(targetApp);
        // if not a system app, just return STATE_AUTO
        // 若不为系统应用,则启动状态设为LAUNCH_STATE_AUTO
        // targetPkg不为空 表示包含在第三方安装列表中
        if (targetPkg != null) {
            return LAUNCH_STATE_AUTO;
        }

        // 启动黑名单且不是用户主动点击
        if (mPowerConfig != null && mPowerConfig.inLaunchBlackList(targetApp)
            && !launchedByUserActivity(intent, targetApp, targetUid, callerApp, callerUid, reason, true)) {
            if (DEBUG) Slog.d(TAG, "in PowerConfig launch black list: " + targetApp + " denyed!!");
            return LAUNCH_STATE_DENY;
        }

        return LAUNCH_STATE_ALLOW;
    }
    
    // 应用安装列表
    private ArrayMap<String, PackageInfo> getInstalledAppList(int userId) {
        ArrayMap<String, PackageInfo> mInstalledAppList = mInstalledAppListForUsers.get(userId);
        if (mInstalledAppList == null) {
            mInstalledAppList = new ArrayMap<>();
            mInstalledAppListForUsers.put(userId, mInstalledAppList);
        }
        return mInstalledAppList;
    }
    
    // 启动黑名单
    // /system/etc/pwctl_config.xml
    public boolean inLaunchBlackList(String pkgName) {
        int index = mConstants.mLaunchBlacklist.indexOf(pkgName);
        if (index >= 0) {
            return true;
        }
        return false;
    }
  • /system/etc/pwctl_config.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<config>
<item name="LaunchBlackList">
	<packageName>com.pp.assistant</packageName>
</item>
</config>

2.9.1 是否为用户界面启动-launchedByUserActivity

是否为用户界面启动-launchedByUserActivity

    private static final String REASON_START_ACTIVITY = "start-activity";


    // check if this a launched operation by user
    // INPUT: ignoreTouchTime if true, then don't see TouchElasedTime <= (1*1000) as a userActivity
    //            else see TouchElasedTime <= (1*1000) as a userActivity for Top App
    // NOTE: this cannot decide exactly
    // 本函数的逻辑确实容易疏漏,尤其是最后一个判断,觉得有点毁掉前面的苛刻的条件 - this cannot decide exactly
    private boolean launchedByUserActivity(Intent intent, String targetApp, int targetUid,
            String callerApp, int callerUid, String reason, boolean ignoreTouchTime) {
        boolean userLaunched = false;

        // 获取当前流逝时刻
        long now = SystemClock.elapsedRealtime();
        long lastTouchTime = 0;
        AppState callerAppState = mAppStateInfoCollector.getAppState(callerApp, UserHandle.getUserId(callerUid));
        if (mPowerControllerInternal != null) {
            // 获取最近一次touch事件时间
            lastTouchTime = mPowerControllerInternal.getLastTouchEventTimeStamp();
        }

        boolean isCallerTopApp  =  false;
        // 重要进程定义:属性为前台进程或者为launch桌面进程或者正在切换为前台进程或当前发生了touch事件
        if (callerAppState != null
            &&((callerAppState.mProcState == ActivityManager.PROCESS_STATE_TOP)
                || (callerAppState.mProcState == ActivityManager.PROCESS_STATE_HOME)
                || (callerAppState.mState == Event.MOVE_TO_FOREGROUND)
                || (now - callerAppState.mLastTimeUsed) < 1000
                || isLauncherApp(callerApp)) // for Bug#712736
        ) {

            isCallerTopApp = true;
        }

        // check the associated-starting because of third party push service
        // 检查是否有进程push黑名单组件
        // 1. 检查进程为重要进程
        // 2. 且启动类型是为 start-activity
        // 3. 且进程不是top进程
        // 4. 且进程含有push黑名单组件
        if (isCallerTopApp &&
            REASON_START_ACTIVITY.equals(reason)
            && !isLauncherAction(intent)
            && intent != null
            && ThirdpartyPush.isAssociatedComponent(intent.getComponent())) {
            if (DEBUG) Slog.d(TAG,"launchedByUserActivity : Start Proc : "+targetApp
                +", callingPackage = "+callerApp
                + " (ProcState:" + (callerAppState != null ? Util.ProcState2Str(callerAppState.mProcState):"none")
                +"), reason = "+reason
                + " Associated-Starting ThirdParty Push Service");
            isCallerTopApp = false;
        }

        // see the caller of a launcher action as top app
        // 是否为非重要进程且为top进程
        // add for bug#776461
        if (!isCallerTopApp && intent != null && isLauncherAction(intent)) {
            isCallerTopApp = true;
        }

        if (DEBUG_MORE) {
            Slog.d(TAG,"launchedByUserActivity : Start Proc : "+targetApp
                +", callingPackage = "+callerApp
                + " (ProcState:" + (callerAppState != null ? Util.ProcState2Str(callerAppState.mProcState):"none")
                +"), reason = "+reason);
        }

        long lastTouchElasedTime = now - lastTouchTime;
        if (DEBUG) Slog.d(TAG, "lastTouchElasedTime: "+lastTouchElasedTime);

        // 启动类型为 start-activityq且当前正在touch事件
        if (isCallerTopApp
            && (REASON_START_ACTIVITY.equals(reason)
                    || (!ignoreTouchTime && lastTouchElasedTime <= (1*1000)))
            ) {
            // 判定用户主动点击事件
            userLaunched = true;
        }

        // check if this call from "android"
        // 检查调用者是否为android
        // 1. isCallerTopApp false
        // 2. 且启动类型是为 start-activity
        // 3. 当前存在touch事件
        // 4. android 进程当前属于前台属性
        if (!isCallerTopApp && REASON_START_ACTIVITY.equals(reason) && lastTouchElasedTime <= (1*1000)) {
            AppState androidState = mAppStateInfoCollector.getAppState("android", UserHandle.USER_SYSTEM);
            if (androidState != null
                && (androidState.mState == Event.MOVE_TO_FOREGROUND
                    || (now - androidState.mLastTimeUsed) < 1000)) {
                // 判定用户主动点击事件
                userLaunched = true;
            }
        }

        // Bug#707888 setting monkey fail --> BEG
        // 只要是系统应用就允许启动,这块我觉得做得不是很好
        // for callerApp is system app, and use "start-activity", then allow
        if ((callerAppState == null || callerAppState.mUid < Process.FIRST_APPLICATION_UID)
            && REASON_START_ACTIVITY.equals(reason)) {
            userLaunched = true;
        }
        // Bug#707888 setting monkey fail <-- END

        return userLaunched;
    }
    
    // 检测当前的action是不是启动界面程序,很好的方法
    private boolean isLauncherAction(Intent intent) {
        if (intent == null) return false;

        if (Intent.ACTION_MAIN.equals(intent.getAction())
            && intent.hasCategory(Intent.CATEGORY_LAUNCHER)
            && (intent.getComponent() != null)) {
            return true;
        }
        return false;
    }
    
    // a component name that will be used for associated-starting in third party push service
    // such as getui push service
    private final static String[] mAssociatedComponentNameList = new String[] {
        "com.igexin.sdk.GActivity", // 这个组件很贱,经常伪装前台进程唤醒
    };

2.10 检查省电管理设置界面的黑白名单

检查预设值白名单和省电管理界面白名单进行判断是否需要允许启动,再次进行launchState设置

检查省电设置界面的黑白名单设置情况

    // return launch state according to user setting
    private int checkLaunchStateByUserSetting(Intent intent, String targetApp, int targetUid,
            String callerApp, int callerUid, String reason) {

        // targetApp 进程不存在 或 targetApp 为 callerApp,表明为自启动
        if (targetApp == null || targetApp.equals(callerApp))
            return LAUNCH_STATE_AUTO;

        // 检查是否为第三方应用启动
        boolean launchByOther = autoLaunchByOtherApp(targetApp, targetUid, callerApp, callerUid, reason);

        // allow app in whitelist
        // 检查是否为CTS应用或者pwctl_config.xml配置的白名单
        if (inSelfStartWhiteAppList(targetApp)) {
            return LAUNCH_STATE_ALLOW;
        }

        // if in ultrasaving mode, don't allow autostart except apps in ultrasve applist
        // 1. 且 非关联启动
        // 2. 且 当前为超级省电模式
        // 3. 且 下一个模式为超级省电模式
        if (!launchByOther && (PowerManagerEx.MODE_ULTRASAVING == mPowerSaveMode)
            && (PowerManagerEx.MODE_ULTRASAVING == mNextPowerSaveMode)) {
            // 获取超级省电白名单
            List<String> appList = mPowerControllerInternal.getAppList_UltraMode();
            if (!appList.contains(targetApp)) {
                if (DEBUG) Slog.d(TAG, "app: " + targetApp + " in applist of ultramode, refuse to autostart, denyed");
                // 非超级省电白名单禁止启动
                return LAUNCH_STATE_DENY;
            }
        }

        // 获取省电设置界面的自启动设置情况
        int autoLaunch = mPowerControllerInternal.getAppPowerSaveConfgWithTypeInternal(targetApp,AppPowerSaveConfig.ConfigType.TYPE_AUTOLAUNCH.value);
        // 获取省电设置界面的关联启动设置情况
        int secondLaunch = mPowerControllerInternal.getAppPowerSaveConfgWithTypeInternal(targetApp,AppPowerSaveConfig.ConfigType.TYPE_SECONDARYLAUNCH.value);
        
        // public static final int VALUE_INVALID = -1;
        // public static final int VALUE_AUTO = 0; 自动
        // public static final int VALUE_OPTIMIZE = 1; 省电优化或禁止 即黑名单
        // public static final int VALUE_NO_OPTIMIZE = 2; 省电不优化或允许 即白名单
        
        // 1. 或 自启动白名单且没有被第三方应用关联启动
        // 2. 或 关联唤醒白名单且被第三方应用关联启动
        if (((autoLaunch == AppPowerSaveConfig.VALUE_NO_OPTIMIZE) && !launchByOther)
            || ((secondLaunch == AppPowerSaveConfig.VALUE_NO_OPTIMIZE) && launchByOther)) {
            if (DEBUG) Slog.d(TAG, "bgclean judgeAppLaunchAllowed: "+targetApp
                + ", callingPackage = "+callerApp+", reason = "+reason +" in my whitelist");
            // 允许启动
            return LAUNCH_STATE_ALLOW;
        }

        // 检查应用是否为黑名单
        //check whether app is in black list
        boolean bInBlackList = false;
        // 1. 且 自启动黑名单
        // 2. 且 非被其他应用关联唤醒
        if ((autoLaunch == AppPowerSaveConfig.VALUE_OPTIMIZE) && !launchByOther) {
            if (DEBUG_MORE) Slog.d(TAG, "in apppowerconfig autolaunch black list: " + targetApp);
            // 标记为黑名单flag
            bInBlackList = true;
        }
        
        // 1. 且 关联唤醒黑名单
        // 2. 且 被启动应用关联唤醒
        if ((secondLaunch == AppPowerSaveConfig.VALUE_OPTIMIZE) && launchByOther) {
            if (DEBUG_MORE) Slog.d(TAG, "in apppowerconfig 2ndlaunch black list: " + targetApp);
            // 标记为黑名单flag
            bInBlackList = true;
        }

        // check whether blacklist app is in exception
        if (bInBlackList) {
            // not allow auto start app that in auto start black list
            // 1. in mAutoLaunch_BlackList AND is not launched by other app (that is launched by system broadcast etc)
            // 2. NOTE: Exception:
            //     1) Start reason is REASON_START_ACTIVITY  (this is alway happened in case of
            //         app use another app to do something, including launcher to launch a app)
            //     2) Self start self ( that is this app is alreadby started, and is start some activity internal)
            // 1. 且 非关联启动
            // 2. 且 非start-activity 类型启动
            if (!launchByOther && !REASON_START_ACTIVITY.equals(reason)) {
                if (DEBUG) Slog.d(TAG, "in autolaunch black list: "+targetApp
                    + ", callingPackage = "+callerApp+", reason = "+reason +" denyed!");
                // 设置为禁止启动状态
                return LAUNCH_STATE_DENY;
            }
            // not allow auto start by other app that in secondary start black list
            // 1. in m2ndLaunch_BlackList AND is launched by other app
            // 2. NOTE: Exception:
            //     1) when callerApp is top AND Start reason is REASON_START_ACTIVITY  (this is alway happened in case of
            //         app use another app to do something, including launcher to launch a app)
            //     2) Self start self ( that is this app is alreadby started, and is start some activity internal)
            //     3) when callerApp is top AND targetApp is in AssociateLaunchExceptionAppList
            // 1. 且 关联启动
            // 2. 且非用户主动点击
            if (launchByOther && !launchedByUserActivity(intent, targetApp, targetUid, callerApp, callerUid, reason, true)) {
                if (DEBUG) Slog.d(TAG, "in 2ndlaunch black list: "+targetApp
                    + ", callingPackage = "+callerApp+", reason = "+ reason + " intent:" + intent + " denyed!");
                return LAUNCH_STATE_DENY;
            }

            // Although it is black list, but it start by user, so allowed to start
            return LAUNCH_STATE_ALLOW;
        }
        return LAUNCH_STATE_AUTO;
    }
2.10.1 是否为白名单应用启动
    
    private boolean inSelfStartWhiteAppList(String pkgName) {
        if (inCommonWhiteAppList(pkgName))
            return true;

        // if setting in the LaunchWhiteList in pwctl_config.xml
        if (mPowerConfig != null && mPowerConfig.inLaunchWhiteList(pkgName)) {
            if (DEBUG) Slog.d(TAG, "in PowerConfig launch white list: " + pkgName);
            return true;
        }

        return false;
    }
    
    /**
     * 这里主要为CTS
     * Common white list, that is  for both lockScreen manager and auto self starting manager
     */
    private boolean inCommonWhiteAppList(String pkgName) {
        if (pkgName == null) return true;
    
        /** auto start and lock screen clean do not need to care about doze white list
         *        try {
         *         if (mPowerControllerInternal.isWhitelistApp(pkgName)) {
         *               return true;
         *           }
         *       } catch (Exception e) {
         *       }
        */

        // 在 /system/etc/pwctl_config.xml 目录预先配置的白名单
        /*check if in internal white app list, like CTS app*/
        for(String s : mInternalWhiteAppList) {
            if(pkgName.contains(s)) {
                if (DEBUG) Slog.d(TAG, "Internal white applist: " + pkgName);
                return true;
            }
        }

        // is cts/gts app
        if (Util.isCts(pkgName)) {
            if (DEBUG) Slog.d(TAG, "CTS/GTS app: " + pkgName + ", see as in white list!!");
            return true;
        }

        return false;
    }
    
    // This white list is used for some app like CTS
    private final String[] mInternalWhiteAppList = new String[] {
        "com.google.android.gms",
        "com.google.android.gsf",
        "android.app.cts",
        "com.android.cts",
        "android.icu.dev.test.util"
    };
    
    
    public boolean inLaunchWhiteList(String pkgName) {
        int index = mConstants.mLaunchWhitelist.indexOf(pkgName);
        if (index >= 0) {
            return true;
        }
        return false;
    }

2.11 待机模式下禁止动态待机黑名单启动

    // 待机模式下禁止第三方应用启动
    // not allow third party app that has been force stopped during standby
    // to be started again
    // 1. 待机时长大于0
    // 2. 是否为待机模式禁止被启动的应用
    if (mStandbyStartTime > 0 && isForceStoppedAppDuringStandby(targetApp, targetUid)) {
        if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed (has been force stopped)!!");
        return false;
    }
===============================================================================================================
    // to recode the system elapsed time when starting standby
    private long mStandbyStartTime = 0;
    
    /*
     * Note device is enter standby state ( screen off / unpluged)
     */
    void noteDeviceStateChanged(boolean bStandby, final long nowELAPSED) {
        if (DEBUG) Slog.d(TAG, "noteDeviceStateChanged bStandby:" + bStandby);

        // 更新待机时间
        if (bStandby) mStandbyStartTime = nowELAPSED; //SystemClock.elapsedRealtime();
        else mStandbyStartTime = 0;

        ....
    }
===============================================================================================================

    // 是否为待机模式禁止被启动的应用
    private boolean isForceStoppedAppDuringStandby(String pkgName, int uid) {
        ArrayMap<String, Integer> mStoppedAppList = getStoppedAppList(UserHandle.getUserId(uid));

        return mStoppedAppList.containsKey(pkgName);
    }
    
    private ArrayMap<String, Integer> getStoppedAppList(int userId) {
        ArrayMap<String, Integer> mStoppedAppList = mStoppedAppListForUsers.get(userId);
        // 对应的userid不存在,则加入StoppedAppList中,便于下次待机中可以取到名单吗?这个逻辑暂时有些绕,存在疑问
        if (mStoppedAppList == null) {
            mStoppedAppList = new ArrayMap<>();
            mStoppedAppListForUsers.put(userId, mStoppedAppList);
        }
        return mStoppedAppList;
    }
    
    // Apps that have be stopped during this standby period
    // it will clear when exit standby state
    //private ArrayList<String> mStoppedAppList = new ArrayList<>();
    private SparseArray<ArrayMap<String,Integer>> mStoppedAppListForUsers = new SparseArray<>(); //<pkgName, userId>

2.12 禁止系统广播启动第三方应用

这里与 handleBroadcastAction()函数功能存在重复,且 handleBroadcastAction 的规避条件更多一些,或者本意是禁止 handleBroadcastAction 没有涉及的一些广播

=================================================================================================
    // not allow system broadcast to launch third party app
    // 1. caller 不为空
    // 2. caller 为系统(android)
    // 3. 启动类型是 send-broadcast
    if ((callerAppState == null ||(callerApp != null && callerApp.equals("android")))
        && REASON_BROADCAST.equals(reason)) {
        if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed!!");
        return false;
    }

2.13 禁止system ui广播启动第三方应用

        // 禁止"system ui"广播启动第三方应用
        // not allow "com.android.systemui" broadcast to launch third party app
        // 1. caller 为system ui
        // 2. 启动类型为send-broadcast 
        if (callerApp != null && callerApp.equals("com.android.systemui")
            && REASON_BROADCAST.equals(reason)) {
            if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed!!");
            return false;
        }

2.14 不允许后台应用关联唤醒其他应用

  • frameworks/base/core/java/android/app/ActivityManager.java

    // not allow background app to launch other third party app
    // 不允许后台应用关联唤醒其他应用
    // 1. caller 不用空
    // 2. caller 状态为非前台进程,mProcState 数值越大进程重要度越低
    // 3. 启动类型不为 start-acitivty
    if (callerAppState != null && callerAppState.mProcState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
        && !REASON_START_ACTIVITY.equals(reason)) {
        if (DEBUG) {
            Slog.d(TAG,"Start Proc : "+targetApp
                +", callingPackage = "+callerApp
                + " (ProcState:" + (callerAppState != null ? Util.ProcState2Str(callerAppState.mProcState):"none")
                +"), reason = "+reason
                + ": denyed!!");
        }
        return false;
    }

    /** @hide Process is a persistent system process. */
    public static final int PROCESS_STATE_PERSISTENT = 0;

    /** @hide Process is a persistent system process and is doing UI. */
    public static final int PROCESS_STATE_PERSISTENT_UI = 1;

    /** @hide Process is hosting the current top activities.  Note that this covers
     * all activities that are visible to the user. */
    public static final int PROCESS_STATE_TOP = 2;

    /** @hide Process is hosting a foreground service due to a system binding. */
    public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3;

    /** @hide Process is hosting a foreground service. */
    public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;

    /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
    public static final int PROCESS_STATE_TOP_SLEEPING = 5;

    /** @hide Process is important to the user, and something they are aware of. */
    public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;

    /** @hide Process is important to the user, but not something they are aware of. */
    public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;

    /** @hide Process is in the background transient so we will try to keep running. */
    public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 8;

    /** @hide Process is in the background running a backup/restore operation. */
    public static final int PROCESS_STATE_BACKUP = 9;

    /** @hide Process is in the background, but it can't restore its state so we want
     * to try to avoid killing it. */
    public static final int PROCESS_STATE_HEAVY_WEIGHT = 10;

    /** @hide Process is in the background running a service.  Unlike oom_adj, this level
     * is used for both the normal running in background state and the executing
     * operations state. */
    public static final int PROCESS_STATE_SERVICE = 11;

    /** @hide Process is in the background running a receiver.   Note that from the
     * perspective of oom_adj receivers run at a higher foreground level, but for our
     * prioritization here that is not necessary and putting them below services means
     * many fewer changes in some process states as they receive broadcasts. */
    public static final int PROCESS_STATE_RECEIVER = 12;

    /** @hide Process is in the background but hosts the home activity. */
    public static final int PROCESS_STATE_HOME = 13;

    /** @hide Process is in the background but hosts the last shown activity. */
    public static final int PROCESS_STATE_LAST_ACTIVITY = 14;

    /** @hide Process is being cached for later use and contains activities. */
    public static final int PROCESS_STATE_CACHED_ACTIVITY = 15;

    /** @hide Process is being cached for later use and is a client of another cached
     * process that contains activities. */
    public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16;

    /** @hide Process is being cached for later use and is empty. */
    public static final int PROCESS_STATE_CACHED_EMPTY = 17;

    /** @hide Process does not exist. */
    public static final int PROCESS_STATE_NONEXISTENT = 18;

    /** @hide The lowest process state number */
    public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT;

    /** @hide The highest process state number */
    public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;

2.15 不允许长时间运行的后台应用唤醒其他应用

======================================================================================
    // not allow long idle third party app to start other third party app
    // 1. caller 状态不为空
    // 2. caller 非第三应用
    // 3. caller 不为前台进程属性
    // 4. 启动类型非start-activity
    if (callerAppState != null && !isSystemApp(callerAppState)
        && callerAppState.mState != Event.MOVE_TO_FOREGROUND
        && callerAppState.mProcState != ActivityManager.PROCESS_STATE_TOP
        && !REASON_START_ACTIVITY.equals(reason)) {

        // 当前流逝时刻
        long nowELAPSED = SystemClock.elapsedRealtime();
        // 待机场景持续时长 
        long idleDuration = 0;
        // 待机场景持续时长 = 当前流逝时刻 - caller 最近一次运行时长
        idleDuration = (callerAppState.mLastTimeUsed > 0 ? (nowELAPSED -callerAppState.mLastTimeUsed) : -1);
        // 第三方应用在后台运行超过 DENY_START_APP_THRESHOLD,则不允许第三方进程
        // 即不允许长时间运行的后台应用唤醒其他应用
        if (idleDuration > DENY_START_APP_THRESHOLD) {
            if (DEBUG) Slog.d(TAG,"Start Proc : "+targetApp+", callingPackage = "+callerApp+", reason = "+reason + ": denyed!! long idle");
            return false;
        }
    }
======================================================================================
    // if a non-top app has been idle for DENY_START_APP_THRESHOLD
    // then don't allow this app to start other 3-party app
    // 5 分钟 或者 30 分钟
    private final long DENY_START_APP_THRESHOLD = (TEST ? 5 * 60 * 1000L : 30 * 60 * 1000L);


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

法迪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值