android cta 权限,[Android][Framework]PackageManagerService处理应用权限流程

app种类

1、system app (有ApplicationInfo.FLAG_SYSTEM标记)

2、privileged app (有ApplicationInfo.FLAG_SYSTEM和ApplicationInfo.PRIVATE_FLAG_PRIVILEGE两个标记)

system app

system app 定义很明了,就是在PMS初始化安装app的时候赋予了ApplicationInfo.FLAG_SYSTEM这个标记

1、特定shareUID的app

代码在PMS的构造函数中

mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,

ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,

ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

mSettings.addSharedUserLPw("android.uid.log", LOG_UID,

ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,

ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,

ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,

ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

2、扫描安装特定目录的app

代码在PMS的构造函数中,扫描安装时给予PackageParser.PARSE_IS_SYSTEM标记的app,例如/vendor/overlay,/system/framework,/system/priv-app,/system/app,/vendor/app,/oem/app等,给予的PackageParser.PARSE_IS_SYSTEM最终会转换为ApplicationInfo.FLAG_SYSTEM,部分代码如下

File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);

scanDirTracedLI(vendorOverlayDir, mDefParseFlags

| PackageParser.PARSE_IS_SYSTEM

| PackageParser.PARSE_IS_SYSTEM_DIR

| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

File customFrameworkDir = new File("/custom/framework");

scanDirLI(customFrameworkDir, PackageParser.PARSE_IS_SYSTEM

| PackageParser.PARSE_IS_SYSTEM_DIR,

scanFlags | SCAN_NO_DEX, 0);

scanDirTracedLI(frameworkDir, mDefParseFlags

| PackageParser.PARSE_IS_SYSTEM

| PackageParser.PARSE_IS_SYSTEM_DIR

| PackageParser.PARSE_IS_PRIVILEGED,

scanFlags | SCAN_NO_DEX, 0);

flag转换的过程大致如下

-> scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime)

-> scanPackageTracedLI(PackageParser.Package pkg, final int policyFlags,

int scanFlags, long currentTime, UserHandle user)

-> scanPackageLI(PackageParser.Package pkg, final int policyFlags,

int scanFlags, long currentTime, UserHandle user)

-> scanPackageDirtyLI(PackageParser.Package pkg,

final int policyFlags, final int scanFlags, long currentTime, UserHandle user)

//在scanPackageDirtyLI方法中将flag转换

// Apply policy

if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {

pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;

...

}

privileged app

privileged app在ApplicationInfo.FLAG_SYSTEM基础上还必须有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED标记

1、特定shareUID的app

特定shareUID的app有ApplicationInfo.FLAG_SYSTEM的同时都有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED

2、扫描特定目录app时,给予了PackageParser.PARSE_IS_SYSTEM标记和PackageParser.PARSE_IS_PRIVILEGED标记,目录有三个:system/framework,system/priv-app,vendor/priv-app

例如:

//vender/framework目录下有PackageParser.PARSE_IS_SYSTEM没有PackageParser.PARSE_IS_PRIVILEGED标记

File vendorFrameworkDir = new File(Environment.getVendorDirectory(), "framework");

scanDirTracedLI(vendorFrameworkDir, PackageParser.PARSE_IS_SYSTEM

| PackageParser.PARSE_IS_SYSTEM_DIR,

scanFlags | SCAN_NO_DEX, 0);

//system/framework目录下两个标记都有

scanDirTracedLI(frameworkDir, mDefParseFlags

| PackageParser.PARSE_IS_SYSTEM

| PackageParser.PARSE_IS_SYSTEM_DIR

| PackageParser.PARSE_IS_PRIVILEGED,

scanFlags | SCAN_NO_DEX, 0);

PackageParser.PARSE_IS_PRIVILEGED也是在scanPackageDirtyLI方法中转换的

if ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {

pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;

}

PMS赋予apk的runtime权限

入口

