Redirect to Safety Center
Any app can open Safety Center using the
android.content.Intent.ACTION_SAFETY_CENTER action (string value
android.intent.action.SAFETY_CENTER).
To open Safety Center, make a call from within an Activity instance:
Intent openSafetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER);
startActivity(openSafetyCenterIntent);
Redirect to a specific issue
It's also possible to redirect to a specific Safety Center warning card using
specific intent extras. These extras aren't meant to be used by third parties so
they're part of SafetyCenterManager, which is part of @SystemApi. Only
system apps can access these extras.
Intent extras that redirect a specific warning card:
EXTRA_SAFETY_SOURCE_ID- String value:
android.safetycenter.extra.SAFETY_SOURCE_ID - String type: Specifies the ID of the safety source of the associated warning card
- Required for the redirection to the issue to work
- String value:
EXTRA_SAFETY_SOURCE_ISSUE_ID- String value:
android.safetycenter.extra.SAFETY_SOURCE_ISSUE_ID - String type: Specifies the warning card ID
- Required for the redirection to the issue to work
- String value:
EXTRA_SAFETY_SOURCE_USER_HANDLE- String value:
android.safetycenter.extra.SAFETY_SOURCE_USER_HANDLE UserHandletype: SpecifiesUserHandlefor the associated warning card- Optional (default is current user)
- String value:
The code snippet below can be used from within an Activity instance to open
the Safety Center screen to a specific issue:
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);
Redirect to a specific subpage (Starting Android 14)
In Android 14 or above, the Safety Center page is split
into multiple subpages which represent the different SafetySourcesGroup (in
Android 13, this is shown as collapsible entries).
It’s possible to redirect to a specific subpage by using this intent extra:
EXTRA_SAFETY_SOURCES_GROUP_ID- String value:
android.safetycenter.extra.SAFETY_SOURCES_GROUP_ID - String type: Specifies the ID of the
SafetySourcesGroup - Required for the redirection to the subpage to work
- String value:
The code snippet below can be used from within an Activity instance to open
the Safety Center screen to a specific subpage:
Intent openSafetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER)
.putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID, "TheSafetySourcesGroupId");
startActivity(openSafetyCenterIntent);
Use the Safety Center source APIs
The Safety Center source APIs are available using
SafetyCenterManager
(which is a @SystemApi). Code for the API surface is available in
Code
Search.
Implementation code of the APIs is available in Code
Search.
Permissions
The Safety Center source APIs are accessible only by allowlisted system apps using the permissions listed below. For additional information, see Privileged Permission Allowlisting.
READ_SAFETY_CENTER_STATUSsignature|privileged- Used for the
SafetyCenterManager#isSafetyCenterEnabled()API (not needed for Safety Center sources, they only need theSEND_SAFETY_CENTER_UPDATEpermission) - Used by system apps that check if the Safety Center is enabled
- Granted only to allowlisted system apps
SEND_SAFETY_CENTER_UPDATEinternal|privileged- Used for the enabled API and the Safety Sources API
- Used by safety sources only
- Granted only to allowlisted system apps
These permissions are privileged and you can acquire them only by adding them to
the relevant file, for example, the
com.android.settings.xml
file for the Settings app, and to the app's AndroidManifest.xml file. See
protectionLevel
for more information on the permission model.
Get the SafetyCenterManager
SafetyCenterManager is a @SystemApi class that's accessible from system apps
starting in Android 13. This call demonstrates how to
get 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;
}
Check if Safety Center is enabled
This call checks whether Safety Center is enabled. The call requires either the
READ_SAFETY_CENTER_STATUS or the SEND_SAFETY_CENTER_UPDATE permission:
boolean isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
if (isSafetyCenterEnabled) {
// …
} else {
// …
}
Provide data
Safety Center source data with the given String sourceId is provided to Safety
Center with the SafetySourceData object, which represents a UI entry and a
list of issues (warning cards). The UI entry and the warning cards can have
different severity levels specified in the SafetySourceData class:
SEVERITY_LEVEL_UNSPECIFIED- No severity specified
- Color: Gray or transparent (depending on the
SafetySourcesGroupof the entry) - Used for dynamic data that poses as a static entry in the UI or to show an unspecified entry
- Must not be used for warning cards
SEVERITY_LEVEL_INFORMATION- Basic information or minor suggestion
- Color: Green
SEVERITY_LEVEL_RECOMMENDATION- Recommendation that the user should take action on this issue, as it could put them at risk
- Color: Yellow
SEVERITY_LEVEL_CRITICAL_WARNING- Critical warning that the user must take action on this issue, as it presents a risk
- Color: Red
SafetySourceData
The SafetySourceData object is composed of a UI entry, warning cards, and
invariants.
- Optional
SafetySourceStatusinstance (UI entry) - List of
SafetySourceIssueinstances (warning cards) - Optional
Bundleextras (Starting 14) - Invariants:
- The
SafetySourceIssuelist must be composed of issues with unique identifiers. - The
SafetySourceIssueinstance must not be of greater importance thanSafetySourceStatusif there is one (unlessSafetySourceStatusisSEVERITY_LEVEL_UNSPECIFIED, in which caseSEVERITY_LEVEL_INFORMATIONissues are allowed). - Additional requirements imposed by the API configuration must be met,
for example, if the source is issue-only, it must not provide a
SafetySourceStatusinstance.
- The
SafetySourceStatus
- Required
CharSequencetitle - Required
CharSequencesummary - Required severity level
- Optional
PendingIntentinstance to redirect the user to the right page (default usesintentActionfrom the config, if any) - Optional
IconAction(shown as a side icon on the entry) composed of:- Required icon type, which must be one of the following types:
ICON_TYPE_GEAR: Shown as a gear next to the UI entryICON_TYPE_INFO: Shown as an information icon next to the UI entry
- Required
PendingIntentto redirect the user to another page
- Required icon type, which must be one of the following types:
- Optional boolean
enabledvalue that allows marking the UI entry as disabled, so it isn't clickable (default istrue) - Invariants:
PendingIntentinstances must open anActivityinstance.- If the entry is disabled, it must be designated
SEVERITY_LEVEL_UNSPECIFIED. - Additional requirements imposed by the API configuration.
SafetySourceIssue
- Required unique
Stringidentifier - Required
CharSequencetitle - Optional
CharSequencesubtitle - Required
CharSequencesummary - Required severity level
- Optional issue category, which must be one of:
ISSUE_CATEGORY_DEVICE: The issue affects the user's device.ISSUE_CATEGORY_ACCOUNT: The issue affects the user's accounts.ISSUE_CATEGORY_GENERAL: The issue affects the user's general safety. This is the default.ISSUE_CATEGORY_DATA(Starting Android 14): The issue affects the user's data.ISSUE_CATEGORY_PASSWORDS(Starting Android 14): The issue affects the user's passwords.ISSUE_CATEGORY_PERSONAL_SAFETY(Starting Android 14): The issue affects the user's personal safety.
- List of
Actionelements that the user can take for this issue, eachActioninstance being composed of:- Required unique
Stringidentifier - Required
CharSequencelabel - Required
PendingIntentto redirect the user to another page or process the action directly from the Safety Center screen - Optional boolean to specify if this issue can be resolved directly from
the Safety Center screen (default is
false) - Optional
CharSequencesuccess message, to be displayed to the user when the issue is successfully resolved directly from the Safety Center screen
- Required unique
- Optional
PendingIntentthat's called when the user dismisses the issue (default is nothing is called) - Required
Stringissue type identifier; this is similar to the issue identifier but doesn't have to be unique and is used for logging - Optional
Stringfor the deduplication id, this allows posting the sameSafetySourceIssuefrom different sources and only showing it once in the UI assuming they have the samededuplicationGroup(Starting Android 14). If not specified, the issue is never deduplicated - Optional
CharSequencefor the attribution title, this is a text that shows where the warning card originated (Starting Android 14). If not specified uses the title of theSafetySourcesGroup - Optional issue actionability (Starting Android 14),
which must be one of:
ISSUE_ACTIONABILITY_MANUAL: The user needs to resolve this issue manually. This is the default.ISSUE_ACTIONABILITY_TIP: This issue is just a tip and may not require any user input.ISSUE_ACTIONABILITY_AUTOMATIC: This issue has already been actioned and may not require any user input.
- Optional notification behavior (Starting Android
14), which must be one of:
NOTIFICATION_BEHAVIOR_UNSPECIFIED: Safety Center will decide whether a notification is needed for the warning card. This is the default.NOTIFICATION_BEHAVIOR_NEVER: No notification is posted.NOTIFICATION_BEHAVIOR_DELAYED: A notification is posted some time after the issue is first reported.NOTIFICATION_BEHAVIOR_IMMEDIATELY: A notification is posted as soon as the issue is reported.
- Optional
Notification, to show a custom notification with the warning card (Starting Android 14). If not specified, theNotificationis derived from the warning card. Composed of:- Required
CharSequencetitle - Required
CharSequencesummary - List of
Actionelements that the user can take for this notification
- Required
- Invariants:
- The list of
Actioninstances must be composed of actions with unique identifiers - The list of
Actioninstances must contain either one or twoActionelements. If the actionability is notISSUE_ACTIONABILITY_MANUAL, having zeroActionis allowed. - The OnDismiss
PendingIntentmust not open anActivityinstance - Additional requirements imposed by the API configuration
- The list of
Data is provided upon certain events to the Safety Center, so it's necessary to
specify what caused the source to provide SafetySourceData with a
SafetyEvent instance.
SafetyEvent
- Required type, which must be one of:
SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED: The state of the source has changed.SAFETY_EVENT_TYPE_REFRESH_REQUESTED: Responding to a refresh/rescan signal from Safety Center; use this instead ofSAFETY_EVENT_TYPE_SOURCE_STATE_CHANGEDfor Safety Center to be able track the refresh/rescan request.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED: We resolvedSafetySourceIssue.Actiondirectly from the Safety Center screen; use this instead ofSAFETY_EVENT_TYPE_SOURCE_STATE_CHANGEDfor Safety Center to be able track theSafetySourceIssue.Actionbeing resolved.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED: We attempted to resolveSafetySourceIssue.Actiondirectly from the Safety Center screen, but failed to do so; use this instead ofSAFETY_EVENT_TYPE_SOURCE_STATE_CHANGEDfor Safety Center to be able trackSafetySourceIssue.Actionhaving failed.SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED: The language of the device has changed, so we're updating the text of the data provided; it's permitted to useSAFETY_EVENT_TYPE_SOURCE_STATE_CHANGEDfor this.SAFETY_EVENT_TYPE_DEVICE_REBOOTED: We're providing this data as part of an initial boot as the Safety Center data isn't persisted across reboots; it's permitted to useSAFETY_EVENT_TYPE_SOURCE_STATE_CHANGEDfor this.
- Optional
Stringidentifier for the refresh broadcast ID. - Optional
Stringidentifier for theSafetySourceIssueinstance getting resolved. - Optional
Stringidentifier for theSafetySourceIssue.Actioninstance getting resolved. - Invariants:
- The refresh broadcast ID must be provided if the type is
SAFETY_EVENT_TYPE_REFRESH_REQUESTED - The issue and action IDs must be provided if the type is either
SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDEDorSAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED
- The refresh broadcast ID must be provided if the type is
Below is an example of how a source might provide data to Safety Center (in this case it's providing an entry with a single warning card):
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);
Get last data provided
You can get the last data provided to Safety Center for a source owned by your
app. You can use this to surface something in your own UI, to check if the data
needs to be updated before performing an expensive operation, or to provide the
same SafetySourceData instance to Safety Center with some changes or with a
new SafetyEvent instance. It's also useful for testing.
Use this code to get the last data provided to Safety Center:
SafetySourceData lastDataProvided =
safetyCenterManager.getSafetySourceData("MySourceId");
Report an error
If you can't gather SafetySourceData data, you can report the error to Safety
Center, which changes the entry to gray, clears the cached data, and provides a
message something like Couldn't check setting. You can also report an error if
an instance of SafetySourceIssue.Action fails to resolve, in which case the
cached data isn't cleared and the UI entry isn't changed; but a message is
surfaced to the user to let them know that something went wrong.
You can provide the error using SafetySourceErrorDetails, which is composed
of:
SafetySourceErrorDetails: RequiredSafetyEventinstance:
// 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);
Respond to a refresh or rescan request
You can get a signal from the Safety Center to provide new data. Responding to a refresh or rescan request ensures that the user views the current status when opening Safety Center and when they tap the scan button.
This is done by receiving a broadcast with the following action:
ACTION_REFRESH_SAFETY_SOURCES- String value:
android.safetycenter.action.REFRESH_SAFETY_SOURCES - Triggered when Safety Center is sending a request to refresh the data of the safety source for a given app
- Protected intent that can be sent only by the system
- Sent to all safety sources in the configuration file as an explicit
intent and requires the
SEND_SAFETY_CENTER_UPDATEpermission
- String value:
The following extras are provided as part of this broadcast:
EXTRA_REFRESH_SAFETY_SOURCE_IDS- String value:
android.safetycenter.extra.REFRESH_SAFETY_SOURCE_IDS - String array type (
String[]), represents the source IDs to refresh for the given app
- String value:
EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE- String value:
android.safetycenter.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE - Integer type, represents a request type
@IntDef - Must be one of:
EXTRA_REFRESH_REQUEST_TYPE_GET_DATA: Requests the source to provide data relatively fast, typically when the user opens the pageEXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA: Requests the source to provide data as fresh as possible, typically when the user presses the rescan button
- String value:
EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID- String value:
android.safetycenter.extra.REFRESH_SAFETY_SOURCES_BROADCAST_ID - String type, represents a unique identifier for the requested refresh
- String value:
To get a signal from the Safety Center, implement a
BroadcastReceiver
instance. The broadcast is sent with special BroadcastOptions that allows the
receiver to start a foreground service.
BroadcastReceiver responds to a refresh request:
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;
}
}
The same instance of BroadcastReceiver in the example above is declared in
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>
Ideally, a Safety Center source is implemented in such a way that it calls
SafetyCenterManager when its data changes. For system health reasons, we
recommend responding only to the rescan signal (when the user taps the scan
button), and not when the user opens the Safety Center. If this functionality is
required, the refreshOnPageOpenAllowed="true" field in the configuration file
must be set for the source to receive the broadcast delivered in these cases.
Respond to the Safety Center when enabled or disabled
You can respond to when the Safety Center when enabled or disabled by using this intent action:
ACTION_SAFETY_CENTER_ENABLED_CHANGED- String value:
android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED - Triggered when the Safety Center is either enabled or disabled while the device is running
- Not called on boot (use
ACTION_BOOT_COMPLETEDfor that) - Protected intent that can be sent only by the system
- Sent to all safety sources in the configuration file as an explicit
intent, requires the
SEND_SAFETY_CENTER_UPDATEpermission - Sent as an implicit intent that requires the
READ_SAFETY_CENTER_STATUSpermission
- String value:
This intent action is useful to enable or disable features that are related to Safety Center on the device.
Implement resolving actions
A resolving action is a SafetySourceIssue.Action instance that a user can
resolve directly from the Safety Center screen. The user taps an action button
and the PendingIntent instance on SafetySourceIssue.Action sent by the
safety source is triggered, which resolves the issue in the background and
notifies the Safety Center when it's done.
To implement resolving actions, the Safety Center source can use a service if
the operation is expected to take some time (PendingIntent.getService) or a
broadcast receiver (PendingIntent.getBroadcast).
Use this code to send a resolving issue to Safety Center:
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 resolves the action:
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).
}
}
The same instance of BroadcastReceiver in the example above is declared in
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>
Respond to issue dismissals
You can specify a PendingIntent instance that can be triggered when a
SafetySourceIssue instance is dismissed. The Safety Center handles these issue
dismissals:
- If a source pushes an issue, a user can dismiss it on the Safety Center screen by tapping the dismiss button (an X button on the warning card).
- When a user dismisses an issue, if the issue continues, it won't be surfaced in the UI again.
- Persistent dismissals on a disk remain during device reboots.
- If the Safety Center source stops providing an issue and then provides the issue again at a later time, the issue resurfaces. This is to allow situations where a user sees a warning, dismisses it, then takes action that should alleviate the problem but then the user does something again that causes a similar issue. At this point, the warning card should resurface.
- Yellow and red warning cards resurface every 180 days unless the user has dismissed them multiple times.
Additional behaviors shouldn't be needed by the source unless:
- The source tries to implement this behavior differently, for example, never resurface the issue.
- The source tries to use this as a callback, for example, to log the information.
Provide data for multiple users/profiles
The SafetyCenterManager API can be used across users and profiles. For more
information, see Building Multiuser-Aware
Apps. The Context
object that provides SafetyCenterManager is associated with a UserHandle
instance, so the returned SafetyCenterManager instance interacts with the
Safety Center for that UserHandle instance. By default, Context is
associated with the running user, but it's possible to create an instance for
another user if the app holds the INTERACT_ACROSS_USERS and
INTERACT_ACROSS_USERS_FULL permissions. This example shows making a call
across users/profiles:
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
Each user on the device can have multiple managed profiles. The Safety Center provides different data for each user, but merges the data of all the managed profiles associated with a given user.
When profile="all_profiles" is set for the source in the configuration file,
the following occurs:
- There's a UI entry for the user (profile parent) and all of its associated
managed profiles (which use
titleForWorkinstances). The refresh or rescan signal is sent for the profile parent and all the associated managed profiles. The associated receiver is started for each profile and can provide the associated data directly to
SafetyCenterManagerwithout having to make a cross-profile call unless the receiver or the app issingleUser.The source is expected to provide data for the user and all its managed profiles. The data for each UI entry might be different depending on the profile.
Testing
you can access ShadowSafetyCenterManager and use it in a Robolectric test.
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);
}
You can write more end-to-end (E2E) tests, but that's out of the scope of this guide. For more information about writing these E2E tests, see CTS tests (CtsSafetyCenterTestCases)
Test and internal APIs
The internal APIs and test APIs are for internal use so they aren't described in detail in this guide. However, we might extend some internal APIs in the future to allow OEMs to build their own UI from and we'll update this guide to provide guidance on how to use them.
Permissions
MANAGE_SAFETY_CENTERinternal|installer|role- Used for the internal Safety Center APIs
- Only granted to PermissionController and shell
Settings app
Safety Center redirection
By default, Safety Center is accessed through the Settings app with a new Security & privacy entry. If you use a different Settings app or if you've modified the Settings app, you might need to customize how Safety Center is accessed.
When Safety Center is enabled:
- Legacy Privacy entry is hidden code
- Legacy Security entry is hidden code
- New Security & privacy entry is added code
- New Security & privacy entry redirects to Safety Center code
android.settings.PRIVACY_SETTINGSandandroid.settings.SECURITY_SETTINGSintent actions are redirected to open Safety Center (code: security, privacy)
Advanced security and privacy pages
The Settings app contains additional settings under More security settings and More privacy settings titles, available from Safety Center:
Advanced security code
Advanced privacy code
Starting Android 14, the advanced security and advanced privacy settings page are merged under a single "More Security & Privacy" page with intent action
"com.android.settings.MORE_SECURITY_PRIVACY_SETTINGS"
Safety sources
Safety Center integrates with a specific set of safety sources provided by the Settings app:
- A lock screen safety source verifies that a lock screen is set up with a passcode (or other security), to ensure that the user's private information is kept safe from external access.
- A biometrics safety source (hidden by default) surfaces to integrate with a fingerprint or face sensor.
The source code for these Safety Center sources is accessible through Android code search. If the Settings app isn't modified (changes aren't made to the package name, source code or the source code that deals with a lock screen and biometrics), then this integration should work out of box. Otherwise, some modifications might be required such as changing the configuration file to change the package name of the Settings app and the sources that integrate with Safety Center, as well as the integration. For more information, see Update the configuration file and the integration settings.
About PendingIntent
If you rely on the existing Settings app Safety Center integration in Android 14 or above, the bug described below has been fixed. Reading this section is not necessary in this case.
When you're sure that the bug doesn't exist, set an XML boolean resource
configuration value in the Settings app
config_isSafetyCenterLockScreenPendingIntentFixed to true to turn off the
workaround within Safety Center.
PendingIntent workaround
This bug is caused by Settings using Intent instance extras to determine which
fragment to open. Because Intent#equals doesn't take the Intent instance
extras into account, the PendingIntent instance for the gear menu icon and the
entry are considered equal and navigate to the same UI (even though they're
intended to navigate to a different UI). This issue is fixed in a QPR release by
differentiating the PendingIntent instances by request code. Alternatively,
this could be differentiated by using Intent#setId.
Internal safety sources
Some Safety Center sources are internal and are implemented in the PermissionController system app inside the PermissionController module. These sources behave like regular Safety Center sources and receive no special treatment. Code for these sources is available through Android code search.
These are mainly privacy signals, for example:
- Accessibility
- Auto revoke unused apps
- Location access
- Notification listener
- Work policy info

