與安全中心互動

重新導向至安全中心

任何應用程式都可以使用android.content.Intent.ACTION_SAFETY_CENTER操作(字串值android.intent.action.SAFETY_CENTER )開啟安全中心。

若要開啟安全中心,請從Activity實例中進行呼叫:

Intent openSafetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER);

startActivity(openSafetyCenterIntent);

重定向到特定問題

也可以使用特定意圖附加功能重定向到特定安全中心警告卡。這些額外功能不適合由第三方使用,因此它們是SafetyCenterManager的一部分,而 SafetyCenterManager 是@SystemApi的一部分。只有系統應用程式可以存取這些附加功能。

重定向特定警告卡的意圖額外內容:

  • EXTRA_SAFETY_SOURCE_ID
    • 字串值: android.safetycenter.extra.SAFETY_SOURCE_ID
    • String類型:指定關聯警告卡的安全來源ID
    • 重定向到問題所必需的
  • EXTRA_SAFETY_SOURCE_ISSUE_ID
    • 字串值: android.safetycenter.extra.SAFETY_SOURCE_ISSUE_ID
    • String類型:指定警告卡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 中,這顯示為可折疊條目)。

可以使用此意圖額外重定向到特定子頁面:

  • 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是一個@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_STATUSSEND_SAFETY_CENTER_UPDATE權限:

boolean isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
if (isSafetyCenterEnabled) {
  // …
} else {
  // …
}

提供數據

