本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

前言

前两篇文章中介绍了user_grant权限的首次申请和再次申请。huanyou另一种特殊的权限类型——manual_settings权限

manual_settings权限是鸿蒙系统中权限级别最高、管控最严格的一类权限,例如拦截键盘输入事件等敏感能力。

一、manual_settings权限

manual_settings权限指的是需要用户在系统设置中手动开启的权限,这类权限涉及用户隐私或可能对系统产生严重影响。

典型manual_settings权限

  • ohos.permission.HOOK_KEY_EVENT - 拦截键盘输入事件(PC/2in1设备)

  • 其他涉及系统核心能力的敏感权限

1.2 manual_settings权限的特点

特点 说明
授权方式 手动设置授权,无法通过弹窗申请
涉及数据 用户个人信息或对系统有严重影响的操作
申请流程 需要AGC Profile + ACL权限 + 引导设置
弹窗能力 不能直接申请,只能引导用户去设置

1.3 与user_grant权限的对比

对比项 user_grant权限 manual_settings权限
授权方式 弹窗授权 手动设置
申请接口 requestPermissionsFromUser openPermissionOnSetting
再次申请 requestPermissionOnSetting openPermissionOnSetting
AGC配置 普通声明即可 需要Profile + ACL
敏感程度 较高 最高

二、申请manual_settings权限的流程

2.1 四步走

步骤1:在AGC申请Profile文件并添加ACL权限
    ↓
步骤2:在module.json5中声明权限
    ↓
步骤3:运行时检查权限 → 未授权则引导用户去设置
    ↓
步骤4:处理授权结果

2.2 步骤1:AGC侧申请Profile

因为manual_settings权限级别很高,普通应用默认无法申请,需要通过ACL(访问控制列表)方式申请。

操作步骤

  1. 登录AppGallery Connect

  2. 进入"我的应用" → 选择应用

  3. 进入"用户与权限" → "权限管理"

  4. 申请发布Profile,并在Profile内添加ACL权限

详细步骤请参考:申请发布Profile

2.3 步骤2:在module.json5中声明权限

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.HOOK_KEY_EVENT",
        "reason": "$string:hook_key_event_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ]
  }
}

注意:即使是manual_settings权限,也需要填写reasonusedScene字段。

三、核心开发步骤

3.1 导入所需模块

import { abilityAccessCtrl, bundleManager, Permissions, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

3.2 检查权限是否已授权

// PermissionChecker.ets
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

async function checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    
    // 1. 获取应用程序的accessTokenID
    let tokenId: number = 0;
    try {
        let bundleInfo: bundleManager.BundleInfo = 
            await bundleManager.getBundleInfoForSelf(
                bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
            );
        let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
        tokenId = appInfo.accessTokenId;
    } catch (error) {
        const err: BusinessError = error as BusinessError;
        console.error(`Failed to get bundle info, code: ${err.code}, message: ${err.message}`);
    }

    // 2. 校验应用是否被授予权限
    try {
        grantStatus = await atManager.checkAccessToken(tokenId, permission);
    } catch (error) {
        const err: BusinessError = error as BusinessError;
        console.error(`Failed to check access token, code: ${err.code}, message: ${err.message}`);
    }

    return grantStatus;
}

async function checkHookKeyEventPermission(): Promise<void> {
    let permission = 'ohos.permission.HOOK_KEY_EVENT';
    let grantStatus: boolean = await checkPermissionGrant(permission) 
        === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;

    if (grantStatus) {
        // 已经授权,可以继续访问目标操作
        console.info('permission is granted.');
        this.startHookKeyEvent();
    } else {
        // 未授权,引导用户跳转到系统设置
        console.info('permission is not granted.');
        this.guideToPermissionSetting();
    }
}

3.3 引导用户跳转到设置页面

