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(numTipIssues: Int): SafetyCenterStatus =
118         SafetyCenterStatus.Builder(
119                 safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
120                 getIcuPluralsString("overall_severity_level_tip_summary", numTipIssues),
121             )
122             .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
123             .build()
124 
125     /**
126      * Returns an information [SafetyCenterStatus] that has "Action(s) taken" as a summary for the
127      * given [numAutomaticIssues].
128      */
129     fun safetyCenterStatusActionsTaken(numAutomaticIssues: Int): SafetyCenterStatus =
130         SafetyCenterStatus.Builder(
131                 safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
132                 getIcuPluralsString(
133                     "overall_severity_level_action_taken_summary",
134                     numAutomaticIssues,
135                 ),
136             )
137             .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
138             .build()
139 
140     /**
141      * Returns the [SafetyCenterStatus] used when the overall status is critical and no scan is in
142      * progress for the given number of alerts.
143      */
144     fun safetyCenterStatusCritical(numAlerts: Int) =
145         SafetyCenterStatus.Builder(
146                 safetyCenterResourcesApk.getStringByName(
147                     "overall_severity_level_critical_safety_warning_title"
148                 ),
149                 getAlertString(numAlerts),
150             )
151             .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
152             .build()
153 
154     /**
155      * Returns a [SafetyCenterEntry] builder with a grey icon (for unknown severity), the summary
156      * generally used for sources of the [SafetyCenterTestConfigs], and a pending intent that
157      * redirects to [TestActivity] for the given source, user id, and title.
158      */
159     fun safetyCenterEntryDefaultBuilder(
160         sourceId: String,
161         userId: Int = UserHandle.myUserId(),
162         title: CharSequence = "OK",
163         pendingIntent: PendingIntent? =
164             safetySourceTestData.createTestActivityRedirectPendingIntent(),
165     ) =
166         SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
167             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
168             .setSummary("OK")
169             .setPendingIntent(pendingIntent)
170             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
171 
172     /**
173      * Returns a [SafetyCenterEntry] with a grey icon (for unknown severity), the summary generally
174      * used for sources of the [SafetyCenterTestConfigs], and a pending intent that redirects to
175      * Safety Center for the given source, user id, and title.
176      */
177     fun safetyCenterEntryDefault(
178         sourceId: String,
179         userId: Int = UserHandle.myUserId(),
180         title: CharSequence = "OK",
181         pendingIntent: PendingIntent? =
182             safetySourceTestData.createTestActivityRedirectPendingIntent(),
183     ) = safetyCenterEntryDefaultBuilder(sourceId, userId, title, pendingIntent).build()
184 
185     /**
186      * Returns a [SafetyCenterEntry] builder with no icon, the summary generally used for sources of
187      * the [SafetyCenterTestConfigs], and a pending intent that redirects to [TestActivity] for the
188      * given source, user id, and title.
189      */
190     fun safetyCenterEntryDefaultStaticBuilder(
191         sourceId: String,
192         userId: Int = UserHandle.myUserId(),
193         title: CharSequence = "OK",
194     ) =
195         SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
196             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
197             .setSummary("OK")
198             .setPendingIntent(
199                 safetySourceTestData.createTestActivityRedirectPendingIntent(explicit = false)
200             )
201             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON)
202 
203     /**
204      * Returns a [SafetyCenterEntry] with a grey icon (for unknown severity), a refresh error
205      * summary, and a pending intent that redirects to [TestActivity] for the given source, user id,
206      * and title.
207      */
208     fun safetyCenterEntryError(sourceId: String) =
209         safetyCenterEntryDefaultBuilder(sourceId).setSummary(getRefreshErrorString(1)).build()
210 
211     /**
212      * Returns a disabled [SafetyCenterEntry] with a grey icon (for unspecified severity), a
213      * standard summary, and a standard title for the given source and pending intent.
214      */
215     fun safetyCenterEntryUnspecified(
216         sourceId: String,
217         pendingIntent: PendingIntent? =
218             safetySourceTestData.createTestActivityRedirectPendingIntent(),
219     ) =
220         SafetyCenterEntry.Builder(entryId(sourceId), "Unspecified title")
221             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
222             .setSummary("Unspecified summary")
223             .setPendingIntent(pendingIntent)
224             .setEnabled(false)
225             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
226             .build()
227 
228     /**
229      * Returns a [SafetyCenterEntry] builder with a green icon (for ok severity), a standard
230      * summary, and a pending intent that redirects to [TestActivity] for the given source, user id,
231      * and title.
232      */
233     fun safetyCenterEntryOkBuilder(
234         sourceId: String,
235         userId: Int = UserHandle.myUserId(),
236         title: CharSequence = "Ok title",
237     ) =
238         SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
239             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_OK)
240             .setSummary("Ok summary")
241             .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent())
242             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
243 
244     /**
245      * Returns a [SafetyCenterEntry] with a green icon (for ok severity), a standard summary, and a
246      * pending intent that redirects to [TestActivity] for the given source, user id, and title.
247      */
248     fun safetyCenterEntryOk(
249         sourceId: String,
250         userId: Int = UserHandle.myUserId(),
251         title: CharSequence = "Ok title",
252     ) = safetyCenterEntryOkBuilder(sourceId, userId, title).build()
253 
254     /**
255      * Returns a [SafetyCenterEntry] with a yellow icon (for recommendation severity), a standard
256      * title, and a pending intent that redirects to [TestActivity] for the given source and
257      * summary.
258      */
259     fun safetyCenterEntryRecommendation(
260         sourceId: String,
261         summary: String = "Recommendation summary",
262     ) =
263         SafetyCenterEntry.Builder(entryId(sourceId), "Recommendation title")
264             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
265             .setSummary(summary)
266             .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent())
267             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
268             .build()
269 
270     /**
271      * Returns a [SafetyCenterEntry] with a red icon (for critical severity), a standard title, a
272      * standard summary, and a pending intent that redirects to [TestActivity] for the given source.
273      */
274     fun safetyCenterEntryCritical(sourceId: String) =
275         SafetyCenterEntry.Builder(entryId(sourceId), "Critical title")
276             .setSeverityLevel(ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING)
277             .setSummary("Critical summary")
278             .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent())
279             .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
280             .build()
281 
282     /**
283      * Returns an information [SafetyCenterIssue] for the given source and user id that is
284      * consistent with information [SafetySourceIssue]s used in [SafetySourceTestData].
285      */
286     fun safetyCenterIssueInformation(
287         sourceId: String,
288         userId: Int = UserHandle.myUserId(),
289         attributionTitle: String? = "OK",
290         groupId: String? = SINGLE_SOURCE_GROUP_ID,
291     ) =
292         SafetyCenterIssue.Builder(
293                 issueId(sourceId, INFORMATION_ISSUE_ID, userId = userId),
294                 "Information issue title",
295                 "Information issue summary",
296             )
297             .setSeverityLevel(ISSUE_SEVERITY_LEVEL_OK)
298             .setShouldConfirmDismissal(false)
299             .setActions(
300                 listOf(
301                     SafetyCenterIssue.Action.Builder(
302                             issueActionId(
303                                 sourceId,
304                                 INFORMATION_ISSUE_ID,
305                                 INFORMATION_ISSUE_ACTION_ID,
306                                 userId,
307                             ),
308                             "Review",
309                             safetySourceTestData.createTestActivityRedirectPendingIntent(),
310                         )
311                         .build()
312                 )
313             )
314             .apply {
315                 if (SdkLevel.isAtLeastU()) {
316                     setAttributionTitle(attributionTitle)
317                     setGroupId(groupId)
318                 }
319             }
320             .build()
321 
322     /**
323      * Returns a recommendation [SafetyCenterIssue] for the given source and user id that is
324      * consistent with recommendation [SafetySourceIssue]s used in [SafetySourceTestData].
325      */
safetyCenterIssueRecommendationnull326     fun safetyCenterIssueRecommendation(
327         sourceId: String,
328         userId: Int = UserHandle.myUserId(),
329         attributionTitle: String? = "OK",
330         groupId: String? = SINGLE_SOURCE_GROUP_ID,
331         confirmationDialog: Boolean = false,
332     ) =
333         SafetyCenterIssue.Builder(
334                 issueId(sourceId, RECOMMENDATION_ISSUE_ID, userId = userId),
335                 "Recommendation issue title",
336                 "Recommendation issue summary",
337             )
338             .setSeverityLevel(ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
339             .setActions(
340                 listOf(
341                     SafetyCenterIssue.Action.Builder(
342                             issueActionId(
343                                 sourceId,
344                                 RECOMMENDATION_ISSUE_ID,
345                                 RECOMMENDATION_ISSUE_ACTION_ID,
346                                 userId,
347                             ),
348                             "See issue",
349                             safetySourceTestData.createTestActivityRedirectPendingIntent(),
350                         )
351                         .apply {
352                             if (confirmationDialog && SdkLevel.isAtLeastU()) {
353                                 setConfirmationDialogDetails(
354                                     SafetyCenterIssue.Action.ConfirmationDialogDetails(
355                                         "Confirmation title",
356                                         "Confirmation text",
357                                         "Confirmation yes",
358                                         "Confirmation no",
359                                     )
360                                 )
361                             }
362                         }
363                         .build()
364                 )
365             )
<lambda>null366             .apply {
367                 if (SdkLevel.isAtLeastU()) {
368                     setAttributionTitle(attributionTitle)
369                     setGroupId(groupId)
370                 }
371             }
372             .build()
373 
374     /**
375      * Returns a critical [SafetyCenterIssue] for the given source and user id that is consistent
376      * with critical [SafetySourceIssue]s used in [SafetySourceTestData].
377      */
safetyCenterIssueCriticalnull378     fun safetyCenterIssueCritical(
379         sourceId: String,
380         isActionInFlight: Boolean = false,
381         userId: Int = UserHandle.myUserId(),
382         attributionTitle: String? = "OK",
383         groupId: String? = SINGLE_SOURCE_GROUP_ID,
384     ) =
385         SafetyCenterIssue.Builder(
386                 issueId(sourceId, CRITICAL_ISSUE_ID, userId = userId),
387                 "Critical issue title",
388                 "Critical issue summary",
389             )
390             .setSeverityLevel(ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
391             .setActions(
392                 listOf(
393                     SafetyCenterIssue.Action.Builder(
394                             issueActionId(
395                                 sourceId,
396                                 CRITICAL_ISSUE_ID,
397                                 CRITICAL_ISSUE_ACTION_ID,
398                                 userId,
399                             ),
400                             "Solve issue",
401                             safetySourceTestData.criticalIssueActionPendingIntent,
402                         )
403                         .setWillResolve(true)
404                         .setIsInFlight(isActionInFlight)
405                         .build()
406                 )
407             )
408             .apply {
409                 if (SdkLevel.isAtLeastU()) {
410                     setAttributionTitle(attributionTitle)
411                     setGroupId(groupId)
412                 }
413             }
414             .build()
415 
416     /**
417      * Returns the [overall_severity_n_alerts_summary] string formatted for the given number of
418      * alerts.
419      */
getAlertStringnull420     fun getAlertString(numberOfAlerts: Int): String =
421         getIcuPluralsString("overall_severity_n_alerts_summary", numberOfAlerts)
422 
423     /** Returns the [refresh_error] string formatted for the given number of error entries. */
424     fun getRefreshErrorString(numberOfErrorEntries: Int): String =
425         getIcuPluralsString("refresh_error", numberOfErrorEntries)
426 
427     private fun getIcuPluralsString(name: String, count: Int, vararg formatArgs: Any): String {
428         val messageFormat =
429             MessageFormat(
430                 safetyCenterResourcesApk.getStringByName(name, formatArgs),
431                 Locale.getDefault(),
432             )
433         val arguments = ArrayMap<String, Any>()
434         arguments["count"] = count
435         return messageFormat.format(arguments)
436     }
437 
438     companion object {
439         /** The default [SafetyCenterData] returned by the Safety Center APIs. */
440         val DEFAULT: SafetyCenterData =
441             SafetyCenterData(
442                 SafetyCenterStatus.Builder("", "")
443                     .setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
444                     .build(),
445                 emptyList(),
446                 emptyList(),
447                 emptyList(),
448             )
449 
450         /** Creates an ID for a Safety Center entry. */
entryIdnull451         fun entryId(sourceId: String, userId: Int = UserHandle.myUserId()) =
452             SafetyCenterIds.encodeToString(
453                 SafetyCenterEntryId.newBuilder()
454                     .setSafetySourceId(sourceId)
455                     .setUserId(userId)
456                     .build()
457             )
458 
459         /** Creates an ID for a Safety Center issue. */
460         fun issueId(
461             sourceId: String,
462             sourceIssueId: String,
463             issueTypeId: String = ISSUE_TYPE_ID,
464             userId: Int = UserHandle.myUserId(),
465         ) =
466             SafetyCenterIds.encodeToString(
467                 SafetyCenterIssueId.newBuilder()
468                     .setSafetyCenterIssueKey(
469                         SafetyCenterIssueKey.newBuilder()
470                             .setSafetySourceId(sourceId)
471                             .setSafetySourceIssueId(sourceIssueId)
472                             .setUserId(userId)
473                             .build()
474                     )
475                     .setIssueTypeId(issueTypeId)
476                     .build()
477             )
478 
479         /** Creates an ID for a Safety Center issue action. */
480         fun issueActionId(
481             sourceId: String,
482             sourceIssueId: String,
483             sourceIssueActionId: String,
484             userId: Int = UserHandle.myUserId(),
485         ) =
486             SafetyCenterIds.encodeToString(
487                 SafetyCenterIssueActionId.newBuilder()
488                     .setSafetyCenterIssueKey(
489                         SafetyCenterIssueKey.newBuilder()
490                             .setSafetySourceId(sourceId)
491                             .setSafetySourceIssueId(sourceIssueId)
492                             .setUserId(userId)
493                             .build()
494                     )
495                     .setSafetySourceIssueActionId(sourceIssueActionId)
496                     .build()
497             )
498 
499         /**
500          * On U+, returns a new [SafetyCenterData] with the dismissed issues set. Prior to U,
501          * returns the passed in [SafetyCenterData].
502          */
503         fun SafetyCenterData.withDismissedIssuesIfAtLeastU(
504             dismissedIssues: List<SafetyCenterIssue>
505         ): SafetyCenterData =
506             if (SdkLevel.isAtLeastU()) {
507                 copy(dismissedIssues = dismissedIssues)
508             } else this
509 
510         /** Returns a [SafetyCenterData] without extras. */
SafetyCenterDatanull511         fun SafetyCenterData.withoutExtras() =
512             if (SdkLevel.isAtLeastU()) {
513                 SafetyCenterData.Builder(this).clearExtras().build()
514             } else this
515 
516         /**
517          * On U+, returns a new [SafetyCenterData] with [SafetyCenterIssue]s having the
518          * [attributionTitle]. Prior to U, returns the passed in [SafetyCenterData].
519          */
SafetyCenterDatanull520         fun SafetyCenterData.withAttributionTitleInIssuesIfAtLeastU(
521             attributionTitle: String?
522         ): SafetyCenterData {
523             return if (SdkLevel.isAtLeastU()) {
524                 val issuesWithAttributionTitle =
525                     this.issues.map {
526                         SafetyCenterIssue.Builder(it).setAttributionTitle(attributionTitle).build()
527                     }
528                 copy(issues = issuesWithAttributionTitle)
529             } else this
530         }
531 
532         /**
533          * On U+, returns a new [SafetyCenterData] with the extras set. Prior to U, returns the
534          * passed in [SafetyCenterData].
535          */
SafetyCenterDatanull536         fun SafetyCenterData.withExtrasIfAtLeastU(extras: Bundle): SafetyCenterData =
537             if (SdkLevel.isAtLeastU()) {
538                 copy(extras = extras)
539             } else this
540 
541         @RequiresApi(UPSIDE_DOWN_CAKE)
SafetyCenterDatanull542         private fun SafetyCenterData.copy(
543             issues: List<SafetyCenterIssue> = this.issues,
544             dismissedIssues: List<SafetyCenterIssue> = this.dismissedIssues,
545             extras: Bundle = this.extras,
546         ): SafetyCenterData =
547             SafetyCenterData.Builder(status)
548                 .apply {
549                     issues.forEach(::addIssue)
550                     entriesOrGroups.forEach(::addEntryOrGroup)
551                     staticEntryGroups.forEach(::addStaticEntryGroup)
552                     dismissedIssues.forEach(::addDismissedIssue)
553                 }
554                 .setExtras(extras)
555                 .build()
556     }
557 }
558