跳转到主要内容
出于保护用户设备上文件和隐私的考虑,插件访问文件系统或发起网络请求前,需要先获得用户授权。这套权限校验发生在”插件进程访问文件系统/网络”这个边界上:无论插件代码是调用 sn-plugin-lib 提供的接口,还是直接使用 Android 官方文件/网络接口、RN 官方文件/网络接口,或插件自带的 C/C++ 代码做文件读写/网络访问,只要访问的是受限范围(见下方默认可访问范围),都会被同一套权限体系拦截,无法绕过。 本章介绍权限体系的整体设计,具体接口请参考:

权限类型

如何声明权限

调用 requestPermission 前,必须先在插件根目录的 PluginConfig.json 里,通过 uses-permissions 字段声明要使用的权限名(字符串数组)。未声明就直接调用会失败(错误码 1500)。 字段格式参见 PluginConfig.json 字段说明 中的 uses-permissions 一行。

如何申请权限

每次进入插件,或在使用需要该权限的功能之前,都建议按下面的流程走一遍(详见权限申请规范):
  1. 调用 hasPermission(permission) 查询当前状态:返回 1 表示已授权,可直接调用相关接口
  2. 若返回 0(未授权),调用 requestPermission(permission, desc?) 发起申请:该权限还没有被设为”不允许”时,会弹出第 3 步的授权对话框;已经被设为”不允许”时,只会弹出第 4 步的前往设置提示
  3. 用户在弹框中选择(默认选中”仅本次允许”):
    • 仅本次允许:返回 1
    • 始终允许:返回 2
    • 不允许:返回 0
    • 点击 Cancel(未做选择):本次没有任何授权,返回 -1,不会记成「不允许」。下次调用 requestPermission 会重新弹出三选一授权对话框
  4. 如果用户此前已经选择过”不允许”,再次调用 requestPermission 会弹出提示框,引导用户前往系统设置修改;该提示框关闭后仍返回 0,不会重新触发三选一的授权流程
第 3 步弹出的是三选一授权对话框。下图是读取权限(FILE:READ)的英文界面,三个选项依次对应「仅本次允许」「始终允许」「不允许」,默认选中第一项。标题下方的说明是读取权限的系统默认文案,不是 desc;desc 不会出现在这张对话框上。写入、删除、网络权限的标题和默认说明不同: 三选一授权对话框 第 4 步、权限已被设为「不允许」时,只会弹出前往设置的提示框。下图同样是读取权限。正文是未传入 desc 时的系统默认说明;传入 desc 时,这句说明会换成 desc: 前往设置提示框
“仅本次允许”只在本次插件会话内有效:插件退出或关闭后会失效,下次重新打开插件需要重新申请;只有”始终允许”会被持久化保存,重启插件后依然有效。建议在插件正常运行期间发起文件读写等调用。

默认可访问范围

  • 插件私有目录(/data/data/com.ratta.supernote.pluginhost/files/plugins/<pluginID>):唯一默认免申请的路径,读取、写入、删除都不需要申请权限
  • sdcard 下 Document/EXPORT/INBOX/MyStyle/Note/SCREENSHOT 这 6 个目录:默认不具备任何权限,读/写/删要分别申请 FILE:READ/FILE:WRITE/FILE:DELETE 后才能使用
  • 外接 SD 卡、OTG 等外部存储设备:访问同样受 FILE:READ/FILE:WRITE/FILE:DELETE 权限管控,插件侧调用的是同一套 hasPermission/requestPermission 接口,不需要额外区分
  • 除上述范围外的其他路径:无法通过申请权限获得访问能力

权限依赖一览

下表只列出 sn-plugin-lib 提供的典型接口作为例子;如果插件代码绕开 sn-plugin-lib,直接使用 Android/RN/C++ 官方接口访问上述目录之外的文件或发起网络请求,同样会被这套权限体系拦截,不是只有下表列出的接口才受影响。 deleteElements/deletePageElements 等删除元素类方法删除的是笔记内容,本质是对文件的修改,因此按 FILE:WRITE 校验;FILE:DELETE 权限用于文件级删除等其他场景。

常见错误码

权限申请规范

每次进入插件,以及每次使用需要权限的功能之前,都要按”声明 → 检查 → 申请”走一遍。前提是已按如何声明权限在 PluginConfig.json 中声明了该权限。
  • 每次进入插件或使用相关功能前,先调用 hasPermission。返回 1 表示已授予,直接调用相关接口,不需要再申请
  • 返回 0 再调用 requestPermission,具体流程和返回值见如何申请权限
下面示例演示”查询 → 未授权则申请 → 申请通过后重试业务调用”的最小流程:
  • 仅本次允许:失效时机见上文如何申请权限中的说明。下次进入插件,或再次使用该功能时,如果没有再次调用 requestPermission,就不会弹出授权框,读取、写入会失败(错误码 1503、1501,见常见错误码),网络请求也会失败
  • 始终允许:这个状态只能在设置里的插件管理中修改。已授予时 hasPermission 返回 1,不需要再调用 requestPermission,所以每次都检查不会重复弹框(本次进入内已经选了”仅本次允许”时同样如此)
  • 不允许:只会弹出前往设置修改的提示框,不会再出现三选一,见如何申请权限第 4 步
如果用户选的是”仅本次允许”,而插件只在第一次运行时申请了一次,之后再次进入插件或使用功能时,不会再弹出授权框,插件会因为没有权限而无法使用。