セーフティ センターとやり取りする

セーフティ センターにリダイレクトする

どのアプリでも 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 およびセーフティ センター 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 設定によって適用される追加の要件を満たしている必要があります。例: ソースが issue-only の場合、SafetySourceStatus インスタンスを提供することはできません。

SafetySourceStatus

  • 必須の CharSequence タイトル
  • 必須の CharSequence サマリー
  • 必須の重大度レベル
  • 省略可能な PendingIntent インスタンス。ユーザーを正しいページにリダイレクトするためのものです(デフォルトでは intentAction を、存在する場合の config から使用します)
  • 省略可能な 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 問題タイプ識別子。これは問題識別子に似ていますが、一意である必要はなく、ロギングに使用されます
  • 省略可能な String。重複除去 ID 用。これにより、異なるソースから同じ SafetySourceIssue を送信できます。同じ deduplicationGroup を持つこと想定し UI に 1 回のみ表示されます(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 インスタンスのリストには、1 つまたは 2 つの Action 要素が含まれている必要があります。アクション可能性が ISSUE_ACTIONABILITY_MANUAL ではない場合、ゼロの Action は許容されます。
    • OnDismiss PendingIntentActivity インスタンスを開くことはできません
    • API 設定によって適用される追加の要件

データは、特定のイベントの発生時に、セーフティ センターに提供されます。したがって、何が要因となってソースから SafetySourceDataSafetyEvent インスタンスが提供されるかを指定する必要があります。

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 を使用することが許可されます。
  • 省略可能な String 識別子。更新ブロードキャスト ID 用。
  • 省略可能な String 識別子。解決される SafetySourceIssue インスタンス用。
  • 省略可能な String 識別子。解決される SafetySourceIssue.Action インスタンス用。
  • 不変条件:
    • 更新ブロードキャスト ID は、タイプが SAFETY_EVENT_TYPE_REFRESH_REQUESTED の場合に提供する必要があります
    • 問題およびアクション ID は、タイプが SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED または SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED の場合に提供する必要があります

以下の例は、どのようにソースからセーフティ センターにデータが提供されるかを示しています(この場合は、1 つの警告カードとともにエントリを提供しています)。

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.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>

問題の却下に応答する

SafetySourceIssue インスタンスが却下されたときにトリガーできる PendingIntent インスタンスを指定できます。セーフティ センターは、これらの問題の却下を次のように処理します。

  • ソースが問題をプッシュした場合、ユーザーは却下ボタン(警告カード上の X ボタン)をタップしてセーフティ センター画面上で問題を却下できます。
  • ユーザーが問題を却下すると、問題が解決されない場合は、UI に再度表示されません。
  • ディスクでの持続的な却下は、デバイスの再起動時もそのままになります。
  • セーフティ センターのソースが問題の提供を停止し、後から再度問題を提供すると、問題が再表示されます。これは、ユーザーが警告を見て却下し、その後に問題を緩和するはずのアクションを実行しても、同じような問題の要因になる何かを再度行うという状況に対応するためのものです。この時点で、警告カードは再表示されます。
  • 黄色と赤色の警告カードは、ユーザーが複数回却下しない限り 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" が構成ファイル内のソースに対して設定されると、以下のようになります。

  • ユーザー(プロファイルの親)用の UI エントリと、その関連付けられたすべての管理対象プロファイル(titleForWork インスタンスを使用する)があります。
  • プロファイルの親と、その関連付けられたすべての管理対象プロファイルに対して更新または再スキャン シグナルが送信されます。関連付けられたレシーバは、それぞれのプロファイルに応じて開始され、レシーバまたはアプリが 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 は、内部使用が目的のため、このガイドでは詳しく説明していません。ただし、今後は OEM が独自の UI を構築する際の基盤になるよう一部の内部 API を拡張する可能性があるため、このガイドをアップデートして、その使用方法についてガイダンス情報を提供していきます。

権限

  • MANAGE_SAFETY_CENTER
    • internal|installer|role
    • 内部セーフティ センター API 用に使用されます
    • PermissionController およびシェルのみに付与されます

設定アプリ

セーフティ センターのリダイレクト

デフォルトでは、セーフティ センターには、新しいセキュリティとプライバシー エントリが追加された設定アプリを通じてアクセスします。異なる設定アプリを使用しているか、設定アプリを変更している場合は、セーフティ センターへのアクセス方法のカスタマイズが必要になる可能性があります。

セーフティ センターが有効になっている場合:

  • 以前のプライバシー エントリは、非表示のコードです
  • 以前のセキュリティ エントリは、非表示のコードです
  • 新しいセキュリティとプライバシー エントリは、追加された コードです
  • 新しいセキュリティとプライバシー エントリは、セーフティ センターのコードにリダイレクトされます
  • インテントのアクション android.settings.PRIVACY_SETTINGS および android.settings.SECURITY_SETTINGS は、セーフティ センターを開くためにリダイレクトされます(コード: セキュリティプライバシー

高度なセキュリティおよびプライバシー ページ

設定アプリは、More security settings および More privacy settings タイトルの下に追加の設定が含まれていて、セーフティ センターから使用できます。

安全性ソース

セーフティ センターは、次のように設定アプリが提供する安全性ソースの特定のセットと統合されています。

  • ロック画面安全性ソースでは、ロック画面がパスコード(または他のセキュリティ)とともにセットアップされていることを検証し、ユーザーの個人情報が外部アクセスから確実に保護されるようにします。
  • 生体認証安全性ソース(デフォルトでは非表示)が、フィンガープリントまたは顔認証センサーと統合するために表示されます。

こうしたセーフティ センターのソースのソースコードは、Android ソースコード検索を通じてアクセスできます。 設定アプリを変更していない(パッケージ名、ソースコード、またはロック画面と生体認証システムを扱うソースコードに変更を加えていない)場合は、この統合がすぐに機能します。それ以外の場合は、設定アプリのパッケージ名を変更するための構成ファイルの変更に加えて、セーフティ センターと統合するソース、統合の設定などが必要になる可能性があります。詳しくは、構成ファイルをアップデートするおよび統合設定をご覧ください。

PendingIntent について

Android 14 以降で既存の設定アプリとセーフティ センターの統合に依存する場合、以下で説明するバグは修正されています。その場合は、このセクションを読む必要はありません。

バグが存在しないことが確実な場合は、設定アプリ内の XML ブール値リソース構成値 config_isSafetyCenterLockScreenPendingIntentFixedtrue に設定して、セーフティ センター内の回避策をオフにしてください。

PendingIntent の回避策

このバグは、どのフラグメントを開くかを決定するために Intent インスタンス エクストラを使用する設定によって生じます。Intent#equalsIntent インスタンス エクストラを考慮しないため、歯車メニュー アイコンとエントリ用の PendingIntent インスタンスは、同等と見なされ、同じ UI に移動します(異なる UI に移動することが意図されている場合も)。この問題は、QPR リリースでは、リクエスト コードによって PendingIntent インスタンスを区別することで修正されています。または、これは Intent#setId を使用して区別されています。

内部安全性ソース

一部のセーフティ センターのソースは内部的に使用され、PermissionController モジュール内の PermissionController システムアプリで実装されています。これらのソースは、通常のセーフティ センターのソースのように動作し、特殊な扱いを受けません。これらのソースのコードは、Android ソースコード検索を通じて使用可能です。

これらは次のように主にプライバシー シグナルです。

  • ユーザー補助
  • 未使用のアプリの自動取り消し
  • 位置情報へのアクセス
  • 通知リスナー
  • 仕事に関するポリシーの情報