方式一:在UIAbility中引导
// OpenPermAbility.ets
import { abilityAccessCtrl, common, Permissions, UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

function openPermOnSetting(permission: Permissions, context: common.UIAbilityContext): void {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    
    // openPermissionOnSetting会判断权限的授权状态来决定是否唤起弹窗
    atManager.openPermissionOnSetting(context, permission)
        .then((data: abilityAccessCtrl.SelectedResult) => {
            if (data === abilityAccessCtrl.SelectedResult.REJECTED) {
                // 用户不允许跳转到"设置"
                console.info('user not allowed.');
            } else if (data === abilityAccessCtrl.SelectedResult.OPENED) {
                // 用户选择跳转到"设置"
                console.info('user allowed to setting.');
            } else {
                // 权限已授权,无需弹窗
                console.info('permission is granted.');
            }
        })
        .catch((err: BusinessError) => {
            console.error(`Failed to openPermissionOnSetting, code: ${err.code}, message: ${err.message}`);
        });
}

export default class OpenPermAbility extends UIAbility {
    onWindowStageCreate(windowStage: window.WindowStage): void {
        // 重要:需要在loadContent回调中调用
        windowStage.loadContent('openpermpages/Index', (err) => {
            if (!err) {
                openPermOnSetting('ohos.permission.HOOK_KEY_EVENT', this.context);
            }
        });
    }
}
方式二:在UI中引导
// Index.ets
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

function openPermOnSetting(permission: Permissions, context: common.UIAbilityContext): void {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    
    atManager.openPermissionOnSetting(context, permission)
        .then((data: abilityAccessCtrl.SelectedResult) => {
            if (data === abilityAccessCtrl.SelectedResult.REJECTED) {
                console.info('user not allowed.');
                this.showManualGuide();
            } else if (data === abilityAccessCtrl.SelectedResult.OPENED) {
                console.info('user allowed to setting.');
                // 等待用户从设置返回后重新检查权限
                setTimeout(() => {
                    this.checkPermissionAgain();
                }, 1000);
            } else {
                console.info('permission is granted.');
                this.startHookKeyEvent();
            }
        })
        .catch((err: BusinessError) => {
            console.error(`Failed to openPermissionOnSetting, code: ${err.code}`);
        });
}

@Entry
@Component
struct Index {
    @State permissionGranted: boolean = false;
    
    aboutToAppear() {
        const context: common.UIAbilityContext = 
            this.getUIContext().getHostContext() as common.UIAbilityContext;
        
        // 先检查权限状态
        this.checkInitialPermission(context);
    }
    
    async checkInitialPermission(context: common.UIAbilityContext) {
        let granted = await this.checkPermission('ohos.permission.HOOK_KEY_EVENT');
        if (granted) {
            this.permissionGranted = true;
            this.startHookKeyEvent();
        } else {
            // 未授权,引导用户
            this.showPermissionDialog(context);
        }
    }
    
    showPermissionDialog(context: common.UIAbilityContext) {
        AlertDialog.show({
            title: '需要键盘输入权限',
            message: '应用需要拦截键盘输入事件来实现快捷操作,请在设置中开启权限',
            primaryButton: {
                value: '去设置',
                action: () => {
                    openPermOnSetting('ohos.permission.HOOK_KEY_EVENT', context);
                }
            },
            secondaryButton: {
                value: '暂不'
            }
        });
    }
    
    showManualGuide() {
        AlertDialog.show({
            message: '您可以在系统设置中手动开启权限:\n设置 → 隐私和安全 → 键盘输入辅助 → 本应用',
            primaryButton: {
                value: '我知道了'
            }
        });
    }
    
    async checkPermission(permission: string): Promise<boolean> {
        // 实现权限检查逻辑
        return false;
    }
    
    async checkPermissionAgain() {
        let granted = await this.checkPermission('ohos.permission.HOOK_KEY_EVENT');
        this.permissionGranted = granted;
        if (granted) {
            this.startHookKeyEvent();
        }
    }
    
    startHookKeyEvent() {
        console.info('开始拦截键盘输入事件');
        // 业务逻辑
    }
    
    build() {
        Column() {
            Text(this.permissionGranted ? '权限已开启' : '权限未开启')
                .fontSize(16)
                .margin(20)
            
            if (!this.permissionGranted) {
                Button('再次申请权限')
                    .onClick(() => {
                        const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
                        this.showPermissionDialog(context);
                    })
            }
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
    }
}

3.4 处理授权结果

调用openPermissionOnSetting()方法后,应用程序需重新校验是否已经授权

// 权限检查结果处理
if (data === abilityAccessCtrl.SelectedResult.REJECTED) {
    // 用户不允许跳转到"设置"
    // 可以提示用户手动去设置开启
    this.showManualGuide();
    
} else if (data === abilityAccessCtrl.SelectedResult.OPENED) {
    // 用户选择跳转到"设置"
    // 等待用户返回后重新检查权限
    setTimeout(async () => {
        let granted = await this.checkPermission('ohos.permission.HOOK_KEY_EVENT');
        if (granted) {
            // 用户已授权
            this.startHookKeyEvent();
        } else {
            // 用户仍未授权,继续引导
            this.showPermissionDialog(context);
        }
    }, 1000);
    
} else {
    // 权限已授权,无需弹窗
    this.startHookKeyEvent();
}

四、设置路径说明

用户在系统设置中开启权限的路径:

路径一:隐私和安全入口

设置 → 隐私和安全 → 权限类型(如键盘输入辅助) → 某个应用

路径二:应用信息入口

设置 → 应用和元服务 → 某个应用 → 权限类型(如键盘输入辅助)

五、注意事项

5.1 调用时机

场景 要求
在UIAbility的onWindowStageCreate中调用 需要等待loadContent/setUIContent执行结束后,或在回调中调用
原因 在Content加载完成前,openPermissionOnSetting会调用失败

5.2 返回值

openPermissionOnSetting返回的SelectedResult有三种可能:

返回值 说明
REJECTED 用户不允许跳转到"设置"
OPENED 用户选择跳转到"设置"
其他 权限已授权,无需弹窗
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