具有給定String sourceId的安全中心來源資料透過SafetySourceData物件提供給安全中心,該物件表示 UI 條目和問題清單(警告卡)。 UI 條目和警告卡可以具有在SafetySourceData類別中指定的不同嚴重性等級:

  • SEVERITY_LEVEL_UNSPECIFIED
    • 未指定嚴重程度
    • 顏色:灰色或透明(取決於條目的SafetySourcesGroup
    • 用於在 UI 中充當靜態條目或顯示未指定條目的動態數據
    • 不得用於警告卡
  • SEVERITY_LEVEL_INFORMATION
    • 基本資訊或小建議
    • 顏色: 綠色
  • SEVERITY_LEVEL_RECOMMENDATION
    • 建議用戶對此問題採取行動,因為這可能會使他們面臨風險
    • 顏色: 黃色
  • SEVERITY_LEVEL_CRITICAL_WARNING
    • 嚴重警告用戶必須對此問題採取行動,因為它有風險
    • 紅色

SafetySourceData

SafetySourceData物件由 UI 條目、警告卡和不變量組成。

  • 可選的SafetySourceStatus實例(UI 條目)
  • SafetySourceIssue實例清單(警告卡)
  • 可選附加Bundle (14 件起)
  • 不變量:
    • SafetySourceIssue清單必須由具有唯一識別碼的問題組成。
    • SafetySourceIssue實例的重要性不得高於SafetySourceStatus (如果存在)(除非SafetySourceStatusSEVERITY_LEVEL_UNSPECIFIED ,在這種情況下允許SEVERITY_LEVEL_INFORMATION問題)。
    • 必須滿足 API 配置施加的附加要求,例如,如果來源僅用於發出,則它不得提供SafetySourceStatus實例。

SafetySourceStatus

  • 必需的CharSequence標題
  • 所需的CharSequence摘要
  • 所需的嚴重級別
  • 可選的PendingIntent實例,用於將使用者重定向到正確的頁面(預設使用配置中的intentAction ,如果有的話)
  • 可選的IconAction (顯示為條目上的側面圖示)組成:
    • 必需的圖標類型,必須是以下類型之一:
      • ICON_TYPE_GEAR :顯示為 UI 條目旁的齒輪
      • ICON_TYPE_INFO :顯示為 UI 條目旁的資訊圖示
    • 需要PendingIntent將使用者重新導向到另一個頁面
  • 可選的布林enabled值,允許將 UI 條目標記為停用,因此它不可單擊(預設為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問題類型標識符;這與問題標識符類似,但不必是唯一的,用於記錄
  • 重複資料刪除 ID 的可選String ,這允許從不同來源發布相同的SafetySourceIssue ,並且僅在 UI 中顯示一次,假設它們具有相同的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實例清單必須包含一個或兩個Action元素。如果可操作性不是ISSUE_ACTIONABILITY_MANUAL ,則允許有零個Action
    • OnDismiss PendingIntent不得開啟Activity實例
    • API 配置施加的附加要求

資料是根據某些事件提供給安全中心的,因此有必要指定導致來源向SafetySourceData提供SafetyEvent實例的原因。

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 ;使用此選項而不是SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED以使安全中心能夠追蹤正在解決的SafetySourceIssue.Action
    • SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED :我們嘗試直接從安全中心螢幕解決SafetySourceIssue.Action ,但未能成功;使用此選項取代SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED以使安全中心能夠追蹤失敗的SafetySourceIssue.Action
    • 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_SUCCEEDEDSAFETY_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);

取得最後提供的數據

您可以獲得為您的應用程式擁有的來源提供給安全中心的最新資料。您可以使用它在您自己的 UI 中顯示某些內容,在執行昂貴的操作之前檢查資料是否需要更新,或者透過一些變更或新的SafetyEvent實例向安全中心提供相同的SafetySourceData實例。它對於測試也很有用。

使用此程式碼取得最後提供給安全中心的資料:

SafetySourceData lastDataProvided = safetyCenterManager.getSafetySourceData("MySourceId");

報告錯誤

如果您無法收集SafetySourceData數據,您可以向安全中心報告錯誤,安全中心會將條目變更為灰色,清除快取的數據,並提供類似Couldn't check setting之類的訊息。如果SafetySourceIssue.Action實例無法解析,您也可以報告錯誤,在這種情況下,快取的資料不會被清除,UI 條目也不會更改;但會向用戶顯示一則訊息,讓他們知道出了問題。

您可以使用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
    • 當安全中心發送刷新給定應用程式安全來源資料的請求時觸發
    • 只能由系統發送的受保護意圖
    • 以明確意圖傳送至設定檔中的所有安全來源,並需要SEND_SAFETY_CENTER_UPDATE權限

作為本次廣播的一部分,提供了以下額外內容:

  • 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
    • String類型,表示請求刷新的唯一識別符

若要從安全中心取得訊號,請實作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"字段,以便來源接收在這些情況下傳遞的廣播。

啟用或停用時響應安全中心

您可以使用以下意圖操作來回應安全中心何時啟用或停用:

  • ACTION_SAFETY_CENTER_ENABLED_CHANGED
    • 字串值: android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED
    • 當設備運作時啟用或停用安全中心時觸發
    • 啟動時不呼叫(為此使用ACTION_BOOT_COMPLETED
    • 只能由系統發送的受保護意圖
    • 作為明確意圖傳送到設定檔中的所有安全來源,需要SEND_SAFETY_CENTER_UPDATE權限
    • 以需要READ_SAFETY_CENTER_STATUS權限的隱式意圖傳送

此意圖操作對於啟用或停用與裝置上的安全中心相關的功能非常有用。

實施解決措施

解析操作是一個SafetySourceIssue.Action實例,使用者可以直接從安全中心畫面解析該實例。使用者點擊操作按鈕,安全來源發送的SafetySourceIssue.Action上的PendingIntent實例將被觸發,這會在後台解決問題並在完成後通知安全中心。

若要實作解析操作,如果操作預計需要一些時間 ( PendingIntent.getService ),安全中心來源可以使用服務 ( 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按鈕)在安全中心螢幕上將其關閉。
  • 當用戶消除問題時,如果問題仍然存在,它不會再次出現在 UI 中。
  • 在設備重新啟動期間,磁碟上的持久解除仍然存在。
  • 如果安全中心來源停止提供問題,然後稍後再次提供問題,則問題會重新出現。這是為了允許以下情況:使用者看到警告,將其忽略,然後採取措施緩解問題,但隨後使用者再次執行某些操作,導致類似問題。此時,警告卡應該會重新出現。
  • 黃色和紅色警告卡每 180 天就會重新出現一次,除非使用者多次忽略它們。

源不需要額外的行為,除非:

  • 消息來源嘗試以不同的方式實現此行為,例如,永遠不會重新出現該問題。
  • 來源嘗試將其用作回調,例如記錄資訊。

為多個使用者/設定檔提供數據

SafetyCenterManager API 可以跨使用者和設定檔使用。有關更多信息,請參閱建立多用戶感知的應用程式。提供SafetyCenterManagerContext物件與UserHandle實例關聯,因此傳回的SafetyCenterManager實例與該UserHandle實例的安全中心進行互動。預設情況下, Context與正在運行的使用者關聯,但如果應用程式擁有INTERACT_ACROSS_USERSINTERACT_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_SETTINGSandroid.settings.SECURITY_SETTINGS Intent 作業被重新導向到開啟安全中心(代碼: securityPrivacy

高級安全和隱私頁面

「設定」應用程式在「更多安全設定」「更多隱私設定」標題下包含其他設置,可從安全中心取得:

安全來源

安全中心與「設定」應用程式提供的一組特定安全來源整合:

  • 鎖定螢幕安全來源驗證鎖定畫面是否設定了密碼(或其他安全措施),以確保使用者的私人資訊免受外部存取。
  • 生物辨識安全源(預設隱藏)表面可與指紋或臉部感應器整合。

這些安全中心來源的源代碼可透過Android 代碼搜尋存取。如果未修改「設定」應用程式(未對程式包名稱、原始程式碼或處理鎖定螢幕和生物識別的原始程式碼進行更改),則此整合應該可以開箱即用。否則,可能需要進行一些修改,例如更改設定檔以更改「設定」應用程式的套件名稱以及與安全中心整合的來源以及整合。有關詳細信息,請參閱更新設定檔整合設定

關於 PendingIntent

如果您依賴 Android 14 或更高版本中現有的「設定」應用程式安全中心集成,則下面描述的錯誤已修復。在這種情況下,沒有必要閱讀本節。

當您確定該錯誤不存在時,請將「設定」應用程式config_isSafetyCenterLockScreenPendingIntentFixed中的 XML 布林資源配置值設為true以關閉安全中心內的解決方法。

PendingIntent 解決方法

此錯誤是由設定使用Intent實例額外資訊來決定要開啟哪個片段所引起的。由於Intent#equals不考慮Intent實例額外信息,因此齒輪選單圖示和條目的PendingIntent實例被視為相等並導航到相同的 UI(即使它們旨在導航到不同的 UI)。透過按請求代碼區分PendingIntent實例,此問題在 QPR 版本中已修復。或者,可以透過使用Intent#setId來區分。

內部安全源

一些安全中心來源是內部的,並在 PermissionController 模組內的 PermissionController 系統應用程式中實作。這些源的行為與常規安全中心源一樣,沒有受到特殊處理。這些來源的代碼可透過Android 代碼搜尋取得。

這些主要是隱私訊號,例如:

  • 無障礙
  • 自動撤銷未使用的應用程式
  • 位置訪問
  • 通知監聽器
  • 工作政策訊息