안전 센터와 상호작용

안전 센터로 리디렉션

모든 앱에서 android.content.Intent.ACTION_SAFETY_CENTER 작업(문자열 값 android.intent.action.SAFETY_CENTER)을 사용하여 안전 센터를 열 수 있습니다.

안전 센터를 열려면 Activity 인스턴스 내에서 다음과 같이 호출하세요.

Intent openSafetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER);

startActivity(openSafetyCenterIntent);

특정 문제로 리디렉션

특정 인텐트 추가 항목을 사용하여 특정 안전 센터 경고 카드로 리디렉션할 수도 있습니다. 이러한 추가 항목은 서드 파티에서 사용하면 안 되므로 @SystemApi의 일부인 SafetyCenterManager의 일부입니다. 시스템 앱만 이러한 추가 항목에 액세스할 수 있습니다.

특정 경고 카드를 리디렉션하는 인텐트 추가 항목은 다음과 같습니다.

  • 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에서는 접을 수 있는 항목으로 표시됨).

다음 인텐트 추가 항목을 사용하여 특정 하위 페이지로 리디렉션할 수 있습니다.

  • 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와 Safety Sources API에 사용됩니다.
    • 안전 소스에서만 사용됩니다.
    • 허용된 시스템 앱에만 부여됩니다.

이러한 권한은 독점적이며 관련 파일(예: 설정 앱의 com.android.settings.xml 파일) 및 앱의 AndroidManifest.xml 파일에 추가하면 획득할 수 있습니다. 권한 모델에 관한 자세한 내용은 protectionLevel을 참고하세요.

SafetyCenterManager 가져오기

SafetyCenterManager는 Android 13부터 시스템 앱에서 액세스할 수 있는 @SystemApi 클래스입니다. 이 호출은 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가 있는 안전 센터 소스 데이터는 UI 항목과 문제 목록(경고 카드)을 나타내는 SafetySourceData 객체를 사용하여 안전 센터에 제공됩니다. 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
  • UI 항목을 사용 중지됨으로 표시하여 클릭할 수 없도록 하는 선택적 불리언 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 문제 유형 식별자. 이는 문제 식별자와 비슷하나 고유하지 않아도 되며 로깅에 사용됩니다.
  • 중복 삭제 ID에 관한 선택적 String. 이를 통해 다양한 소스에서 동일한 SafetySourceIssue를 게시하고, 동일한 deduplicationGroup을 보유한다고 가정하여 UI에 한 번만 표시할 수 있습니다(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 PendingIntentActivity 인스턴스를 열면 안 됩니다.
    • 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);

제공된 마지막 데이터 가져오기

앱이 소유한 소스에 관해 안전 센터에 제공된 마지막 데이터를 가져올 수 있습니다. 이 데이터를 사용하여 자체 UI에 무언가를 표시하거나, 비용이 많이 드는 작업을 실행하기 전에 데이터를 업데이트해야 하는지 확인하거나, 일부를 변경하여 또는 새 SafetyEvent 인스턴스와 함께 안전 센터에 동일한 SafetySourceData 인스턴스를 제공할 수 있습니다. 테스트에도 유용합니다.

다음 코드를 사용하여 안전 센터에 제공된 마지막 데이터를 가져오세요.

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

오류 신고

SafetySourceData 데이터를 수집할 수 없다면 안전 센터에 오류를 신고할 수 있습니다. 그러면 항목이 회색으로 변경되고 캐시된 데이터가 삭제되며 설정을 확인할 수 없음과 같은 메시지가 표시됩니다. 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
    • 문자열 유형으로, 요청된 새로고침의 고유 식별자를 나타냅니다.

안전 센터에서 신호를 받으려면 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.ActionPendingIntent 인스턴스가 트리거되어 백그라운드에서 문제를 해결하고 완료 시 안전 센터에 알림이 전송됩니다.