PMS(PackageManagerService)在SystemServer中初始化完成最后调用PMS::systemReady,在systemReady方法中开始授予默认权限

final DefaultPermissionGrantPolicy mDefaultPermissionPolicy;

for (int userId : grantPermissionsUserIds) {

mDefaultPermissionPolicy.grantDefaultPermissions(userId);

}

DefaultPermissionGrantPolicy这个类就是管理默认权限的,最终的实现都是在PMS代码中

public void grantDefaultPermissions(int userId) {

// 系统组件赋予Dangerous权限

grantPermissionsToSysComponentsAndPrivApps(userId);

// 指定app的指定权限

grantDefaultSystemHandlerPermissions(userId);

}

系统组件赋予Dangerous权限private void grantPermissionsToSysComponentsAndPrivApps(int userId) {

Log.i(TAG, "Granting permissions to platform components for user " + userId);

synchronized (mService.mPackages) {

for (PackageParser.Package pkg : mService.mPackages.values()) {

// 过滤掉privileged app , FLAG_PERSISTENT标记app ,系统签名相同的app

if (!isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg)

// 判断targetSdkVersion > 22

|| !doesPackageSupportRuntimePermissions(pkg)

// 请求权限列表为空

|| pkg.requestedPermissions.isEmpty()) {

continue;

}

Setpermissions = new ArraySet<>();

final int permissionCount = pkg.requestedPermissions.size();

for (int i = 0; i < permissionCount; i++) {

String permission = pkg.requestedPermissions.get(i);

BasePermission bp = mService.mSettings.mPermissions.get(permission);

// 权限为Dangerous权限

if (bp != null && bp.isRuntime()) {

permissions.add(permission);

}

}

if (!permissions.isEmpty()) {

// 添加权限

grantRuntimePermissionsLPw(pkg, permissions, true, userId);

}

}

}

}

接下来看下isSysComponentOrPersistentPlatformSignedPrivAppLPr和isRuntime的内容

private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {

// UserID 小于10000

if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {

return true;

}

// privileged app过滤

if (!pkg.isPrivilegedApp()) {

return false;

}

// 这两个判断过滤掉FLAG_PERSISTENT

PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);

if (sysPkg != null && sysPkg.pkg != null) {

if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {

return false;

}

} else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {

return false;

}

// 过滤掉系统签名相同的apk

return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,

pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;

}

public boolean isRuntime() {

// protectionLevel & PermissionInfo.PROTECTION_MASK_BASE 表示当前等级

return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)

== PermissionInfo.PROTECTION_DANGEROUS;

}

指定app的指定权限

这里是给指定某个app,给予它指定的几个权限,这里的代码十分类似,截取片段如下

private void grantDefaultSystemHandlerPermissions(int userId) {

...

// Dialer

if (dialerAppPackageNames == null) {

Intent dialerIntent = new Intent(Intent.ACTION_DIAL);

PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackageLPr(

dialerIntent, userId);

if (dialerPackage != null) {

grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);

}

} else {

for (String dialerAppPackageName : dialerAppPackageNames) {

PackageParser.Package dialerPackage = getSystemPackageLPr(dialerAppPackageName);

if (dialerPackage != null) {

grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);

}

}

}

// SMS

if (smsAppPackageNames == null) {

Intent smsIntent = new Intent(Intent.ACTION_MAIN);

smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);

PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackageLPr(

smsIntent, userId);

if (smsPackage != null) {

grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);

}

} else {

for (String smsPackageName : smsAppPackageNames) {

PackageParser.Package smsPackage = getSystemPackageLPr(smsPackageName);

if (smsPackage != null) {

grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);

}

}

}

...

}

private void grantDefaultPermissionsToDefaultSystemDialerAppLPr(

PackageParser.Package dialerPackage, int userId) {

if (doesPackageSupportRuntimePermissions(dialerPackage)) {

boolean isPhonePermFixed =

mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0);

grantRuntimePermissionsLPw(

dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);

grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, userId);

grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, userId);

grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, userId);

}

}

两种添加最后都走到了grantRuntimePermissionsLPw,我们接着分析grantRuntimePermissionsLPw函数

添加权限grantRuntimePermissionsLPw

grantRuntimePermissionsLPw赋予权限的代码,最终是交给PMS来处理,经过一些列判断后调用关键方法mService.grantRuntimePermission和mService.updatePermissionFlags,代码片段如下

private final PackageManagerService mService;

private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Setpermissions,

boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {

...

mService.grantRuntimePermission(pkg.packageName, permission, userId);

...

mService.updatePermissionFlags(permission, pkg.packageName,

newFlags, newFlags, userId);

...

}

接着看PMS的grantRuntimePermission如何添加权限

public void grantRuntimePermission(String packageName, String name, final int userId) {

...

// 要添加权限,也需要“添加”权限

mContext.enforceCallingOrSelfPermission(

android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,

"grantRuntimePermission");

enforceCrossUserPermission(Binder.getCallingUid(), userId,

true /* requireFullPermission */, true /* checkShell */,

"grantRuntimePermission");

...

// 添加权限

final int result = permissionsState.grantRuntimePermission(bp, userId);

switch (result) {

case PermissionsState.PERMISSION_OPERATION_FAILURE: {

return;

}

case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {

final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);

mHandler.post(new Runnable() {

@Override

public void run() {

killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);

}

});

}

break;

}

// PermissionsChanged监听

mOnPermissionChangeListeners.onPermissionsChanged(uid);

// 把数据更新到runtime-permissions.xml中

mSettings.writeRuntimePermissionsForUserLPr(userId, false);

}

最后grantRuntimePermission就是把permission存到mPermissions数据map中,再把数据跟新到/data/system/users/0/runtime-permissions.xml中,片段如下

PMS赋予apk安装权限

过程精简如下:

-> scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime)

-> scanPackageTracedLI(PackageParser.Package pkg, final int policyFlags,

int scanFlags, long currentTime, UserHandle user)

-> scanPackageLI(PackageParser.Package pkg, final int policyFlags,

int scanFlags, long currentTime, UserHandle user)

-> scanPackageDirtyLI(PackageParser.Package pkg,

final int policyFlags, final int scanFlags, long currentTime, UserHandle user)

-> updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,

int[] allUsers, PackageInstalledInfo res, UserHandle user)

-> updateSettingsInternalLI(PackageParser.Package newPackage,

String installerPackageName, int[] allUsers, int[] installedForUsers,

PackageInstalledInfo res, UserHandle user)

-> updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,

int flags)

-> updatePermissionsLPw(String changingPkg,

PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags)

-> grantPermissionsLPw(PackageParser.Package pkg, boolean replace,

String packageOfInterest)

在grantPermissionsLPw函数中做最后权限赋予操作,代码片段如下:

private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,

