1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.safetycenter.testing
18 
19 import android.app.PendingIntent
20 import android.content.Context
21 import android.icu.text.MessageFormat
22 import android.os.Build.VERSION_CODES.TIRAMISU
23 import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
24 import android.os.Bundle
25 import android.os.UserHandle
26 import android.safetycenter.SafetyCenterData
27 import android.safetycenter.SafetyCenterEntry
28 import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING
29 import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK
30 import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION
31 import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN
32 import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED
33 import android.safetycenter.SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON
34 import android.safetycenter.SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION
35 import android.safetycenter.SafetyCenterIssue
36 import android.safetycenter.SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING
37 import android.safetycenter.SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK
38 import android.safetycenter.SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION
39 import android.safetycenter.SafetyCenterStatus
40 import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
41 import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK
42 import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
43 import android.util.ArrayMap
44 import androidx.annotation.RequiresApi
45 import com.android.modules.utils.build.SdkLevel
46 import com.android.safetycenter.internaldata.SafetyCenterEntryId
47 import com.android.safetycenter.internaldata.SafetyCenterIds
48 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId
49 import com.android.safetycenter.internaldata.SafetyCenterIssueId
50 import com.android.safetycenter.internaldata.SafetyCenterIssueKey
51 import com.android.safetycenter.resources.SafetyCenterResourcesApk
52 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_GROUP_ID
53 import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ACTION_ID
54 import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID
55 import com.android.safetycenter.testing.SafetySourceTestData.Companion.INFORMATION_ISSUE_ACTION_ID
56 import com.android.safetycenter.testing.SafetySourceTestData.Companion.INFORMATION_ISSUE_ID
57 import com.android.safetycenter.testing.SafetySourceTestData.Companion.ISSUE_TYPE_ID
58 import com.android.safetycenter.testing.SafetySourceTestData.Companion.RECOMMENDATION_ISSUE_ACTION_ID
59 import com.android.safetycenter.testing.SafetySourceTestData.Companion.RECOMMENDATION_ISSUE_ID
60 import java.util.Locale
61 
62 /**
63  * A class that provides [SafetyCenterData] objects and associated constants to facilitate asserting
64  * on specific Safety Center states in SafetyCenter for testing.
65  */
66 @RequiresApi(TIRAMISU)
67 class SafetyCenterTestData(context: Context) {
68 
69     private val safetyCenterResourcesApk = SafetyCenterResourcesApk.forTests(context)
70     private val safetySourceTestData = SafetySourceTestData(context)
71 
72     /**
73      * The [SafetyCenterStatus] used when the overall status is unknown and no scan is in progress.
74      */
75     val safetyCenterStatusUnknown: SafetyCenterStatus
76         get() =
77             SafetyCenterStatus.Builder(
78                     safetyCenterResourcesApk.getStringByName(
79                         "overall_severity_level_ok_review_title"
80                     ),
81                     safetyCenterResourcesApk.getStringByName(
82                         "overall_severity_level_ok_review_summary"
83                     )
84                 )
85                 .setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
86                 .build()
87 
88     /**
89      * Returns a [SafetyCenterStatus] with one alert and the given [statusResource] and
90      * [overallSeverityLevel].
91      */
safetyCenterStatusOneAlertnull92     fun safetyCenterStatusOneAlert(
93         statusResource: String,
94         overallSeverityLevel: Int
95     ): SafetyCenterStatus = safetyCenterStatusNAlerts(statusResource, overallSeverityLevel, 1)
96 
97     /**
98      * Returns a [SafetyCenterStatus] with [numAlerts] and the given [statusResource] and
99      * [overallSeverityLevel].
100      */
101     fun safetyCenterStatusNAlerts(
102         statusResource: String,
103         overallSeverityLevel: Int,
104         numAlerts: Int,
105     ): SafetyCenterStatus =
106         SafetyCenterStatus.Builder(
107                 safetyCenterResourcesApk.getStringByName(statusResource),
108                 getAlertString(numAlerts)
109             )
110             .setSeverityLevel(overallSeverityLevel)
111             .build()
112 
113     /**
114      * Returns an information [SafetyCenterStatus] that has "Tip(s) available" as a summary for the
115      * given [numTipIssues].
116      */
117     fun safetyCenterStatusTips(
118         numTipIssues: Int,
119     ): SafetyCenterStatus =
120         SafetyCenterStatus.Builder(
121                 safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
122                 getIcuPluralsString("overall_severity_level_tip_summary", numTipIssues)
123             )
124             .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
125             .build()
126 
127     /**
128      * Returns an information [SafetyCenterStatus] that has "Action(s) taken" as a summary for the
129      * given [numAutomaticIssues].
130      */
131     fun safetyCenterStatusActionsTaken(
132         numAutomaticIssues: Int,
133     ): SafetyCenterStatus =
134         SafetyCenterStatus.Builder(
135                 safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
136                 getIcuPluralsString(
137                     "overall_severity_level_action_taken_summary",
138                     numAutomaticIssues
139                 )
140             )
141             .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
142             .build()
143 
144     /**
145      * Returns the [SafetyCenterStatus] used when the overall status is critical and no scan is in
146      * progress for the given number of alerts.
147      */
148     fun safetyCenterStatusCritical(numAlerts: Int) =
149         SafetyCenterStatus.Builder(
150                 safetyCenterResourcesApk.getStringByName(
151                     "overall_severity_level_critical_safety_warning_title"
152                 ),
153                 getAlertString(numAlerts)
154             )
155             .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
156             .build()
157 
158     /**
159      * Returns a [SafetyCenterEntry] builder with a grey icon (for unknown severity), the summary
160      * generally used for sources of the [SafetyCenterTestConfigs], and a pending intent that
161      * redirects to [TestActivity] for the given source, user id, and title.
162      */
163     fun safetyCenterEntryDefaultBuilder(
164         sourceId: String,
165         userId: Int = UserHandle.myUserId(),
166         title: CharSequence = "OK",
167         pendingIntent: PendingIntent? =
168             safetySourceTestData.createTestActivityRedirectPendingIntent()
169     ) =
170         SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
171             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
172             .setSummary("OK")
173             .setPendingIntent(pendingIntent)
174             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
175 
176     /**
177      * Returns a [SafetyCenterEntry] with a grey icon (for unknown severity), the summary generally
178      * used for sources of the [SafetyCenterTestConfigs], and a pending intent that redirects to
179      * Safety Center for the given source, user id, and title.
180      */
181     fun safetyCenterEntryDefault(
182         sourceId: String,
183         userId: Int = UserHandle.myUserId(),
184         title: CharSequence = "OK",
185         pendingIntent: PendingIntent? =
186             safetySourceTestData.createTestActivityRedirectPendingIntent()
187     ) = safetyCenterEntryDefaultBuilder(sourceId, userId, title, pendingIntent).build()
188 
189     /**
190      * Returns a [SafetyCenterEntry] builder with no icon, the summary generally used for sources of
191      * the [SafetyCenterTestConfigs], and a pending intent that redirects to [TestActivity] for the
192      * given source, user id, and title.
193      */
194     fun safetyCenterEntryDefaultStaticBuilder(
195         sourceId: String,
196         userId: Int = UserHandle.myUserId(),
197         title: CharSequence = "OK"
198     ) =
199         SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
200             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
201             .setSummary("OK")
202             .setPendingIntent(
203                 safetySourceTestData.createTestActivityRedirectPendingIntent(explicit = false)
204             )
205             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON)
206 
207     /**
208      * Returns a [SafetyCenterEntry] with a grey icon (for unknown severity), a refresh error
209      * summary, and a pending intent that redirects to [TestActivity] for the given source, user id,
210      * and title.
211      */
212     fun safetyCenterEntryError(sourceId: String) =
213         safetyCenterEntryDefaultBuilder(sourceId).setSummary(getRefreshErrorString(1)).build()
214 
215     /**
216      * Returns a disabled [SafetyCenterEntry] with a grey icon (for unspecified severity), a
217      * standard summary, and a standard title for the given source and pending intent.
218      */
219     fun safetyCenterEntryUnspecified(
220         sourceId: String,
221         pendingIntent: PendingIntent? =
222             safetySourceTestData.createTestActivityRedirectPendingIntent()
223     ) =
224         SafetyCenterEntry.Builder(entryId(sourceId), "Unspecified title")
225             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
226             .setSummary("Unspecified summary")
227             .setPendingIntent(pendingIntent)
228             .setEnabled(false)
229             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
230             .build()
231 
232     /**
233      * Returns a [SafetyCenterEntry] builder with a green icon (for ok severity), a standard
234      * summary, and a pending intent that redirects to [TestActivity] for the given source, user id,
235      * and title.
236      */
237     fun safetyCenterEntryOkBuilder(
238         sourceId: String,
239         userId: Int = UserHandle.myUserId(),
240         title: CharSequence = "Ok title"
241     ) =
242         SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
243             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_OK)
244             .setSummary("Ok summary")
245             .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent())
246             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
247 
248     /**
249      * Returns a [SafetyCenterEntry] with a green icon (for ok severity), a standard summary, and a
250      * pending intent that redirects to [TestActivity] for the given source, user id, and title.
251      */
252     fun safetyCenterEntryOk(
253         sourceId: String,
254         userId: Int = UserHandle.myUserId(),
255         title: CharSequence = "Ok title"
256     ) = safetyCenterEntryOkBuilder(sourceId, userId, title).build()
257 
258     /**
259      * Returns a [SafetyCenterEntry] with a yellow icon (for recommendation severity), a standard
260      * title, and a pending intent that redirects to [TestActivity] for the given source and
261      * summary.
262      */
263     fun safetyCenterEntryRecommendation(
264         sourceId: String,
265         summary: String = "Recommendation summary"
266     ) =
267         SafetyCenterEntry.Builder(entryId(sourceId), "Recommendation title")
268             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
269             .setSummary(summary)
270             .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent())
271             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
272             .build()
273 
274     /**
275      * Returns a [SafetyCenterEntry] with a red icon (for critical severity), a standard title, a
276      * standard summary, and a pending intent that redirects to [TestActivity] for the given source.
277      */
278     fun safetyCenterEntryCritical(sourceId: String) =
279         SafetyCenterEntry.Builder(entryId(sourceId), "Critical title")
280             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING)
281             .setSummary("Critical summary")
282             .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent())
283             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
284             .build()
285 
286     /**
287      * Returns an information [SafetyCenterIssue] for the given source and user id that is
288      * consistent with information [SafetySourceIssue]s used in [SafetySourceTestData].
289      */
290     fun safetyCenterIssueInformation(
291         sourceId: String,
292         userId: Int = UserHandle.myUserId(),
293         attributionTitle: String? = "OK",
294         groupId: String? = SINGLE_SOURCE_GROUP_ID
295     ) =
296         SafetyCenterIssue.Builder(
297                 issueId(sourceId, INFORMATION_ISSUE_ID, userId = userId),
298                 "Information issue title",
299                 "Information issue summary"
300             )
301             .setSeverityLevel(ISSUE_SEVERITY_LEVEL_OK)
302             .setShouldConfirmDismissal(false)
303             .setActions(
304                 listOf(
305                     SafetyCenterIssue.Action.Builder(
306                             issueActionId(
307                                 sourceId,
308                                 INFORMATION_ISSUE_ID,
309                                 INFORMATION_ISSUE_ACTION_ID,
310                                 userId
311                             ),
312                             "Review",
313                             safetySourceTestData.createTestActivityRedirectPendingIntent()
314                         )
315                         .build()
316                 )
317             )
318             .apply {
319                 if (SdkLevel.isAtLeastU()) {
320                     setAttributionTitle(attributionTitle)
321                     setGroupId(groupId)
322                 }
323             }
324             .build()
325 
326     /**
327      * Returns a recommendation [SafetyCenterIssue] for the given source and user id that is
328      * consistent with recommendation [SafetySourceIssue]s used in [SafetySourceTestData].
329      */
safetyCenterIssueRecommendationnull330     fun safetyCenterIssueRecommendation(
331         sourceId: String,
332         userId: Int = UserHandle.myUserId(),
333         attributionTitle: String? = "OK",
334         groupId: String? = SINGLE_SOURCE_GROUP_ID,
335         confirmationDialog: Boolean = false
336     ) =
337         SafetyCenterIssue.Builder(
338                 issueId(sourceId, RECOMMENDATION_ISSUE_ID, userId = userId),
339                 "Recommendation issue title",
340                 "Recommendation issue summary"
341             )
342             .setSeverityLevel(ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
343             .setActions(
344                 listOf(
345                     SafetyCenterIssue.Action.Builder(
346                             issueActionId(
347                                 sourceId,
348                                 RECOMMENDATION_ISSUE_ID,
349                                 RECOMMENDATION_ISSUE_ACTION_ID,
350                                 userId
351                             ),
352                             "See issue",
353                             safetySourceTestData.createTestActivityRedirectPendingIntent()
354                         )
355                         .apply {
356                             if (confirmationDialog && SdkLevel.isAtLeastU()) {
357                                 setConfirmationDialogDetails(
358                                     SafetyCenterIssue.Action.ConfirmationDialogDetails(
359                                         "Confirmation title",
360                                         "Confirmation text",
361                                         "Confirmation yes",
362                                         "Confirmation no"
363                                     )
364                                 )
365                             }
366                         }
367                         .build()
368                 )
369             )
<lambda>null370             .apply {
371                 if (SdkLevel.isAtLeastU()) {
372                     setAttributionTitle(attributionTitle)
373                     setGroupId(groupId)
374                 }
375             }
376             .build()
377 
378     /**
379      * Returns a critical [SafetyCenterIssue] for the given source and user id that is consistent
380      * with critical [SafetySourceIssue]s used in [SafetySourceTestData].
381      */
safetyCenterIssueCriticalnull382     fun safetyCenterIssueCritical(
383         sourceId: String,
384         isActionInFlight: Boolean = false,
385         userId: Int = UserHandle.myUserId(),
386         attributionTitle: String? = "OK",
387         groupId: String? = SINGLE_SOURCE_GROUP_ID
388     ) =
389         SafetyCenterIssue.Builder(
390                 issueId(sourceId, CRITICAL_ISSUE_ID, userId = userId),
391                 "Critical issue title",
392                 "Critical issue summary"
393             )
394             .setSeverityLevel(ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
395             .setActions(
396                 listOf(
397                     SafetyCenterIssue.Action.Builder(
398                             issueActionId(
399                                 sourceId,
400                                 CRITICAL_ISSUE_ID,
401                                 CRITICAL_ISSUE_ACTION_ID,
402                                 userId
403                             ),
404                             "Solve issue",
405                             safetySourceTestData.criticalIssueActionPendingIntent
406                         )
407                         .setWillResolve(true)
408                         .setIsInFlight(isActionInFlight)
409                         .build()
410                 )
411             )
412             .apply {
413                 if (SdkLevel.isAtLeastU()) {
414                     setAttributionTitle(attributionTitle)
415                     setGroupId(groupId)
416                 }
417             }
418             .build()
419 
420     /**
421      * Returns the [overall_severity_n_alerts_summary] string formatted for the given number of
422      * alerts.
423      */
getAlertStringnull424     fun getAlertString(numberOfAlerts: Int): String =
425         getIcuPluralsString("overall_severity_n_alerts_summary", numberOfAlerts)
426 
427     /** Returns the [refresh_error] string formatted for the given number of error entries. */
428     fun getRefreshErrorString(numberOfErrorEntries: Int): String =
429         getIcuPluralsString("refresh_error", numberOfErrorEntries)
430 
431     private fun getIcuPluralsString(name: String, count: Int, vararg formatArgs: Any): String {
432         val messageFormat =
433             MessageFormat(
434                 safetyCenterResourcesApk.getStringByName(name, formatArgs),
435                 Locale.getDefault()
436             )
437         val arguments = ArrayMap<String, Any>()
438         arguments["count"] = count
439         return messageFormat.format(arguments)
440     }
441 
442     companion object {
443         /** The default [SafetyCenterData] returned by the Safety Center APIs. */
444         val DEFAULT: SafetyCenterData =
445             SafetyCenterData(
446                 SafetyCenterStatus.Builder("", "")
447                     .setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
448                     .build(),
449                 emptyList(),
450                 emptyList(),
451                 emptyList()
452             )
453 
454         /** Creates an ID for a Safety Center entry. */
entryIdnull455         fun entryId(sourceId: String, userId: Int = UserHandle.myUserId()) =
456             SafetyCenterIds.encodeToString(
457                 SafetyCenterEntryId.newBuilder()
458                     .setSafetySourceId(sourceId)
459                     .setUserId(userId)
460                     .build()
461             )
462 
463         /** Creates an ID for a Safety Center issue. */
464         fun issueId(
465             sourceId: String,
466             sourceIssueId: String,
467             issueTypeId: String = ISSUE_TYPE_ID,
468             userId: Int = UserHandle.myUserId()
469         ) =
470             SafetyCenterIds.encodeToString(
471                 SafetyCenterIssueId.newBuilder()
472                     .setSafetyCenterIssueKey(
473                         SafetyCenterIssueKey.newBuilder()
474                             .setSafetySourceId(sourceId)
475                             .setSafetySourceIssueId(sourceIssueId)
476                             .setUserId(userId)
477                             .build()
478                     )
479                     .setIssueTypeId(issueTypeId)
480                     .build()
481             )
482 
483         /** Creates an ID for a Safety Center issue action. */
484         fun issueActionId(
485             sourceId: String,
486             sourceIssueId: String,
487             sourceIssueActionId: String,
488             userId: Int = UserHandle.myUserId()
489         ) =
490             SafetyCenterIds.encodeToString(
491                 SafetyCenterIssueActionId.newBuilder()
492                     .setSafetyCenterIssueKey(
493                         SafetyCenterIssueKey.newBuilder()
494                             .setSafetySourceId(sourceId)
495                             .setSafetySourceIssueId(sourceIssueId)
496                             .setUserId(userId)
497                             .build()
498                     )
499                     .setSafetySourceIssueActionId(sourceIssueActionId)
500                     .build()
501             )
502 
503         /**
504          * On U+, returns a new [SafetyCenterData] with the dismissed issues set. Prior to U,
505          * returns the passed in [SafetyCenterData].
506          */
507         fun SafetyCenterData.withDismissedIssuesIfAtLeastU(
508             dismissedIssues: List<SafetyCenterIssue>
509         ): SafetyCenterData =
510             if (SdkLevel.isAtLeastU()) {
511                 copy(dismissedIssues = dismissedIssues)
512             } else this
513 
514         /** Returns a [SafetyCenterData] without extras. */
SafetyCenterDatanull515         fun SafetyCenterData.withoutExtras() =
516             if (SdkLevel.isAtLeastU()) {
517                 SafetyCenterData.Builder(this).clearExtras().build()
518             } else this
519 
520         /**
521          * On U+, returns a new [SafetyCenterData] with [SafetyCenterIssue]s having the
522          * [attributionTitle]. Prior to U, returns the passed in [SafetyCenterData].
523          */
SafetyCenterDatanull524         fun SafetyCenterData.withAttributionTitleInIssuesIfAtLeastU(
525             attributionTitle: String?
526         ): SafetyCenterData {
527             return if (SdkLevel.isAtLeastU()) {
528                 val issuesWithAttributionTitle =
529                     this.issues.map {
530                         SafetyCenterIssue.Builder(it).setAttributionTitle(attributionTitle).build()
531                     }
532                 copy(issues = issuesWithAttributionTitle)
533             } else this
534         }
535 
536         /**
537          * On U+, returns a new [SafetyCenterData] with the extras set. Prior to U, returns the
538          * passed in [SafetyCenterData].
539          */
SafetyCenterDatanull540         fun SafetyCenterData.withExtrasIfAtLeastU(extras: Bundle): SafetyCenterData =
541             if (SdkLevel.isAtLeastU()) {
542                 copy(extras = extras)
543             } else this
544 
545         @RequiresApi(UPSIDE_DOWN_CAKE)
SafetyCenterDatanull546         private fun SafetyCenterData.copy(
547             issues: List<SafetyCenterIssue> = this.issues,
548             dismissedIssues: List<SafetyCenterIssue> = this.dismissedIssues,
549             extras: Bundle = this.extras
550         ): SafetyCenterData =
551             SafetyCenterData.Builder(status)
552                 .apply {
553                     issues.forEach(::addIssue)
554                     entriesOrGroups.forEach(::addEntryOrGroup)
555                     staticEntryGroups.forEach(::addStaticEntryGroup)
556                     dismissedIssues.forEach(::addDismissedIssue)
557                 }
558                 .setExtras(extras)
559                 .build()
560     }
561 }
562