해결 작업을 구현하려면 안전 센터 소스는 작업에 시간이 걸릴 것으로 예상되는 경우 서비스(PendingIntent.getService) 또는 broadcast receiver(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>

문제 닫기에 응답

SafetySourceIssue 인스턴스가 닫힐 때 트리거될 수 있는 PendingIntent 인스턴스를 지정할 수 있습니다. 안전 센터는 이러한 문제 닫기를 다음과 같이 처리합니다.

  • 소스에서 문제를 푸시하면 사용자는 닫기 버튼(경고 카드의 X 버튼)을 탭하여 안전 센터 화면에서 문제를 닫을 수 있습니다.
  • 사용자가 문제를 닫으면 문제가 계속돼도 UI에 다시 표시되지 않습니다.
  • 디스크에서 지속적 닫기는 기기 재부팅 중에 유지됩니다.
  • 안전 센터 소스에서 문제 제공을 중지했다가 나중에 문제를 다시 제공하면 문제가 다시 표시됩니다. 이는 사용자가 경고를 확인한 후 닫고 문제를 완화하는 조치를 취했지만 비슷한 문제를 일으키는 작업을 다시 실행하는 상황을 허용하기 위한 것입니다. 이 시점에 경고 카드가 다시 표시되어야 합니다.
  • 노란색 및 빨간색 경고 카드는 180일마다 다시 표시됩니다. 단, 사용자가 여러 번 닫은 경우는 예외입니다.

다음과 같은 경우를 제외하고 소스에는 추가 동작이 필요하지 않습니다.

  • 소스에서 이 동작을 다르게 구현하려고 합니다(예: 문제를 다시 표시하지 않음).
  • 소스에서 이를 콜백으로 사용하려고 합니다(예: 정보를 기록하기 위해).

여러 사용자/프로필 데이터 제공

SafetyCenterManager API는 사용자 및 프로필에서 사용할 수 있습니다. 자세한 내용은 여러 사용자 인식 앱 빌드를 참고하세요. SafetyCenterManager를 제공하는 Context 객체는 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 항목이 있습니다.
  • 새로고침 또는 재검색 신호가 프로필 상위 요소 및 연결된 모든 관리 프로필을 위해 전송됩니다. 연결된 수신기가 각 프로필에 대해 시작되고 수신기나 앱이 singleUser가 아닌 한 교차 프로필 호출을 실행하지 않고도 연결된 데이터를 직접 SafetyCenterManager에 제공할 수 있습니다.

  • 소스가 사용자 및 모든 관리 프로필의 데이터를 제공해야 합니다. 각 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를 빌드할 수 있도록 향후 내부 API를 확장할 수 있으므로 사용 방법을 설명하기 위해 이 가이드를 업데이트할 예정입니다.

권한

  • MANAGE_SAFETY_CENTER
    • internal|installer|role
    • 내부 안전 센터 API에 사용됩니다.
    • PermissionController 및 셸에만 부여됩니다.

설정 앱

안전 센터 리디렉션

기본적으로 안전 센터에는 새로운 보안 및 개인 정보 보호 항목이 있는 설정 앱을 통해 액세스합니다. 다른 설정 앱을 사용하거나 설정 앱을 수정한 경우 안전 센터에 액세스하는 방법을 맞춤설정해야 할 수 있습니다.

안전 센터가 사용 설정된 경우:

  • 기존 개인 정보 보호 항목은 숨겨진 코드입니다.
  • 기존 보안 항목은 숨겨진 코드입니다.
  • 새로운 보안 및 개인 정보 보호 항목은 추가된 코드입니다.
  • 새로운 보안 및 개인 정보 보호 항목은 안전 센터 코드로 리디렉션됩니다.
  • android.settings.PRIVACY_SETTINGSandroid.settings.SECURITY_SETTINGS 인텐트 작업은 안전 센터를 열도록 리디렉션됩니다(코드: security, privacy).

고급 보안 및 개인 정보 보호 페이지

설정 앱에는 기타 보안 설정기타 개인 정보 보호 설정 제목 아래에 추가 설정이 포함되어 있으며 안전 센터에서 사용할 수 있습니다.

안전 소스

안전 센터는 설정 앱에서 제공한 특정 안전 소스 세트와 통합됩니다.

  • 잠금 화면 안전 소스는 잠금 화면이 비밀번호(또는 다른 보안)로 설정되어 사용자의 개인 정보가 외부 액세스로부터 안전하게 보호되는지 확인합니다.
  • 생체 인식 안전 소스(기본적으로 숨겨짐)는 지문이나 얼굴 센서와 통합되도록 표시됩니다.

이러한 안전 센터 소스의 소스 코드에는 Android 코드 검색을 통해 액세스할 수 있습니다. 설정 앱이 수정되지 않은 경우(패키지 이름이나 소스 코드, 잠금 화면과 생체 인식을 처리하는 소스 코드가 변경되지 않음) 이 통합은 즉시 작동합니다. 그 외의 경우 통합뿐 아니라 설정 앱의 패키지 이름과 안전 센터와 통합하는 소스를 변경하기 위해 구성 파일을 변경하는 등 일부 수정이 필요할 수 있습니다. 자세한 내용은 구성 파일 업데이트통합 설정을 참고하세요.

PendingIntent 정보

Android 14 이상에서 기존 설정 앱 안전 센터 통합을 사용한다면 아래 설명된 버그가 수정되었습니다. 이 경우 이 섹션은 읽지 않아도 됩니다.

버그가 없다고 확신하는 경우 설정 앱 config_isSafetyCenterLockScreenPendingIntentFixed의 XML 불리언 리소스 구성 값을 true로 설정하여 안전 센터 내 해결 방법을 사용 중지합니다.

PendingIntent 해결 방법

이 버그는 Intent 인스턴스 추가 항목을 사용하여 열 프래그먼트를 결정하는 설정으로 인해 발생합니다. Intent#equalsIntent 인스턴스 추가 항목을 고려하지 않으므로 톱니바퀴 메뉴 아이콘의 PendingIntent 인스턴스와 해당 항목이 동일하게 간주되어 동일한 UI로 이동합니다(다른 UI로 이동하도록 되어 있어도 마찬가지임). 이 문제는 요청 코드별로 PendingIntent 인스턴스를 구별하여 QPR 출시에서 수정되었습니다. 또는 Intent#setId를 사용하여 구별할 수 있습니다.

내부 안전 소스

일부 안전 센터 소스는 내부용이며 PermissionController 모듈 내 PermissionController 시스템 앱에서 구현됩니다. 이러한 소스는 일반 안전 센터 소스처럼 동작하고 특별하게 처리되지 않습니다. 이러한 소스의 코드는 Android 코드 검색을 통해 사용할 수 있습니다.

이는 주로 개인 정보 보호 신호입니다. 예를 들면 다음과 같습니다.

  • 접근성
  • 사용되지 않는 앱 자동 취소
  • 위치 정보 액세스
  • 알림 리스너
  • 업무 정책 정보