String packageOfInterest) {

for (int i=0; i= Build.VERSION_CODES.M;

// 根据权限级别定义grant

switch (level) {

case PermissionInfo.PROTECTION_NORMAL: {

// For all apps normal permissions are install time ones.

grant = GRANT_INSTALL;

} break;

case PermissionInfo.PROTECTION_DANGEROUS: {

if (!appSupportsRuntimePermissions && !Build.isPermissionReviewRequired()) {

// For legacy apps dangerous permissions are install time ones.

grant = GRANT_INSTALL;

} else if (origPermissions.hasInstallPermission(bp.name)) {

// For legacy apps that became modern, install becomes runtime.

grant = GRANT_UPGRADE;

} else if (mPromoteSystemApps

&& isSystemApp(ps)

&& mExistingSystemPackages.contains(ps.name)) {

// For legacy system apps, install becomes runtime.

// We cannot check hasInstallPermission() for system apps since those

// permissions were granted implicitly and not persisted pre-M.

grant = GRANT_UPGRADE;

} else {

// For modern apps keep runtime permissions unchanged.

grant = GRANT_RUNTIME;

}

} break;

case PermissionInfo.PROTECTION_SIGNATURE: {

// For all apps signature permissions are install time ones.

allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);

if (allowedSig) {

grant = GRANT_INSTALL;

}

} break;

}

...

if (grant != GRANT_DENIED) {

if (!isSystemApp(ps) && ps.installPermissionsFixed) {

// If this is an existing, non-system package, then

// we can't add any new permissions to it.

if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {

// Except... if this is a permission that was added

// to the platform (note: need to only do this when

// updating the platform).

if (!isNewPlatformPermissionForPackage(perm, pkg)) {

grant = GRANT_DENIED;

}

}

}

// 根据grant 选择权限给予方式

switch (grant) {

case GRANT_INSTALL: {

for (int userId : UserManagerService.getInstance().getUserIds()) {

if (origPermissions.getRuntimePermissionState(

bp.name, userId) != null) {

// Revoke the runtime permission and clear the flags.

origPermissions.revokeRuntimePermission(bp, userId);

origPermissions.updatePermissionFlags(bp, userId,

PackageManager.MASK_PERMISSION_FLAGS, 0);

// If we revoked a permission permission, we have to write.

changedRuntimePermissionUserIds = ArrayUtils.appendInt(

changedRuntimePermissionUserIds, userId);

}

}

// install权限

if (permissionsState.grantInstallPermission(bp) !=

PermissionsState.PERMISSION_OPERATION_FAILURE) {

changedInstallPermission = true;

}

} break;

case GRANT_RUNTIME: {

// Grant previously granted runtime permissions.

for (int userId : UserManagerService.getInstance().getUserIds()) {

PermissionState permissionState = origPermissions

.getRuntimePermissionState(bp.name, userId);

int flags = permissionState != null

? permissionState.getFlags() : 0;

if (origPermissions.hasRuntimePermission(bp.name, userId)) {

if (permissionsState.grantRuntimePermission(bp, userId) ==

PermissionsState.PERMISSION_OPERATION_FAILURE) {

// If we cannot put the permission as it was, we have to write.

changedRuntimePermissionUserIds = ArrayUtils.appendInt(

changedRuntimePermissionUserIds, userId);

}

// If the app supports runtime permissions no need for a review.

/// M: CTA requirement - permission control

if (Build.isPermissionReviewRequired()

&& appSupportsRuntimePermissions

&& (flags & PackageManager

.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {

flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;

// Since we changed the flags, we have to write.

changedRuntimePermissionUserIds = ArrayUtils.appendInt(

changedRuntimePermissionUserIds, userId);

}

/// M: CTA requirement - permission control

} else if (Build.isPermissionReviewRequired()

&& !appSupportsRuntimePermissions) {

if (CtaUtils.isPlatformPermission(bp.sourcePackage, bp.name)

&& pkgReviewRequired) {

if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {

/// M: CTA requirement - review UI for all apps @{

Slog.d(TAG, "add review UI for legacy pkg = " +

pkg.packageName + ", permission = " +

bp.name + ", userId = " + userId +

", uid = " + pkg.mSharedUserId);

///@}

flags |= FLAG_PERMISSION_REVIEW_REQUIRED;

// We changed the flags, hence have to write.

changedRuntimePermissionUserIds = ArrayUtils.appendInt(

changedRuntimePermissionUserIds, userId);

}

}

// runtime权限

if (permissionsState.grantRuntimePermission(bp, userId)

!= PermissionsState.PERMISSION_OPERATION_FAILURE) {

// We changed the permission, hence have to write.

changedRuntimePermissionUserIds = ArrayUtils.appendInt(

changedRuntimePermissionUserIds, userId);

}

/// M: CTA requirement - review UI for all apps @{

} else if (appSupportsRuntimePermissions &&

pkgReviewRequired) {

if (CtaUtils.isPlatformPermission(bp.sourcePackage, bp.name)) {

if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0 &&

(flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)

== 0) {

Slog.d(TAG, "add review UI for non-legacy pkg = " +

pkg.packageName + ", permission = " +

bp.name + ", userId = " + userId +

", uid = " + pkg.mSharedUserId);

flags |= FLAG_PERMISSION_REVIEW_REQUIRED;

// We changed the flags, hence have to write.

changedRuntimePermissionUserIds = ArrayUtils.appendInt(

changedRuntimePermissionUserIds, userId);

}

}

}

///@}

// Propagate the permission flags.

permissionsState.updatePermissionFlags(bp, userId, flags, flags);

}

} break;

case GRANT_UPGRADE: {

// Grant runtime permissions for a previously held install permission.

PermissionState permissionState = origPermissions

.getInstallPermissionState(bp.name);

final int flags = permissionState != null ? permissionState.getFlags() : 0;

if (origPermissions.revokeInstallPermission(bp)

!= PermissionsState.PERMISSION_OPERATION_FAILURE) {

// 跟新应用的权限

origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,

PackageManager.MASK_PERMISSION_FLAGS, 0);

changedInstallPermission = true;

}

// If the permission runtime-permissions.xmlis not to be promoted to runtime we ignore it and

// also its other flags as they are not applicable to install permissions.

if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {

for (int userId : currentUserIds) {

if (permissionsState.grantRuntimePermission(bp, userId) !=

PermissionsState.PERMISSION_OPERATION_FAILURE) {

// Transfer the permission flags.

permissionsState.updatePermissionFlags(bp, userId,

flags, flags);

// If we granted the permission, we have to write.

changedRuntimePermissionUserIds = ArrayUtils.appendInt(

changedRuntimePermissionUserIds, userId);

}

}

}

} break;

default: {

if (packageOfInterest == null

|| packageOfInterest.equals(pkg.packageName)) {

Slog.w(TAG, "Not granting permission " + perm

+ " to package " + pkg.packageName

+ " because it was previously installed without");

}

} break;

}

} else {

if (permissionsState.revokeInstallPermission(bp) !=

PermissionsState.PERMISSION_OPERATION_FAILURE) {

// Also drop the permission flags.

permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,

PackageManager.MASK_PERMISSION_FLAGS, 0);

changedInstallPermission = true;

Slog.i(TAG, "Un-granting permission " + perm

+ " from package " + pkg.packageName

+ " (protectionLevel=" + bp.protectionLevel

+ " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)

+ ")");

} else if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) == 0) {

// Don't print warning for app op permissions, since it is fine for them

// not to be granted, there is a UI for the user to decide.

if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {

Slog.w(TAG, "Not granting permission " + perm

+ " to package " + pkg.packageName

+ " (protectionLevel=" + bp.protectionLevel

+ " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)

+ ")");

}

}

}

}

if ((changedInstallPermission || replace) && !ps.installPermissionsFixed &&

!isSystemApp(ps) || isUpdatedSystemApp(ps)){

// This is the first that we have heard about this package, so the

// permissions we have now selected are fixed until explicitly

// changed.

ps.installPermissionsFixed = true;

}

// 把runtime权限跟新到runtime-permissions.xml中

for (int userId : changedRuntimePermissionUserIds) {

mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);

}

Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);

}

grantPermissionsLPw函数根据权限等级,来赋予权限。注意:这里的安装时赋予的runtime权限是之前已经赋予过此apk的权限才会执行到GRANT_RUNTIME

回到上面的精简过程:

updateSettingsInternalLI方法中调用updatePermissionsLPw后,最后会调用mSettings.writeLPr();

这些权限信息就被写到data/system/packages.xml中了,举例片段代码:

...

...

小结

1、PMS扫描安装时赋予安装权限

2、PMS在systemReady函数中调用mDefaultPermissionPolicy.grantDefaultPermissions来赋予运行权限即dangerous权限

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值