Android 系统开发人员的权限说明文档
文档地址:frameworks/base/core/java/android/permission/Permissions.md
大家在这个文档中看到实例:frameworks/base/core/res/AndroidManifest.xml
自己使用工具翻译,里面可能有错误,欢迎指正
正文
This document targets system developers. App developers should refer to the public documentation.
本文档面向系统开发者。应用开发者请参考公开文档。
Definitions (定义)
Each app (often called package) has a unique name called package name. Each package has a manifest file describing properties of the package. The android system server is in a special package named “android”.
每个应用程序(通常称为包)都有一个唯一的名称,称为包名。每个包都有一个清单文件,描述包的属性。Android system server 位于名为“android”的特殊包中。
注:这里的System server 就是 system_server(system) 进程
进程入口:frameworks/base/services/java/com/android/server/SystemServer.java
When a package gets installed the package is (usually) assigned a unique identifier called uid . This is the same as a uid in Linux, but this often leads to confusion. It is easiest to see a uid as a unique identifier for a package.
当安装软件包时,通常会为软件包分配一个称为 uid 的唯一标识符。这与 Linux 中的 uid 相同,但这经常导致混淆。最简单的方法是将 uid 视为软件包的唯一标识符。
注:这里的信息有误,在多用户环境下,分配的唯一标识是 appId 。
单用户时:uid = appId
多用户时:uid = userId * 100000 + appId
Usually an app is running in a container called a process. Each process has a unique id called pid, but unlike the uid the pid changes each time the process is restarted and app that are not currently running don’t have a pid. The process container makes sure that other apps cannot negatively interact with an app. Processes can only interact via controlled interactions called remote procedure calls (RPCs). Android’s RPC mechanism is called Binder.
通常,应用程序在称为进程的容器中运行。每个进程都有一个唯一的 ID,称为 pid,但与 uid 不同,每次重新启动进程时,pid 都会发生变化,并且当前未运行的应用程序没有 pid。进程容器确保其他应用无法对某个应用产生负面影响。进程只能通过称为远程过程调用 (RPC) 的受控交互进行通信。Android 的 RPC 机制称为 Binder。
注:单台设备上的 RPC 就是进程间通信。
As no app code can be trusted the permission need to be checked on the receiving side of the Binder call.
由于任何应用代码都不值得完全信任,因此需要在 Binder 调用的接收端检查权限。
For more details please take a look at Android’s security model.
有关更多详细信息,请查看 Android 的安全模型。
注:连接的源码地址:frameworks/base/core/java/android/os/Users.md
Permissions for regular apps (常规应用的权限)
2.1 Install time permissions
The purpose of install time permissions is to control access to APIs where it does not makes sense to involve the user. This can be either because the API is not sensitive, or because additional checks exist.
安装时权限的目的是控制对那些无需用户介入的API的访问。这可能是因为这些API本身并不敏感,或者是因为已经有其他验证措施存在。
Another benefit of install time permissions is that is becomes very easy to monitor which apps can access certain APIs. E.g. by checking which apps have the android.permission.INTERNET permission you can list all apps that are allowed to use APIs that can send data to the internet.
安装时权限的另一个好处是可以非常轻松地监控哪些应用可以访问某些 API。例如,通过检查哪些应用具有 android.permission.INTERNET 权限,你可以列出所有允许使用向互联网发送数据的API的应用。
2.1.1 Defining a permission
Any package can define a permission. For that it simply adds an entry in the manifest file
任何包都可以定义权限。只需在清单文件中添加一个条目即可
Any package can do this, including the system package. When talking about permissions for system apps we will see that it is important which package defines a permission.
任何包都可以这样做,包括系统包。在讨论系统应用的权限时,我们会看到由哪个包定义权限是非常重要的。
It is common good practice to prefix the permission name with the package name to avoid collisions.
通常,为了避免冲突,建议在权限名称前加上包名作为前缀。
2.1.2 Requesting a permission
Any app can request any permission via adding an entry in the manifest file like
任何应用都可以通过在清单文件中添加一个条目来请求任何权限,例如
。
A requested permission does not necessarily mean that the permission is granted. When and how a permission is granted depends on the protection level of the permission. If no protection level is set, it will default to normal and the permission will always be granted if requested. Such normal permissions can still be useful as it will be easy to find apps using a certain functionality on app stores and by checking dumpsys package.
请求一个权限并不意味着该权限一定会被授予。权限何时以及如何被授予取决于权限的保护级别。如果没有设置保护级别,则默认为普通(normal),并且如果请求了该权限,它将始终被授予。这种普通权限仍然有用,因为通过查找应用商店中的应用或检查 dumpsys package 输出,很容易发现使用了特定功能的应用。
2.1.3 Checking a permission
Context.checkPermission(permission, pid, uid) returns if the pid/uid has the permission. By far the most common case is to check the permission on the receiving end of a binder call. In this case the pid can be read as Binder.callingPid() and the uid as Binder.callingUid(). The uid is a mandatory argument as permissions are maintained per uid. The pid can be set to -1 if not pid is available. The context class contains handy wrappers for checkPermission, such as enforeCallingPermission which calls checkPermission with Binder.callingPid/Binder.callingUid and throws a SecurityException when the permission is not granted.
**Context.checkPermission(permission, pid, uid) **用于检查指定的 pid/uid 是否拥有某个权限。最常见的用例是在Binder调用的接收端检查权限。在这种情况下,可以通过 Binder.callingPid() 获取 pid,通过 Binder.callingUid() 获取 uid。uid 是必需的参数,因为权限是按 uid 维护的。如果没有可用的 pid,可以将其设置为 -1。Context 类包含了 checkPermission 的一些便捷包装方法,例如 enforceCallingPermission,该方法会使用 Binder.callingPid 和 Binder.callingUid 调用 checkPermission,并在权限未被授予时抛出
**SecurityException**
。
2.1.4 Verifying an app has an install time permission
In dumpsys package my.package.name there are two sections. In requested permissions all permissions of the uses-permission tags are listed. In install permission the permissions with their grant state are listed. If an install time permission is not listed here, it is not granted.
在
dumpsys package my.package.name
中有两个部分。在requested permissions
中,列出了所有uses-permission
标签的权限。在install permission
中,列出了权限及其授予状态。如果某个安装时的权限未列出,则表示该权限未被授予。
adb shell dumpsys package com.android.packageinstaller 部分数据示例:
Packages:
Package [com.android.packageinstaller] (2eb7062):
userId=10071
[...]
requested permissions:
android.permission.MANAGE_USERS
android.permission.INSTALL_PACKAGES
android.permission.DELETE_PACKAGES
android.permission.READ_INSTALL_SESSIONS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS
android.permission.USE_RESERVED_DISK
android.permission.UPDATE_APP_OPS_STATS
android.permission.MANAGE_APP_OPS_MODES
android.permission.INTERACT_ACROSS_USERS_FULL
android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME
android.permission.PACKAGE_USAGE_STATS
install permissions:
android.permission.USE_RESERVED_DISK: granted=true
android.permission.INSTALL_PACKAGES: granted=true
android.permission.RECEIVE_BOOT_COMPLETED: granted=true
android.permission.INTERACT_ACROSS_USERS_FULL: granted=true
android.permission.PACKAGE_USAGE_STATS: granted=true
android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME: granted=true
android.permission.READ_INSTALL_SESSIONS: granted=true
android.permission.MANAGE_USERS: granted=true
android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS: granted=true
android.permission.MANAGE_APP_OPS_MODES: granted=true
android.permission.UPDATE_APP_OPS_STATS: granted=true
android.permission.DELETE_PACKAGES: granted=true
2.1.5 End-to-end: Protecting an RPC call via a permission
Service Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.example.myservice">
<!-- Define a permission -->
<permission android:name="com.android.example.myservice.MY_PERMISSION" />
<application>
<service android:name=".MyService" android:exported="true" />
</application>
</manifest>
Service code
class MyService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return object : IMyService.Stub() {
override fun doSomething() {
// Verify that calling UID has the permission
enforceCallingPermission(
"com.android.example.myservice.MY_PERMISSION",
"Need to hold permission"
)
// do something
}
}.asBinder()
}
}
Caller Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.example.myapp">
<!-- request a permission -->
<uses-permission android:name="com.android.example.myservice.MY_PERMISSION" />
<application />
</manifest>
2.2 Runtime permissions
Runtime permission must be granted by the user during runtime. This is needed if the API protects data or functionality that is sensitive for the user. E.g. the users current location is protected by a runtime permission.
运行时权限必须在应用运行时由用户授予。如果某个API保护了对于用户是敏感的数据或功能,就需要这种权限。例如,用户的当前位置受到运行时权限的保护。
Users want a system that is secure and privacy focused by default. User can also often not make a good choice when asked at the wrong time without enough context. Hence in general runtime permissions should be avoided and the API should be built in a way where no private data needs to be leaked.
用户希望系统默认是安全且注重隐私的。然而,当用户在错误的时间被要求做出决定且没有足够的上下文时,往往无法做出明智的选择。因此,通常应避免使用运行时权限,API 应该以无需泄露私人数据的方式构建。
2.2.1 Defining a runtime permission
Runtime permissions are defined in the same way as install time permissions. To tag them as runtime permissions the protectionLevel
needs to be set to dangerous. Dangerous is a synonym for runtime permissions in the Android platform.
运行时权限的定义方式与安装时权限相同。要将它们标记为运行时权限,需要将
protectionLevel
设置为dangerous
。在 Android 平台中,dangerous
是运行时权限的同义词。
<uses-permission android:name="com.example.myapp.myfirstruntimepermission"
android:protectionLevel="dangerous" />
2.2.2 Requesting a runtime permission
Similar to install time permissions any app can request a runtime permission by adding the <uses-permission android:name="com.example.myapp.myfirstruntimepermission" />
to the manifest.
与安装时权限类似,任何应用都可以通过在清单文件中添加以下代码来请求运行时权限:
<uses-permission android:name="com.example.myapp.myfirstruntimepermission" />
这样,应用程序就声明了它需要这个运行时权限。
By default runtime permissions are not granted. The app needs to call Activity.requestPermissions
during runtime to ask the user for the permission. The user might then grant or deny and once the decision is made the activity is called by via Activity.onPermissionGranted
.
默认情况下,运行时权限不会被授予。应用程序需要在运行时调用 Activity.requestPermissions 方法来向用户请求权限。用户可以选择授予或拒绝权限,一旦用户做出决定,系统会通过 Activity.onPermissionGranted 回调通知应用程序权限请求的结果。
During development and testing a runtime permission can be granted via the pm
shell command or by using the UiAutomator.grantRuntimePermission
API call. Please note that this does not grant the app-op synchronously. Unless the app needs to test the actual permission grant flow it is recommended to grant the runtime permissions during install using adb install -g /my/package.apk
.
在开发和测试过程中,可以通过
pm
shell 命令或使用UiAutomator.grantRuntimePermission
API 调用来授予运行时权限。但请注意,这不会同步授予 app-op (#runtime-permissions-and-app_ops) 。除非应用程序需要测试实际的权限授予流程,否则建议在安装时使用以下命令来授予运行时权限:
adb install -g /my/package.apk
这样可以在安装时自动授予所有请求的运行时权限,避免在运行时向用户请求权限的流程。
2.2.3 Checking a runtime permission
For runtime permissions defined by 3rd party apps it is fine to check a runtime permission like an install time permission. For system defined permissions you need to check all runtime permissions by using the PermissionChecker utility. It is good practice to use the tool anywhere possible.
对于由第三方应用定义的运行时权限,可以像检查安装时权限一样进行检查。但对于系统定义的权限,必须使用 PermissionChecker 工具来检查所有运行时权限。推荐在任何可能的情况下都使用该工具,这是一个良好的开发实践。
The permission checker might return PERMISSION_DENIED_APP_OP which should lead to a silent failure. This can only happen for system defined runtime permissions.
PermissionChecker
可能返回PERMISSION_DENIED_APP_OP
,这应当导致静默失败(即不提示用户)。这种情况只会发生在系统定义的运行时权限中。
Runtime permissions and app-ops
See App-ops.
The PermissionChecker code fundamentally looks like this:
PermissionChecker
的代码基本上如下所示:
class PermissionChecker {
fun checkCallingPermission(context: Context, permission: String) {
if (isRuntimePermission(permission)) {
if (context.checkPermission(uid, permission) == DENIED) {
return PERMISSION_DENIED
}
val appOpOfPermission = AppOpsManager.permissionToOp(permission)
if (appOpOfPermission == null) {
// not platform defined
return PERMISSION_GRANTED
}
val appOpMode = appOpsManager.noteOp(appOpOfPermission)
if (appOpMode == AppOpsManager.MODE_ALLOWED) {
return PERMISSION_GRANTED
} else {
return PERMISSION_DENIED_APP_OP
}
} else {
return PERMISSION_DENIED
}
}
}
For each platform defined runtime permission there is a matching app-op. When calling AppOpsManager.noteOp this returns either MODE_ALLOWED or MODE_IGNORED.
对于每个平台定义的运行时权限,都会有一个匹配的 app-op。当调用
AppOpsManager.noteOp
时,它返回的结果是MODE_ALLOWED
或MODE_IGNORED
。
This value is then used to decide between PERMISSION_DENIED_APP_OP and PERMISSION_GRANTED.
该值随后用于决定返回
PERMISSION_DENIED_APP_OP
还是PERMISSION_GRANTED
。
The primary purpose of the special PERMISSION_DENIED_APP_OP state was to support apps targeting an SDK lower than 23. These apps do not understand the concept of denied runtime permissions. Hence they would crash when getting a SecurityException. To protect the users’ privacy while still not crashing the app the special PERMISSION_DENIED_APP_OP mandates that the API should somehow silently fail.
PERMISSION_DENIED_APP_OP
状态的主要目的是支持目标 SDK 低于 23 的应用程序。这些应用不理解被拒绝的运行时权限概念,因此如果遇到SecurityException
可能会崩溃。为了保护用户隐私,同时避免应用崩溃,特殊的PERMISSION_DENIED_APP_OP
状态要求 API 以某种方式静默失败。
A secondary use case of the AppOpsManager.noteOp calls is to track which apps perform what runtime protected actions.
AppOpsManager.noteOp
的另一个用途是跟踪哪些应用程序执行了受运行时权限保护的操作。通过这种方式,系统可以记录哪些应用尝试访问受保护的数据或功能。
2.2.4 Verifying an app has a runtime time permission
In dumpsys package my.package.name the runtime permissions are listed per uid. I.e. different users might have different runtime permission grants and shared uids share a grant-set. If a runtime permission is listed as requested but not in the runtime permission section it is in its initial state, i.e. not granted.
在
dumpsys package my.package.name
中,运行时权限是按uid
列出的。这意味着不同的用户可能会有不同的运行时权限授予,而共享uid
的应用则共享一组权限。如果某个运行时权限在“requested permissions”部分被列出但未在“runtime permission”部分出现,则该权限处于初始状态,即未被授予。
Packages:
Package [com.google.android.GoogleCamera] (ccb6af):
userId=10181
[...]
requested permissions:
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_NETWORK_STATE
[...]
User 0: [ ... ]
overlay paths:
runtime permissions:
android.permission.ACCESS_FINE_LOCATION: granted=false [ ... ]
android.permission.READ_EXTERNAL_STORAGE: granted=true [ ... ]
2.2.5 End-to-end: Protecting an RPC call via a runtime permission
Service Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.example.myservice">
<!-- Define a runtime permission -->
<permission android:name="com.android.example.myservice.MY_RUNTIME_PERMISSION"
android:protectionLevel="dangerous" />
<application>
<service android:name=".MyService" android:exported="true" />
</application>
</manifest>
Service code
class MyService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return object : IMyService.Stub() {
override fun doSomething(callingPackage: String?, callingFeatureId: String?) {
Objects.requireNonNull(callingPackageName)
// Verify that calling UID has the permission
when (run {
PermissionChecker.checkCallingPermission(
this@MyService,
"com.android.example.myservice.MY_RUNTIME_PERMISSION",
callingPackageName,
callingFeatureId,
"Did something"
)
}) {
PERMISSION_GRANTED -> /* do something */
PERMISSION_DENIED_APP_OP -> /* silent failure, do nothing */
else -> throw SecurityException(
"Cannot do something as caller is missing "
+ "com.android.example.myservice.MY_RUNTIME_PERMISSION"
)
}
}
}.asBinder()
}
}
Caller Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.example.myapp">
<!-- request a permission -->
<uses-permission android:name="com.android.example.myservice.MY_RUNTIME_PERMISSION" />
<application />
</manifest>
Caller code
class MyActivity : Activity {
fun callDoSomething() {
if (checkSelfPermission("com.android.example.myservice.MY_RUNTIME_PERMISSION") == PERMISSION_DENIED) {
// Interrupt operation and request permission
requestPermissions(arrayOf("com.android.example.myservice.MY_RUNTIME_PERMISSION"), 23)
} else {
myService.doSomething(this@MyActivity.opPackageName, this@MyActivity.featureId)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == 23 && grantResults[0] == PERMISSION_GRANTED) {
// Finish operation
callDoSomething()
}
}
}
2.2.6 Restricted permissions
Some runtime permissions are restricted. They are annotated in the platforms AndroidManifest.xml as hardRestricted or softRestricted.
某些运行时权限受到限制。它们在平台 AndroidManifest.xml 中被注释为 hardRestricted 或 softRestricted。
Restricted permissions behave uncommon when not whitelisted. When whitelisted the permissions behave normally. What uncommon means depends on the whether they are hard or soft restricted.
受限权限在未列入白名单时表现不常见。列入白名单后,权限表现正常。不常见的含义取决于它们是硬限制还是软限制。
They can either be whitelisted during upgrade P->Q by the system or need to be whitelisted by the installer via PackageInstaller.SessionParams.setWhitelistedRestrictedPermissions. If this method is not used all permissions will be whitelisted.
它们可以在升级 P->Q 期间由系统列入白名单,也可以通过 PackageInstaller.SessionParams.setWhitelistedRestrictedPermissions 由安装程序列入白名单。如果不使用此方法,所有权限都将列入白名单。
Afterwards the app that originally installed the app can change the whitelisting state via PackageManager.addWhitelistedRestrictedPermission and PackageManager.removeWhitelistedRestrictedPermission.
之后,最初安装该应用的应用可以通过 PackageManager.addWhitelistedRestrictedPermission 和 PackageManager.removeWhitelistedRestrictedPermission 更改白名单状态。
The system tracks the source of the whitelisting by having three different flags RESTRICTION_SYSTEM_EXEMPT, RESTRICTION_UPGRADE_EXEMPT, and RESTRICTION_INSTALLER_EXEMPT,The flags can be checked in dumpsys package my.package.name
系统通过三个不同的标志 RESTRICTION_SYSTEM_EXEMPT、RESTRICTION_UPGRADE_EXEMPT 和 RESTRICTION_INSTALLER_EXEMPT 来跟踪白名单的来源,可以在 dumpsys package my.package.name 中检查这些标志。
User 0:
[...]
runtime permissions:
android.permission.READ_EXTERNAL_STORAGE: granted=false, flags=[ RESTRICTION_UPGRADE_EXEMPT ]
android.permission.ACCESS_FINE_LOCATION: granted=true, flags=[ RESTRICTION_SYSTEM_EXEMPT|RESTRICTION_UPGRADE_EXEMPT ]
Hard restricted
Hard restricted permissions need to be whitelisted to be grant-able.
硬限制权限需要列入白名单才可授予。
Soft restricted
The behavior of non-whitelisted soft restricted permissions is not uniform. The behavior is defined in the SoftRestrictedPermissionPolicy.
非白名单软限制权限的行为并不统一。该行为在 SoftRestrictedPermissionPolicy 中定义。
2.2.7 System fixed permission (系统固定权限)
Some runtime permissions are required for normal operation of the device. In this case the system can grant the permission as SYSTEM_FIXED. In this case the permission can be seen in the permission management settings but cannot be revoked by the user.
一些运行时权限对于设备的正常运行是必需的。在这种情况下,系统可以将这些权限标记为 SYSTEM_FIXED。这意味着用户可以在权限管理设置中看到这些权限,但是用户无法撤销这些权限。
The flag can be checked in dumpsys package my.package.name
这个标志可以通过 dumpsys package my.package.name 命令来进行检查。
User 0:
[...]
runtime permissions:
android.permission.READ_EXTERNAL_STORAGE: granted=true, flags=[ SYSTEM_FIXED|GRANTED_BY_DEFAULT ]
2.2.8 Background access (后台访问)
Whether the app is currently visible to the user is reflected in the ActivityManager’s proc state. There is a lot of granularity to this, but runtime permissions are using the app-ops services’definition of foreground and background.
应用程序当前是否对用户可见反映在 ActivityManager 的进程状态(proc state)中。这一状态划分非常细致,但运行时权限使用的是 app-ops 服务对于前台和后台的定义。
Most runtime permissions are not affected by foreground/background-ness. Microphone and Camera are foreground-only while Location is usually foreground-only, but background access can be added by granting the ACCESS_BACKGROUND_LOCATION modifier runtime permission.
大多数运行时权限并不会受到应用处于前台或后台的影响。然而,麦克风和摄像头权限仅限于前台使用,而位置权限通常是前台使用的,但通过授予 ACCESS_BACKGROUND_LOCATION 运行时权限,可以增加后台访问位置的能力。
Microphone and Camera
Currently these only allow access while the app is in foreground. There is a manual whitelist for e.g. the voice interaction service.
目前,这些仅允许在应用处于前台时访问。存在一个手动白名单,白名单中存在语音交互服务
This is currently (Mar 2020) reworked and will behave like location soon.
目前(2020 年 3 月)已重新设计,很快就会像位置一样运行。
Location
As described above the app-op mode for granted permissions is MODE_ALLOWED to allow access or MODE_IGNORED to suppress access.
如上所述, 授予的权限对应的 app-op 模式为 MODE_ALLOWED(允许访问)或 MODE_IGNORED(禁止访问)。
The important case is the case where the permission is granted and the app-op is MODE_IGNORED. In the case of location this state causes the LocationManagerService to stop delivering locations to the app. This is not a breaking behavior as the same scenario happens if e.g. no satellites could be found.
特别重要的情况是,权限被授予但 app-op 模式为
MODE_IGNORED
时,在这种情况下,LocationManagerService 会停止向应用程序提供位置信息。此行为不会导致崩溃或异常,因为类似的情况也可能发生,例如未找到卫星时。
This behavior is used to implement the foregound/background behavior for location. If the app is in the foreground the app-op mode is MODE_ALLOWED and works normally. If the app goes into background the app-op mode changes to MODE_IGNORED. This means that locations are delivered while the app is in foreground and while the app is background, the app won’t get any locations.
这种机制用于实现位置权限的前台/后台行为:
- 当应用在前台时,app-op 模式为
MODE_ALLOWED
,位置服务正常工作。- 当应用进入后台时,app-op 模式切换为
MODE_IGNORED
,应用不会再接收到任何位置信息。
The automatic switching between MODE_ALLOWED and MODE_IGNORED is done inside of AppOpsManager.
MODE_ALLOWED 和 MODE_IGNORED 之间的自动切换是在 AppOpsManager 内部完成的。
Background access can be enabled by also granting the ACCESS_BACKGROUND_LOCATION to the app. In this case the app-op mode will always be MODE_ALLOWED.
如果还授予了
ACCESS_BACKGROUND_LOCATION
权限,应用的后台位置访问权限将被启用,此时 app-op 模式将始终为MODE_ALLOWED
,即使应用处于后台,也能接收位置信息。
2.2.9 UI Granting
An app following the best practices does not ask for any runtime permissions until absolutely needed. Once needed the request should be made in context. I.e. the user should understand from the current state of the app and the user’s action why the request is made. E.g. if the user presses a “show me the next ATM”-button the user is most likely expecting a request for the location permission.
遵循最佳实践的应用在绝对需要时才会请求运行时权限。请求应该在合理的上下文中进行,即用户应该从应用的当前状态和自己的操作中理解为什么需要该权限。例如,当用户按下“显示我附近的 ATM”按钮时,用户很可能会预期看到一个请求位置权限的提示。
This is central premise to the runtime permission UI. It is the app’s responsibility to avoid showing permission requests dialogs to the user which might get denied. These dialogs are not meant to be user-choices, they are meant to be user-confirmations.
这是运行时权限用户界面的核心前提。应用有责任避免显示可能会被拒绝的权限请求对话框。这些对话框的设计初衷不是作为用户的选择,而是作为用户的确认。
Hence any denied permission dialog is probably due to the app asking for permissions the user does not expect. If too many permission requests get denied the app is apparently trying to get more than the user wants to give to the app. In this case the permission gets permanently denied and all future requests will be denied automatically without showing a UI.
因此,任何被拒绝的权限对话框,可能都是因为应用请求了用户没有预期到的权限。如果权限请求被拒绝次数过多,说明应用试图获取超出用户愿意授予的权限。在这种情况下,权限将被永久拒绝,未来的请求将自动被拒绝且不会再显示 UI。
Context.requestPermission calls for more than one permission are allowed and might result in multiple dialogs in sequence. This might make sense for e.g. getting microphone and camera permission when starting a video call.
Context.requestPermission 允许调用多个权限,并可能导致连续出现多个对话框。这可能适用于例如在开始视频通话时获取麦克风和摄像头权限。
Each time the the user makes a choice (either to grant or the deny) a permission request the permission is marked as USER_SET. If a permission gets permanently denied the permission is marked as USER_FIXED.
每次用户选择(授予或拒绝)权限请求时,权限都会被标记为 USER_SET。如果权限被永久拒绝,权限将被标记为 USER_FIXED。
This can be found in dumpsys package my.package.name
这些信息可以在 dumpsys package my.package.name 命令的输出中找到。
User 0:
[...]
runtime permissions:
android.permission.READ_EXTERNAL_STORAGE: granted=false, flags=[ USER_SET|USER_FIXED ]
android.permission.ACCESS_FINE_LOCATION: granted=true, flags=[ USER_SET ]
Settings
By far most interactions with the permission system are via the permission grant flow. The main purpose of the permission settings is to show the user the previous choices and allow the user to revisit previous choices. In reality few users do that.
大多数与权限系统的交互都通过权限授予流程进行。权限设置的主要目的是向用户展示他们之前的选择,并允许用户重新审视和更改这些选择。然而,实际上很少有用户会主动回到设置中修改之前的权限决定。
Grouping
There are too many runtime permissions for the user to individually manage. Hence the UI bundles the permissions into groups. Apps should never assume the grouping. The grouping might change with SDK updates, but also at any other time. Certain form factors or locales might use other permission models and sometimes some of the permissions of a group cannot be granted for various reasons. The grouping is defined inside the permission controller app.
运行时权限数量众多,用户无法逐一管理,因此 UI 会将权限分组展示。应用程序不应假设权限分组方式,因为分组可能会随着 SDK 更新而改变,也可能在其他情况下发生变化。某些设备形态或区域设置可能会使用不同的权限模型,并且由于各种原因,有时组中的某些权限可能无法授予。
If two permissions belong to a group and the first permission is already granted the second one will be granted on request of the app without user interaction. For that reason a permission group with at least one individual permission granted will show up as granted in the UI.
权限分组是由权限控制器应用定义的。如果两个权限属于同一组,并且第一个权限已经被授予,那么应用程序请求第二个权限时将无需用户交互,自动授予。因此,如果某个权限组中的至少一个权限被授予,该组在 UI 中将显示为已授予。
Alternate permission management
It is not allowed to build alternate permission management UIs. While restricting innovation is not a good choice this is a required one to enforce a consistent, predictable, but flexible permission model for users and app developers.
不允许构建替代的权限管理 UI。虽然限制创新并非理想选择,但这是为了为用户和应用开发者强制执行一致、可预测且灵活的权限模型所必须的做法。
Further some data needed for permission management (e.g. the grouping) is not available outside the permission controller app.
此外,一些用于权限管理的数据(例如权限分组)仅在权限控制器应用中可用,外部应用无法访问。因此,
Hence all permission management UI needs to be integrated with AOSP.
所有的权限管理 UI 都必须集成在 AOSP(Android 开放源代码项目)中。
**注:**这种统一的权限管理方式确保了用户体验的一致性,并且避免了不同应用实现的权限管理可能导致的混乱或安全风险。
2.2.10 Pre granting
Runtime permissions protect user private data. It is a violation of user trust to give the data to an app without explicit user consent (i.e. the user granting the permission). Still the user expects certain functionality (e.g. receiving a phone call) to work out of the box.
运行时权限保护用户的私人数据。未经用户明确同意(即用户授予权限)就将数据提供给应用,是对用户信任的一种违背。然而,用户希望某些功能(例如接听电话)能够开箱即用。
Hence the DefaultPermissionGrantPolicy and roles allow to grant permission without the user . The default permission grant policy grants permissions for three categories of apps
因此,DefaultPermissionGrantPolicy 和角色机制允许在没有用户直接干预的情况下授予权限。默认权限授予策略针对三类应用授予权限:
● Apps running in well defined uids as they are considered as part of the platform
运行在明确定义的UID中的应用:这些应用被视为平台的一部分,例如系统应用或预装的核心组件。
● Apps that are in certain predefined categories, e.g. the browser and the SMS app. This is meant for the most basic phone functionality, not for all pre-installed apps.
属于某些预定义类别的应用,例如浏览器和短信应用。这主要是为了最基本的手机功能,而不是为了所有预装应用。
● Apps that are explicitly mentioned as pre-grant-exceptions. This is meant to be used for setup and other highly critical use cases, not to improve the user experience. The exceptions are listed in xml files in etc/ and follow the following syntax
明确标记为预授权例外的应用:这类应用通常用于设置过程或其他高度关键的用例,而不是为了提升用户体验。这些例外会列在
etc/
目录中的 XML 文件中,遵循特定的语法结构。
<exceptions>
<exception package="my.package.name">
<permission name="android.permission.ACCESS_FINE_LOCATION" fixed="false"/>
</exception>
</exceptions>
Pre-granted runtime permissions can still be revoked by the user in settings unless they are granted as SYSTEM_FIXED.
预授予的运行时权限仍然可以在设置中被用户撤销,除非它们被授予时标记为
SYSTEM_FIXED
,这意味着该权限对用户是固定的,无法通过常规设置界面进行修改。
Whether a permission was granted by the default can be checked in the permission flags of dumpsys package my.package.name
是否通过默认策略授予权限可以在
dumpsys package my.package.name
的权限标志(permission flags)中检查。通过查看这些标志,可以确定某个权限是由于默认策略授予的,还是用户手动同意的。
User 0:
[...]
runtime permissions:
android.permission.ACCESS_FINE_LOCATION: granted=true, flags=[ GRANTED_BY_DEFAULT ]
2.3 Permission restricted components
As publicly documented it is possible to restrict starting an activity/binding to a service by using permission.
It is a common pattern to
如公开文档所述,可以通过使用权限来限制启动活动或绑定到服务。常见的模式是:
● define a permission in the platform as signature
在平台中定义一个签名权限。
● protect a service in an app by this permission using the android:permission attribute of the tag
使用该权限保护应用中的服务,通过
<service>
标签中的android:permission
属性进行定义。
Then it is guaranteed that only the system can bind to such service. This is used for services that provide extensions to platform functionality, such as auto-fill services, print services, and accessibility services.
这样可以保证只有系统能够绑定到这些服务。这种模式常用于提供平台功能扩展的服务,例如自动填充服务、打印服务和辅助功能服务。
This does not work for app-op or runtime permissions as the way to check these permissions is more complex than install time permissions.
然而,这种方法仅适用于安装时权限,不适用于 app-op 或 运行时权限,因为这些权限的检查机制比安装时权限更加复杂。
2.3.1 End-to-end A service only the system can bind to
Make sure to set the android:permission flag for this service. As developers can forget this it is a good habit to check this before binding to the service. This makes sure that the services are implemented correctly and no random app can bind to the service.
为了确保只有系统能够绑定到某个服务,务必为该服务设置
android:permission
标志。开发者可能会忘记设置这一标志,因此在绑定服务之前检查该标志是个好习惯。这样可以确保服务被正确实现,避免其他任意应用绑定到该服务。
The result is that the permission is granted only to the system. It is not granted to the service’s package, but this has no negative side-effects.
结果是,该权限只授予系统,而不是服务所在的包。然而,这并不会产生任何负面影响,因为绑定服务的目标是确保只有系统级组件能够访问该服务。
Permission definition
frameworks/base/core/res/AndroidManifest.xml:
<manifest>
[...]
<permission android:name="android.permission.BIND_MY_SERVICE"
android:label="@string/permlab_bindMyService"
android:description="@string/permdesc_bindMyService"
android:protectionLevel="signature" />
[...]
</manifest>
Service definition
Manifest of the service providing the functionality:
<manifest>
<service android:name="com.my.ServiceImpl"
android:permission="android.permission.BIND_MY_SERVICE">
<!-- add an intent filter so that the system can find this package -->
<intent-filter>
<action android:name="android.my.Service" />
</intent-filter>
</service>
</manifest>
System server code binding to service
val serviceConnections = mutableListOf<ServiceConnection>()
val potentialServices = context.packageManager.queryIntentServicesAsUser(
Intent("android.my.Service"), GET_SERVICES or GET_META_DATA, userId)
for (val ri in potentialServices) {
val serviceComponent = ComponentName(ri.serviceInfo.packageName, ri.serviceInfo.name)
if (android.Manifest.permission.BIND_MY_SERVICE != ri.serviceInfo.permission) {
Slog.w(TAG, "$serviceComponent is not protected by " +
"${android.Manifest.permission.BIND_MY_SERVICE}")
continue
}
val newConnection = object : ServiceConnection {
...
}
val wasBound = context.bindServiceAsUser(Intent().setComponent(serviceComponent),
serviceConnection, BIND_AUTO_CREATE, UserHandle.of(userId))
if (wasBound) {
serviceConnections.add(newConnection)
}
}
Permissions for system apps
System apps need to integrate deeper with the system than regular apps. Hence they need to be able to call APIs not available to other apps. This is implemented by granting permissions to these system apps and then enforcing the permissions in the API similar to other install time permissions.
系统应用程序比普通应用需要更深地集成到系统中,因此它们需要调用其他应用无法访问的 API。这种需求通过授予系统应用特定权限来实现,并在 API 层面上通过类似于安装时权限的方式进行权限控制。
System apps are not different from regular apps, but the protection flags (e.g. privileged, preinstalled) mentioned in this section are more commonly used by system apps.
系统应用与普通应用并没有本质上的区别,但一些保护标志(例如
privileged
和preinstalled
)更常用于系统应用。这些标志允许系统应用访问更多的功能和资源:
3.1 Permission protection level
Every permission has a protection level (android:protectionlevel), which is a combination of one required protection (PermissionInfo.getProtection()) and multiple optional protection flags (PermissionInfo.getProtectionFlags()).
每个权限都有一个保护级别(
android:protectionLevel
),由一个必需的保护级别(PermissionInfo.getProtection()
)和多个可选的保护标志(PermissionInfo.getProtectionFlags()
)组成。
The protection can be one of the following:
权限保护级别可以是以下之一:
● normal: The permission will be granted to apps requesting it in their manifest.
normal:权限会自动授予请求它的应用,应用只需在
AndroidManifest.xml
中声明该权限。
● dangerous: The permission will be a runtime permission.
dangerous:权限属于运行时权限,用户在使用过程中需要明确授予该权限。
● signature: The permission will be granted to apps being signed with the same certificate as the app defining the permission. If the permission is a platform permission, it means those apps need to be platform-signed.
signature:权限只会授予那些使用与定义权限的应用相同证书签名的应用。如果这是一个平台权限,则意味着只有平台签名的应用可以获得该权限。
● internal: This is a no-op protection so that it won’t allow granting the permission by itself. However, it will be useful when defining permissions that should only be granted according to its protection flags, e.g. internal|role for a role-only permission.
internal:这是一种无操作保护,单独不会授予权限。但当权限带有其他保护标志时(例如
internal|role
),它用于定义只根据标志授予的权限,如仅角色拥有的权限。
There are various optional protection flags that can be added to protection level, in addition to the required protection, e.g. appop, preinstalled, privileged, installer, role and development.
除了必需的保护级别之外,还有一些可选的保护标志可以添加到保护级别中,例如
appop
、preinstalled
、privileged
、installer
、role
和development
。
The permission will be granted to an app if it meets any of the protection or protection flags (an OR relationship). For example, signature|privileged allows the permission to be granted to platform-signed apps as well as privileged apps.
如果应用满足任何保护级别或保护标志(逻辑 “OR” 关系),它就会被授予相应权限。例如,
signature|privileged
允许权限授予给平台签名的应用以及具有特权的应用。这种灵活性保证了特定权限的适当控制和使用。
3.2 App-op permissions
See App-ops [frameworks/base/core/java/android/app/AppOps.md].
App-op permissions are user-switchable permissions that are not runtime permissions. This should be used for permissions that are really only meant to be ever granted to a very small amount of apps. Traditionally granting these permissions is intentionally very heavy weight so that the user really needs to understand the use case. For example one use case is the INTERACT_ACROSS_PROFILES permission that allows apps of different users within the same profile group to interact. Of course this is breaking a very basic security container and hence should only ever be granted with a lot of care.
App-op 权限是用户可切换的权限,但它们不是运行时权限。这类权限主要用于只应授予少数应用的场景。传统上,授予这些权限的过程是刻意设计得非常复杂,以确保用户能够完全理解其用例。例如,INTERACT_ACROSS_PROFILES 权限允许同一用户组内的不同用户之间的应用进行交互,这打破了基本的安全隔离,因此需要非常谨慎地授予。
Warning: Most app-op permissions follow this logic, but most of them also have exceptions and special behavior. Hence this section is a guideline, not a rule.
警告:大多数 app-op 权限都遵循此逻辑,但其中大多数也有例外和特殊行为。因此,本节是指南,而不是规则。
3.2.1 Defining an app-op permission
Only the platform can reasonably define an app-op permission. The permission is defined in the platforms manifest using the appop protection flag:
只有平台才能合理地定义 app-op 权限。该权限在平台清单中使用 appop 保护标志进行定义:
<manifest package="android">
<permission android:name="android.permission.MY_APPOP_PERMISSION"
android:protectionLevel="signature|appop" />
</manifest>
3.2.2 Checking an app-op permission
The PermissionChecker utility can check app-op permissions with the same syntax as runtime permissions.
The permission checker internally follows this flow
PermissionChecker 工具可以使用与运行时权限相同的语法来检查 App-op 权限。
PermissionChecker 工具内部遵循以下流程:
class PermissionChecker {
fun checkCallingPermission(context: Context, permission: String) {
if (isAppOpPermission(permission)) {
val appopOfPermission = AppOpsManager.permissionToOp(permission)
if (appopOfPermission == null) {
// not platform defined
return PERMISSION_DENIED
}
val appopMode = appOpsManager.noteOp(appopOfPermission)
when (appopMode) {
AppOpsManager.MODE_ALLOWED -> return PERMISSION_GRANTED
AppOpsManager.MODE_IGNORED -> return PERMISSION_DENIED
AppOpsManager.MODE_DEFAULT -> {
if (context.checkPermission(uid, permission) == GRANTED) {
return PERMISSION_GRANTED
} else {
return PERMISSION_DENIED
}
}
}
} else {
return PERMISSION_DENIED
}
}
}
3.2.3 Granting an app-op permission
The permission’s grant state is only considered if the app-op’s mode is MODE_DEFAULT. This allows to have default grants while still being overridden by the app-op.
权限的授予状态只有在 App-op 模式为
MODE_DEFAULT
时才会被考虑。这允许默认授予权限,但仍然可以通过 App-op 覆盖。
The permission is then granted by setting the app-op mode. This is usually done via dedicated APIs for each use case. Similarly whether and how an app can request the permission is different for each app-op permission.
权限通过设置 App-op 模式来授予。通常,每个用例都有专门的 API 用于设置相应的模式。同样,应用程序是否可以请求该权限以及如何请求取决于每个 App-op 权限的具体实现。
When implementing a new app-op permission, make sure to set the app-op mode using AppOpsManager.setUidMode to make sure the permission is granted on the uid as this is the security domain.
在实现新的 App-op 权限时,请确保使用
AppOpsManager.setUidMode
设置 App-op 模式,以确保权限在 UID(即安全域)上授予。
During development app-ops can be grated to app via the appops set shell command. E.g.
在开发过程中,可以通过
appops set
shell 命令将 App-op 授予应用程序。例如:
adb shell appops set 10187 INTERACT_ACROSS_PROFILES allow
sets the INTERACT_ACROSS_PROFILES app-op for uid 10187 to allow thereby granting apps in this uid the ability to interact across profiles.
这将把 UID 10187 的
INTERACT_ACROSS_PROFILES
App-op 设置为allow
,从而授予该 UID 中的应用跨配置文件交互的权限。
UI
Most UIs for app-op permissions are in the “Special app access” section of the settings app.
大多数 app-op 权限的用户界面位于设置应用的“特殊应用访问”部分。
In most cases the permission should only be granted with the user’s explicit agreement, usually by allowing the app to directly open the “Special app access” page for this permission and app.
在大多数情况下,只有在用户明确同意的情况下才应授予权限,通常是允许应用直接打开此权限和应用的“特殊应用访问”页面。
To repeat: this is a guideline for app-op permissions and there are many exceptions.
重复一遍:这是应用操作权限的指导方针,但是有很多例外情况。
3.3 Signature permissions
Only apps signed with the defining app’s certificate will be granted the permission. This is used to restrict APIs to apps of the same developer.
只有使用定义该权限的应用程序证书签名的应用程序才会被授予该权限。这种方法用于将 API 限制为同一开发者的应用程序使用。
This is frequently used to restrict permissions defined by the platform to apps also signed with the platform’s certificate. As this is a very tight restriction this is recommended for permissions that are only used by apps built out of AOSP which are signed with the platform certificate.
平台经常使用签名权限来限制某些权限,仅授予那些也由平台证书签名的应用。由于这是非常严格的限制,建议将其用于仅供 AOSP(Android 开源项目)构建并由平台证书签名的应用所使用的权限。
Please note that OEMs sign their platform themselves. I.e. OEMs can implement new apps using these permissions. It is unlikely that 3rd party apps will be able to use APIs protected by signature permissions as they are usually not signed with the platform certificate.
请注意,OEM 厂商会自行为其平台进行签名。也就是说,OEM 可以使用这些权限来实现新的应用程序。而第三方应用由于通常不会使用平台证书进行签名,无法使用受签名权限保护的 API。
If possible, role protected permissions should also be considered as an alternative to better restrict which apps may get the permission.
如果可能,建议考虑使用角色保护权限作为替代方案,以更好地限制哪些应用可以获得权限。
Such permissions are defined and checked like an install time permission.
这种权限的定义和检查方式类似于安装时权限。
3.4 Preinstalled permissions ( 预装权限 )
This means that the app has to be pre-installed. There is no restriction what apps are pre-installed on a particular device install there. Hence it can be really any app including 3rd party apps.
预装权限意味着应用必须是预装的应用程序。设备上预装的应用没有特定限制,因此这类权限可以授予任何应用,包括第三方应用。
Hence this permission level is discouraged unless there are further restrictions.
因此,除非有进一步的限制,否则不推荐使用这种权限级别。
If possible, role protected permissions should also be considered as an alternative to better restrict which apps may get the permission.
如果可能,建议考虑使用角色保护权限作为替代方案,以更好地限制哪些应用可以获得权限。
Such permissions are defined and checked like an install time permission.
这种权限的定义和检查方式类似于安装时权限。
3.5 Privileged permissions ( 特权权限 )
This means that the app has to be pre-installed and in the system/priv directory in the filesystem. There is no restriction what apps are in this directory on a particular device. Hence it can be really any app including 3rd party apps.
特权权限意味着应用必须预装,并且位于文件系统的
system/priv
目录中。该目录中的应用没有特定限制,因此理论上可以是任何应用,包括第三方应用。注:是 priv-app
An app is only ever granted privileged permissions requested by the pre-installed apk. I.e. privileged permissions added in updates will never be granted.
大致意思是,只有预装的 apk 中声明的特权权限可以被赋予,如果更新的apk中添加特权权限,权限将不被赋予。
Hence this permission level is discouraged unless there are further restrictions.
因此,除非有进一步的限制,否则不推荐使用这种权限级别。
If possible, role protected permissions should also be considered as an alternative to better restrict which apps may get the permission.
如果可能,建议考虑使用角色保护权限作为替代方案,以更好地限制哪些应用可以获得权限。
Such permissions are defined and checked like an install time permission.
这种权限的定义和检查方式类似于安装时权限。
3.5.1 Restricted by tests
As all apps that might get preinstalled or privilidged permissions need to be pre-installed and new images need to pass compliance tests it is possible to use a test to whitelist the apps that can request the permission.
由于所有可能获得预装或特权权限的应用都需要预装,并且新系统镜像需要通过合规性测试,因此可以通过测试来将能够请求权限的应用列入白名单。
Example of such a test:
此类测试的示例:
/* Add new whitelisted packages to this list */
private val whitelistedPkgs = listOf("my.whitelisted.package")
@Test
fun onlySomeAppsAreAllowedToHavePermissionGranted() {
assertThat(whitelistedPkgs).containsAtLeastElementsIn(
context.packageManager.getInstalledPackages(MATCH_ALL)
.filter { pkg ->
context.checkPermission(android.Manifest.permission.MY_PRIVILEGED_PERMISSION, -1,
pkg.applicationInfo.uid) == PERMISSION_GRANTED
/* The permission is defined by the system and hence granted to it */
}.filter { pkg -> pkg.applicationInfo.uid != SYSTEM_UID }
.map { it.packageName }
)
}
3.5.2 Whitelist
As mentioned above it is not suggested, but still common practice to install 3rd party apps as privileged. To verify and restrict which privileged permissions those apps get granted all privileged permissions need to be explicitly whitelisted in a file /etc.
如上所述,虽然不建议,但仍然常见的做法是将第三方应用安装为特权应用。为了验证和限制这些应用获得的特权权限,所有特权权限都需要在
/etc
文件中明确列入白名单。
<permissions>
<privapp-permissions package="my.privileged.package">
<!-- allow the app to request a permission -->
<permission name="android.permission.MY_PRIVILEGED_PERMISSION"/>
<!-- Even though the app requests the permission, do not grant it -->
<deny-permission name="android.permission.MY_OTHER_PRIVILEGED_PERMISSION"/>
</privapp-permissions>
</permissions>
If the pre-installed apk of an app requests a privileged permission that is not mentioned in any whitelist or that is not denied the system will refuse to boot. As mentioned above privileged permissions added in updates to the pre-installed app will never be granted.
如果应用的预装 APK 请求了未在任何白名单中提到或未明确拒绝的特权权限,系统将拒绝启动。如前所述,预装应用更新中添加的特权权限永远不会被授予。
3.6 Limited permissions
E.g. installer, wellbeing, documenter, etc… This allows the system to restrict the permission to a well defined app or set of apps. It is possible to add new types in <font style="color:rgb(32, 33, 36);">PackageManagerService</font>
.
例如,安装程序、健康管理、文档管理等权限允许系统将权限限制为特定的应用或应用集。可以在
PackageManagerService
中添加新的权限类型。
Which apps qualify for such a permission level is flexible and custom for each such level. Usually they refer to a single or small set of apps, usually - but not always - apps defined in AOSP.
对于此类权限级别,符合条件的应用程序是灵活且定制化的,通常指单个或少数几个应用,通常——但不总是——是 AOSP 中定义的应用。
This type of permission is deprecated in favor of role protected permissions.
这种类型的权限已经被弃用,建议使用角色保护权限替代。
These permissions are defined and checked like an install time permission.
这些权限的定义和检查方式类似于安装时权限。
3.7 Role protected permissions
See Using role for permission protection.
请参阅《使用角色进行权限保护》。
地址:packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md
3.8 Development permissions
Not recommended
不推荐
By adding the development protection flag to any permissions the permission can be granted via the pm grant shell command. This appears to be useful for development and testing, but it is very highly discouraged. Any user can grant them permanently via adb, hence adding this tag removes all guarantees the permission might otherwise provide.
通过为任何权限添加开发保护标志,可以通过
pm grant
shell 命令授予权限。尽管这在开发和测试中似乎很有用,但强烈不推荐。任何用户都可以通过 adb 永久授予这些权限,因此添加此标志会取消权限可能提供的所有其他保证。
3.9 Other protection flags
There are other levels (such as runtime ) but they are for special purposes on should not be used by platform developers.
还有其他级别(如运行时权限),但它们用于特殊用途,不应由平台开发者使用。