重定向到安全中心
任何应用都可以使用 android.content.Intent.ACTION_SAFETY_CENTER
操作(字符串值 android.intent.action.SAFETY_CENTER
)打开安全中心。
要打开安全中心,请从 Activity
实例中调用
Intent openSafetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER);
startActivity(openSafetyCenterIntent);
重定向到特定问题
还可以使用特定的 intent extra 重定向到特定的安全中心警告卡片。这些 extra 并非供第三方使用,因此它们是 SafetyCenterManager
的一部分,而 SafetyCenterManager
是 @SystemApi
的一部分。只有系统应用才能访问这些 extra。
用于重定向特定警告卡片的 Intent extra
EXTRA_SAFETY_SOURCE_ID
- 字符串值:
android.safetycenter.extra.SAFETY_SOURCE_ID
- 字符串类型:指定关联警告卡片的安全性来源的 ID
- 必需,重定向到问题才能生效
- 字符串值:
EXTRA_SAFETY_SOURCE_ISSUE_ID
- 字符串值:
android.safetycenter.extra.SAFETY_SOURCE_ISSUE_ID
- 字符串类型:指定警告卡片 ID
- 必需,重定向到问题才能生效
- 字符串值:
EXTRA_SAFETY_SOURCE_USER_HANDLE
- 字符串值:
android.safetycenter.extra.SAFETY_SOURCE_USER_HANDLE
UserHandle
类型:指定关联警告卡片的UserHandle
- 可选(默认为当前用户)
- 字符串值:
以下代码段可从 Activity
实例中使用,以打开安全中心屏幕并转到特定问题
UserHandle theUserHandleThisIssueCameFrom = …;
Intent openSafetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER)
.putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID, "TheSafetySourceIdThisIssueCameFrom")
.putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID, "TheSafetySourceIssueIdToRedirectTo")
.putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCE_USER_HANDLE, theUserHandleThisIssueCameFrom);
startActivity(openSafetyCenterIntent);
重定向到特定子页面(Android 14 及更高版本)
在 Android 14 或更高版本中,安全中心页面分为多个子页面,这些子页面代表不同的 SafetySourcesGroup
(在 Android 13 中,这显示为可折叠条目)。
可以使用以下 intent extra 重定向到特定子页面
EXTRA_SAFETY_SOURCES_GROUP_ID
- 字符串值:
android.safetycenter.extra.SAFETY_SOURCES_GROUP_ID
- 字符串类型:指定
SafetySourcesGroup
的 ID - 必需,重定向到子页面才能生效
- 字符串值:
以下代码段可从 Activity
实例中使用,以打开安全中心屏幕并转到特定子页面
Intent openSafetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER)
.putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID, "TheSafetySourcesGroupId");
startActivity(openSafetyCenterIntent);
使用安全中心来源 API
安全中心来源 API 可通过 SafetyCenterManager
(它是 @SystemApi
)获得。API 表面的代码可在代码搜索中找到。API 的实现代码可在代码搜索中找到。
权限
只有使用以下列出的权限的允许列表中的系统应用才能访问安全中心来源 API。如需了解详情,请参阅特许权限允许列表。
READ_SAFETY_CENTER_STATUS
signature|privileged
- 用于
SafetyCenterManager#isSafetyCenterEnabled()
API(安全中心来源不需要此权限,它们只需要SEND_SAFETY_CENTER_UPDATE
权限) - 由检查安全中心是否已启用的系统应用使用
- 仅授予允许列表中的系统应用
SEND_SAFETY_CENTER_UPDATE
internal|privileged
- 用于启用的 API 和安全来源 API
- 仅由安全来源使用
- 仅授予允许列表中的系统应用
这些权限是特许权限,您只能通过将它们添加到相关文件(例如,设置应用的 com.android.settings.xml
文件)和应用的 AndroidManifest.xml
文件中来获取它们。如需详细了解权限模型,请参阅 protectionLevel
。
获取 SafetyCenterManager
SafetyCenterManager
是一个 @SystemApi
类,从 Android 13 开始,系统应用可以访问它。此调用演示了如何获取 SafetyCenterManager
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// Must be on T or above to interact with Safety Center.
return;
}
SafetyCenterManager safetyCenterManager = context.getSystemService(SafetyCenterManager.class);
if (safetyCenterManager == null) {
// Should not be null on T.
return;
}
检查安全中心是否已启用
此调用检查安全中心是否已启用。此调用需要 READ_SAFETY_CENTER_STATUS
权限或 SEND_SAFETY_CENTER_UPDATE
权限
boolean isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
if (isSafetyCenterEnabled) {
// …
} else {
// …
}
提供数据
具有给定 String sourceId
的安全中心来源数据通过 SafetySourceData
对象提供给安全中心,该对象表示一个界面条目和一个问题(警告卡片)列表。界面条目和警告卡片可以具有在 SafetySourceData
类中指定的不同严重级别
SEVERITY_LEVEL_UNSPECIFIED
- 未指定严重级别
- 颜色:灰色或透明(取决于条目的
SafetySourcesGroup
) - 用于在界面中充当静态条目的动态数据或显示未指定的条目
- 不得用于警告卡片
SEVERITY_LEVEL_INFORMATION
- 基本信息或次要建议
- 颜色:绿色
SEVERITY_LEVEL_RECOMMENDATION
- 建议用户对此问题采取措施,因为它可能会让他们面临风险
- 颜色:黄色
SEVERITY_LEVEL_CRITICAL_WARNING
- 严重警告,用户必须对此问题采取措施,因为它会带来风险
- 颜色:红色
SafetySourceData
SafetySourceData
对象由界面条目、警告卡片和不变量组成。
- 可选
SafetySourceStatus
实例(界面条目) SafetySourceIssue
实例(警告卡片)列表- 可选
Bundle
extra(从版本 14 开始) - 不变量
SafetySourceIssue
列表必须由具有唯一标识符的问题组成。- 如果存在
SafetySourceStatus
实例,则SafetySourceIssue
实例的重要性不得高于SafetySourceStatus
(除非SafetySourceStatus
为SEVERITY_LEVEL_UNSPECIFIED
,在这种情况下,允许使用SEVERITY_LEVEL_INFORMATION
问题)。 - 必须满足 API 配置施加的其他要求,例如,如果来源仅限问题,则不得提供
SafetySourceStatus
实例。
SafetySourceStatus
- 必需的
CharSequence
标题 - 必需的
CharSequence
摘要 - 必需的严重级别
- 可选的
PendingIntent
实例,用于将用户重定向到正确的页面(默认情况下使用配置中的intentAction
,如果有) - 可选的
IconAction
(在条目上显示为侧边图标),由以下项组成:- 必需的图标类型,必须是以下类型之一
ICON_TYPE_GEAR
:在界面条目旁边显示为齿轮ICON_TYPE_INFO
:在界面条目旁边显示为信息图标
- 必需的
PendingIntent
,用于将用户重定向到另一个页面
- 必需的图标类型,必须是以下类型之一
- 可选的布尔值
enabled
,用于将界面条目标记为已停用,使其不可点击(默认为true
) - 不变量
PendingIntent
实例必须打开Activity
实例。- 如果条目已停用,则必须指定
SEVERITY_LEVEL_UNSPECIFIED
。 - API 配置施加的其他要求。
SafetySourceIssue
- 必需的唯一
String
标识符 - 必需的
CharSequence
标题 - 可选的
CharSequence
副标题 - 必需的
CharSequence
摘要 - 必需的严重级别
- 可选的问题类别,必须是以下类别之一
ISSUE_CATEGORY_DEVICE
:问题影响用户的设备。ISSUE_CATEGORY_ACCOUNT
:问题影响用户的帐号。ISSUE_CATEGORY_GENERAL
:问题影响用户的总体安全。这是默认设置。ISSUE_CATEGORY_DATA
(从 Android 14 开始):问题影响用户的数据。ISSUE_CATEGORY_PASSWORDS
(从 Android 14 开始):问题影响用户的密码。ISSUE_CATEGORY_PERSONAL_SAFETY
(从 Android 14 开始):问题影响用户的个人安全。
- 用户可以针对此问题采取的
Action
元素列表,每个Action
实例都由以下项组成:- 必需的唯一
String
标识符 - 必需的
CharSequence
标签 - 必需的
PendingIntent
,用于将用户重定向到另一个页面或直接从安全中心屏幕处理操作 - 可选的布尔值,用于指定此问题是否可以直接从安全中心屏幕解决(默认为
false
) - 可选的
CharSequence
成功消息,当问题直接从安全中心屏幕成功解决时,将向用户显示此消息
- 必需的唯一
- 可选的
PendingIntent
,在用户关闭问题时调用(默认情况下不调用任何内容) - 必需的
String
问题类型标识符;这类似于问题标识符,但不必是唯一的,并且用于日志记录 - 可选的
String
重复数据删除 ID,这允许从不同来源发布相同的SafetySourceIssue
,并且只在界面中显示一次(假设它们具有相同的deduplicationGroup
)(从 Android 14 开始)。如果未指定,则问题永远不会重复数据删除 - 可选的
CharSequence
归因标题,这是显示警告卡片来源的文本(从 Android 14 开始)。如果未指定,则使用SafetySourcesGroup
的标题 - 可选的问题可操作性(从 Android 14 开始),必须是以下可操作性之一
ISSUE_ACTIONABILITY_MANUAL
:用户需要手动解决此问题。这是默认设置。ISSUE_ACTIONABILITY_TIP
:此问题只是一个提示,可能不需要任何用户输入。ISSUE_ACTIONABILITY_AUTOMATIC
:此问题已得到处理,可能不需要任何用户输入。
- 可选的通知行为(从 Android 14 开始),必须是以下通知行为之一
NOTIFICATION_BEHAVIOR_UNSPECIFIED
:安全中心将决定警告卡片是否需要通知。这是默认设置。NOTIFICATION_BEHAVIOR_NEVER
:不发布任何通知。NOTIFICATION_BEHAVIOR_DELAYED
:在首次报告问题后的一段时间发布通知。NOTIFICATION_BEHAVIOR_IMMEDIATELY
:在报告问题后立即发布通知。
- 可选的
Notification
,用于显示带有警告卡片的自定义通知(从 Android 14 开始)。如果未指定,则Notification
派生自警告卡片。由以下项组成:- 必需的
CharSequence
标题 - 必需的
CharSequence
摘要 - 用户可以针对此通知采取的
Action
元素列表
- 必需的
- 不变量
Action
实例列表必须由具有唯一标识符的操作组成Action
实例列表必须包含一个或两个Action
元素。如果可操作性不是ISSUE_ACTIONABILITY_MANUAL
,则允许使用零个Action
。- OnDismiss
PendingIntent
不得打开Activity
实例 - API 配置施加的其他要求
数据会在特定事件发生时提供给安全中心,因此有必要指定是什么导致来源提供带有 SafetyEvent
实例的 SafetySourceData
。
SafetyEvent
- 必需的类型,必须是以下类型之一
SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED
:来源的状态已更改。SAFETY_EVENT_TYPE_REFRESH_REQUESTED
:响应来自安全中心的刷新/重新扫描信号;对于安全中心能够跟踪刷新/重新扫描请求,请使用此类型而不是SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED
。SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED
:我们直接从安全中心屏幕解决了SafetySourceIssue.Action
;对于安全中心能够跟踪SafetySourceIssue.Action
是否已解决,请使用此类型而不是SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED
。SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED
:我们尝试直接从安全中心屏幕解决SafetySourceIssue.Action
,但未能成功;对于安全中心能够跟踪SafetySourceIssue.Action
是否解决失败,请使用此类型而不是SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED
。SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED
:设备的语言已更改,因此我们正在更新所提供数据的文本;允许为此使用SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED
。SAFETY_EVENT_TYPE_DEVICE_REBOOTED
:我们将此数据作为初始启动的一部分提供,因为安全中心数据不会跨重启持久存在;允许为此使用SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED
。
- 可选的刷新广播 ID 的
String
标识符。 - 可选的正在解决的
SafetySourceIssue
实例的String
标识符。 - 可选的正在解决的
SafetySourceIssue.Action
实例的String
标识符。 - 不变量
- 如果类型为
SAFETY_EVENT_TYPE_REFRESH_REQUESTED
,则必须提供刷新广播 ID - 如果类型为
SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED
或SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED
,则必须提供问题和操作 ID
- 如果类型为
以下示例说明了来源可能如何向安全中心提供数据(在本例中,它提供了一个带有一张警告卡片的条目)
PendingIntent redirectToMyScreen =
PendingIntent.getActivity(
context, requestCode, redirectToMyScreenIntent, PendingIntent.FLAG_IMMUTABLE);
SafetySourceData safetySourceData =
new SafetySourceData.Builder()
.setStatus(
new SafetySourceStatus.Builder(
"title", "summary", SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION)
.setPendingIntent(redirectToMyScreen)
.build())
.addIssue(
new SafetySourceIssue.Builder(
"MyIssueId",
"title",
"summary",
SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION,
"MyIssueTypeId")
.setSubtitle("subtitle")
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
.addAction(
new SafetySourceIssue.Action.Builder(
"MyIssueActionId", "label", redirectToMyScreen)
.build())
.build())
.build();
SafetyEvent safetyEvent = new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build();
safetyCenterManager.setSafetySourceData("MySourceId", safetySourceData, safetyEvent);
获取上次提供的数据
您可以获取上次为您的应用拥有的来源提供给安全中心的数据。您可以使用此数据在您自己的界面中显示某些内容,以检查数据是否需要在执行开销大的操作之前更新,或者提供相同的 SafetySourceData
实例给安全中心,其中包含一些更改或新的 SafetyEvent
实例。它也适用于测试。
使用以下代码获取上次提供给安全中心的数据
SafetySourceData lastDataProvided = safetyCenterManager.getSafetySourceData("MySourceId");
报告错误
如果您无法收集 SafetySourceData
数据,您可以向安全中心报告错误,这将条目更改为灰色,清除缓存的数据,并提供类似于无法检查设置的消息。如果 SafetySourceIssue.Action
实例无法解决,您也可以报告错误,在这种情况下,缓存的数据不会被清除,并且界面条目也不会更改;但会向用户显示一条消息,告知他们发生了错误。
您可以使用 SafetySourceErrorDetails
提供错误,SafetySourceErrorDetails
由以下项组成:
SafetySourceErrorDetails
:必需的SafetyEvent
实例
// An error has occurred in the background, need to clear the Safety Center data to avoid showing data that may not be valid anymore
SafetyEvent safetyEvent = new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build();
SafetySourceErrorDetails safetySourceErrorDetails = new SafetySourceErrorDetails(safetyEvent);
safetyCenterManager.reportSafetySourceError("MySourceId", safetySourceErrorDetails);
响应刷新或重新扫描请求
您可以从安全中心获取信号以提供新数据。响应刷新或重新扫描请求可确保用户在打开安全中心以及点按扫描按钮时查看当前状态。
这通过接收具有以下操作的广播来完成
ACTION_REFRESH_SAFETY_SOURCES
- 字符串值:
android.safetycenter.action.REFRESH_SAFETY_SOURCES
- 当安全中心发送请求以刷新给定应用的安全来源的数据时触发
- 只能由系统发送的受保护 intent
- 作为显式 intent 发送到配置文件中的所有安全来源,并且需要
SEND_SAFETY_CENTER_UPDATE
权限
- 字符串值:
以下 extra 作为此广播的一部分提供
EXTRA_REFRESH_SAFETY_SOURCE_IDS
- 字符串值:
android.safetycenter.extra.REFRESH_SAFETY_SOURCE_IDS
- 字符串数组类型 (
String[]
),表示要为给定应用刷新的来源 ID
- 字符串值:
EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE
- 字符串值:
android.safetycenter.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE
- 整数类型,表示请求类型
@IntDef
- 必须是以下类型之一
EXTRA_REFRESH_REQUEST_TYPE_GET_DATA
:请求来源相对快速地提供数据,通常在用户打开页面时EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA
:请求来源尽可能提供最新数据,通常在用户按下重新扫描按钮时
- 字符串值:
EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID
- 字符串值:
android.safetycenter.extra.REFRESH_SAFETY_SOURCES_BROADCAST_ID
- 字符串类型,表示请求刷新的唯一标识符
- 字符串值:
要从安全中心获取信号,请实现 BroadcastReceiver
实例。广播通过特殊的 BroadcastOptions
发送,该选项允许接收器启动前台服务。
BroadcastReceiver
响应刷新请求
public final class SafetySourceReceiver extends BroadcastReceiver {
// All the safety sources owned by this application.
private static final String[] ALL_SAFETY_SOURCES = new String[] {"MySourceId1", "…"};
@Override
public void onReceive(Context context, Intent intent) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// Must be on T or above to interact with Safety Center.
return;
}
String action = intent.getAction();
if (!SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES.equals(action)) {
return;
}
String refreshBroadcastId =
intent.getStringExtra(SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID);
if (refreshBroadcastId == null) {
// Should always be provided.
return;
}
String[] sourceIds =
intent.getStringArrayExtra(SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS);
if (sourceIds == null) {
sourceIds = ALL_SAFETY_SOURCES;
}
int requestType =
intent.getIntExtra(
SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE,
SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_GET_DATA);
SafetyCenterManager safetyCenterManager = context.getSystemService(SafetyCenterManager.class);
if (safetyCenterManager == null) {
// Should not be null on T.
return;
}
if (!safetyCenterManager.isSafetyCenterEnabled()) {
// Preferably, no Safety Source code should be run if Safety Center is disabled.
return;
}
SafetyEvent refreshSafetyEvent =
new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
.setRefreshBroadcastId(refreshBroadcastId)
.build();
for (String sourceId : sourceIds) {
SafetySourceData safetySourceData = getSafetySourceDataFor(sourceId, requestType);
// Set the data (or report an error with reportSafetySourceError, if something went wrong).
safetyCenterManager.setSafetySourceData(sourceId, safetySourceData, refreshSafetyEvent);
}
}
private SafetySourceData getSafetySourceDataFor(String sourceId, int requestType) {
switch (requestType) {
case SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_GET_DATA:
return getRefreshSafetySourceDataFor(sourceId);
case SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA:
return getRescanSafetySourceDataFor(sourceId);
default:
}
return getRefreshSafetySourceDataFor(sourceId);
}
// Data to provide when the user opens the page or on specific events.
private SafetySourceData getRefreshSafetySourceDataFor(String sourceId) {
// Get data for the source, if it's a fast operation it could potentially be executed in the
// receiver directly.
// Otherwise, it must start some kind of foreground service or expedited job.
return null;
}
// Data to provide when the user pressed the rescan button.
private SafetySourceData getRescanSafetySourceDataFor(String sourceId) {
// Could be implemented the same way as getRefreshSafetySourceDataFor, depending on the source's
// need.
// Otherwise, could potentially perform a longer task.
// In which case, it must start some kind of foreground service or expedited job.
return null;
}
}
上面示例中的同一 BroadcastReceiver
实例在 AndroidManifest.xml
中声明
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="…">
<application>
<!-- … -->
<receiver android:name=".SafetySourceReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES"/>
</intent-filter>
</receiver>
<!-- … -->
</application>
</manifest>
理想情况下,安全中心来源的实现方式应使其在数据更改时调用 SafetyCenterManager
。出于系统健康原因,我们建议仅响应重新扫描信号(当用户点按扫描按钮时),而不是在用户打开安全中心时响应。如果需要此功能,则必须为来源设置配置文件中的 refreshOnPageOpenAllowed="true"
字段,以便在这些情况下接收传递的广播。
在安全中心启用或停用时做出响应
您可以使用此 intent 操作在安全中心启用或停用时做出响应
ACTION_SAFETY_CENTER_ENABLED_CHANGED
- 字符串值:
android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED
- 当设备运行时安全中心启用或停用时触发
- 不在启动时调用(为此使用
ACTION_BOOT_COMPLETED
) - 只能由系统发送的受保护 intent
- 作为显式 intent 发送到配置文件中的所有安全来源,需要
SEND_SAFETY_CENTER_UPDATE
权限 - 作为隐式 intent 发送,需要
READ_SAFETY_CENTER_STATUS
权限
- 字符串值:
此 intent 操作对于启用或停用与设备上的安全中心相关的功能非常有用。
实施解决操作
解决操作是 SafetySourceIssue.Action
实例,用户可以直接从安全中心屏幕解决该实例。用户点按操作按钮,安全来源发送的 SafetySourceIssue.Action
上的 PendingIntent
实例被触发,这会在后台解决问题并在完成后通知安全中心。
要实施解决操作,如果操作预计需要一些时间(PendingIntent.getService
),安全中心来源可以使用服务,或者使用广播接收器 (PendingIntent.getBroadcast
)。
使用以下代码向安全中心发送解决问题操作
Intent resolveIssueBroadcastIntent =
new Intent("my.package.name.MY_RESOLVING_ACTION").setClass(ResolveActionReceiver.class);
PendingIntent resolveIssue =
PendingIntent.getBroadcast(
context, requestCode, resolveIssueBroadcastIntent, PendingIntent.FLAG_IMMUTABLE);
SafetySourceData safetySourceData =
new SafetySourceData.Builder()
.setStatus(
new SafetySourceStatus.Builder(
"title", "summary", SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION)
.setPendingIntent(redirectToMyScreen)
.build())
.addIssue(
new SafetySourceIssue.Builder(
"MyIssueId",
"title",
"summary",
SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION,
"MyIssueTypeId")
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
.addAction(
new SafetySourceIssue.Action.Builder(
"MyIssueActionId", "label", resolveIssue)
.setWillResolve(true)
.build())
.build())
.build();
SafetyEvent safetyEvent = new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build();
safetyCenterManager.setSafetySourceData("MySourceId", safetySourceData, safetyEvent);
BroadcastReceiver
解决操作
public final class ResolveActionReceiver extends BroadcastReceiver {
private static final String MY_RESOLVING_ACTION = "my.package.name.MY_RESOLVING_ACTION";
@Override
public void onReceive(Context context, Intent intent) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// Must be on T or above to interact with Safety Center.
return;
}
String action = intent.getAction();
if (!MY_RESOLVING_ACTION.equals(action)) {
return;
}
SafetyCenterManager safetyCenterManager = context.getSystemService(SafetyCenterManager.class);
if (safetyCenterManager == null) {
// Should not be null on T.
return;
}
if (!safetyCenterManager.isSafetyCenterEnabled()) {
// Preferably, no Safety Source code should be run if Safety Center is disabled.
return;
}
resolveTheIssue();
SafetyEvent resolveActionSafetyEvent =
new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED)
.setSafetySourceIssueId("MyIssueId")
.setSafetySourceIssueActionId("MyIssueActionId")
.build();
SafetySourceData dataWithoutTheIssue = …;
// Set the data (or report an error with reportSafetySourceError and
// SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED, if something went wrong).
safetyCenterManager.setSafetySourceData("MySourceId", dataWithoutTheIssue, resolveActionSafetyEvent);
}
private void resolveTheIssue() {
// Resolves the issue for the user. Given this a BroadcastReceiver, this should be a fast action.
// Otherwise, a foreground service and PendingIntent.getService should be used instead (or a job
// could be scheduled here, too).
}
}
上面示例中的同一 BroadcastReceiver
实例在 AndroidManifest.xml
中声明
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="…">
<application>
<!-- … -->
<receiver android:name=".ResolveActionReceiver"
android:exported="false">
<intent-filter>
<action android:name="my.package.name.MY_RESOLVING_ACTION"/>
</intent-filter>
</receiver>
<!-- … -->
</application>
</manifest>
响应问题解除
您可以指定一个 PendingIntent
实例,该实例可以在 SafetySourceIssue
实例被解除时触发。安全中心处理这些问题解除
- 如果来源推送了一个问题,用户可以在安全中心屏幕上点按解除按钮(警告卡片上的 X 按钮)来解除该问题。
- 当用户解除问题时,如果问题仍然存在,则不会再次在界面中显示。
- 磁盘上的永久解除在设备重启期间仍然存在。
- 如果安全中心来源停止提供问题,然后在稍后再次提供该问题,则该问题会重新出现。这是为了允许以下情况:用户看到警告、解除警告,然后采取应缓解问题的操作,但随后用户再次执行某些操作,导致类似的问题。此时,警告卡片应重新出现。
- 除非用户多次解除黄色和红色警告卡片,否则它们每 180 天重新出现一次。
除非出现以下情况,否则来源不应需要其他行为:
- 来源尝试以不同的方式实施此行为,例如,永不重新显示问题。
- 来源尝试将其用作回调,例如,记录信息。
为多个用户/个人资料提供数据
SafetyCenterManager
API 可跨用户和个人资料使用。如需了解详情,请参阅构建支持多用户的应用。提供 SafetyCenterManager
的 Context
对象与 UserHandle
实例相关联,因此返回的 SafetyCenterManager
实例与该 UserHandle
实例的安全中心互动。默认情况下,Context
与正在运行的用户关联,但如果应用拥有 INTERACT_ACROSS_USERS
和 INTERACT_ACROSS_USERS_FULL
权限,则可以为其他用户创建实例。此示例展示了跨用户/个人资料进行调用
Context userContext = context.createContextAsUser(userHandle, 0);
SafetyCenterManager userSafetyCenterManager = userContext.getSystemService(SafetyCenterManager.class);
if (userSafetyCenterManager == null) {
// Should not be null on T.
return;
}
// Calls to userSafetyCenterManager will provide data for the given userHandle
设备上的每个用户可以拥有多个受管理的用户资料。安全中心为每个用户提供不同的数据,但会合并与给定用户关联的所有受管理的用户资料的数据。
当在配置文件中为来源设置 profile="all_profiles"
时,会发生以下情况
- 用户(用户资料父项)及其所有关联的受管理的用户资料(使用
titleForWork
实例)都有一个 UI 条目。 刷新或重新扫描信号会发送给用户资料父项及其所有关联的受管理的用户资料。关联的接收器会为每个用户资料启动,并且可以直接向
SafetyCenterManager
提供关联的数据,而无需进行跨用户资料调用,除非接收器或应用是singleUser
。来源应为用户及其所有受管理的用户资料提供数据。每个 UI 条目的数据可能因用户资料而异。
测试
您可以访问 ShadowSafetyCenterManager
并在 Robolectric 测试中使用它。
private static final String MY_SOURCE_ID = "MySourceId";
private final MyClass myClass = …;
private final SafetyCenterManager safetyCenterManager = getApplicationContext().getSystemService(SafetyCenterManager.class);
@Test
public void whenRefreshingData_providesDataToSafetyCenterForMySourceId() {
shadowOf(safetyCenterManager).setSafetyCenterEnabled(true);
setupDataForMyClass(…);
myClass.refreshData();
SafetySourceData expectedSafetySourceData = …;
assertThat(safetyCenterManager.getSafetySourceData(MY_SOURCE_ID)).isEqualTo(expectedSafetySourceData);
SafetyEvent expectedSafetyEvent = …;
assertThat(shadowOf(safetyCenterManager).getLastSafetyEvent(MY_SOURCE_ID)).isEqualTo(expectedSafetyEvent);
}
您可以编写更多端到端 (E2E) 测试,但这超出了本指南的范围。有关编写这些 E2E 测试的更多信息,请参阅 CTS 测试 (CtsSafetyCenterTestCases)
测试和内部 API
内部 API 和测试 API 供内部使用,因此本指南未对其进行详细描述。但是,我们将来可能会扩展一些内部 API,以允许 OEM 从中构建自己的 UI,并且我们将更新本指南以提供有关如何使用它们的指导。
权限
MANAGE_SAFETY_CENTER
internal|installer|role
- 用于内部安全中心 API
- 仅授予 PermissionController 和 shell
“设置”应用
安全中心重定向
默认情况下,安全中心通过“设置”应用访问,并带有一个新的 安全和隐私 条目。如果您使用不同的“设置”应用或修改了“设置”应用,您可能需要自定义安全中心的访问方式。
启用安全中心时
- 旧版 隐私 条目已隐藏 代码
- 旧版 安全 条目已隐藏 代码
- 新的 安全和隐私 条目已添加 代码
- 新的 安全和隐私 条目重定向到安全中心 代码
android.settings.PRIVACY_SETTINGS
和android.settings.SECURITY_SETTINGS
intent 操作已重定向以打开安全中心(代码:security,privacy)
高级安全和隐私页面
“设置”应用在更多安全设置和更多隐私设置标题下包含其他设置,可从安全中心访问
高级安全代码
高级隐私代码
从 Android 14 开始,高级安全和高级隐私设置页面合并到一个名为“更多安全和隐私”的页面下,intent 操作为
"com.android.settings.MORE_SECURITY_PRIVACY_SETTINGS"
安全来源
安全中心与“设置”应用提供的一组特定安全来源集成
- 锁屏安全来源验证是否已使用密码(或其他安全措施)设置锁屏,以确保用户的私人信息免受外部访问。
- 生物识别安全来源(默认情况下处于隐藏状态)表面与指纹或面部传感器集成。
这些安全中心来源的源代码可通过 Android 代码搜索 访问。如果“设置”应用未修改(未更改软件包名称、源代码或处理锁屏和生物识别技术的源代码),则此集成应可直接使用。否则,可能需要进行一些修改,例如更改配置文件以更改“设置”应用的软件包名称和与安全中心集成的来源,以及集成。有关更多信息,请参阅更新配置文件和集成设置。
关于 PendingIntent
如果您依赖 Android 14 或更高版本中现有的“设置”应用安全中心集成,则下面描述的错误已修复。在这种情况下,无需阅读本节。
当您确定该错误不存在时,请在“设置”应用中将 XML 布尔资源配置值 config_isSafetyCenterLockScreenPendingIntentFixed
设置为 true
,以关闭安全中心内的解决方法。
PendingIntent 解决方法
此错误是由“设置”使用 Intent
实例 extra 来确定要打开哪个 fragment 引起的。由于 Intent#equals
未将 Intent
实例 extra 考虑在内,因此齿轮菜单图标和条目的 PendingIntent
实例被视为相等,并导航到同一 UI(即使它们旨在导航到不同的 UI)。此问题已在 QPR 版本中修复,方法是按请求代码区分 PendingIntent
实例。或者,可以使用 Intent#setId
来区分。
内部安全来源
一些安全中心来源是内部的,并在 PermissionController 模块内的 PermissionController 系统应用中实现。这些来源的行为类似于常规安全中心来源,并且没有受到特殊处理。这些来源的代码可通过 Android 代码搜索 获得。
这些主要是隐私信号,例如
- 无障碍功能
- 自动撤消未使用的应用
- 位置信息访问权限
- 通知侦听器
- 工作单位政策信息