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.content.Intent
22 import android.content.Intent.FLAG_RECEIVER_FOREGROUND
23 import android.os.Build.VERSION_CODES.TIRAMISU
24 import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
25 import android.safetycenter.SafetyEvent
26 import android.safetycenter.SafetySourceData
27 import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
28 import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION
29 import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION
30 import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED
31 import android.safetycenter.SafetySourceIssue
32 import android.safetycenter.SafetySourceIssue.Action
33 import android.safetycenter.SafetySourceIssue.Action.ConfirmationDialogDetails
34 import android.safetycenter.SafetySourceStatus
35 import android.safetycenter.SafetySourceStatus.IconAction
36 import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_GEAR
37 import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_INFO
38 import androidx.annotation.RequiresApi
39 import com.android.modules.utils.build.SdkLevel
40 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ACTION_TEST_ACTIVITY
41 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ACTION_TEST_ACTIVITY_EXPORTED
42 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
43 import com.android.safetycenter.testing.SafetySourceIntentHandler.Companion.ACTION_DISMISS_ISSUE
44 import com.android.safetycenter.testing.SafetySourceIntentHandler.Companion.ACTION_RESOLVE_ACTION
45 import com.android.safetycenter.testing.SafetySourceIntentHandler.Companion.EXTRA_SOURCE_ID
46 import com.android.safetycenter.testing.SafetySourceIntentHandler.Companion.EXTRA_SOURCE_ISSUE_ACTION_ID
47 import com.android.safetycenter.testing.SafetySourceIntentHandler.Companion.EXTRA_SOURCE_ISSUE_ID
48 import kotlin.math.max
49 
50 /**
51  * A class that provides [SafetySourceData] objects and associated constants to facilitate setting
52  * up specific states in SafetyCenter for testing.
53  */
54 @RequiresApi(TIRAMISU)
55 class SafetySourceTestData(private val context: Context) {
56 
57     /**
58      * A [PendingIntent] that redirects to the [TestActivity] page.
59      *
60      * @param explicit whether the returned [PendingIntent] should use an explicit [Intent] (default
61      *   [true])
62      * @param identifier the [Intent] identifier (default [null])
63      */
createTestActivityRedirectPendingIntentnull64     fun createTestActivityRedirectPendingIntent(
65         explicit: Boolean = true,
66         identifier: String? = null
67     ) =
68         createRedirectPendingIntent(
69             context,
70             createTestActivityIntent(context, explicit).setIdentifier(identifier)
71         )
72 
73     /** A [SafetySourceData] with a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus]. */
74     val unspecified =
75         SafetySourceData.Builder()
76             .setStatus(
77                 SafetySourceStatus.Builder(
78                         "Unspecified title",
79                         "Unspecified summary",
80                         SEVERITY_LEVEL_UNSPECIFIED
81                     )
82                     .setEnabled(false)
83                     .build()
84             )
85             .build()
86 
87     /**
88      * A disabled [SafetySourceData] with a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus], and a
89      * [PendingIntent] that redirects to [TestActivity].
90      */
91     val unspecifiedDisabledWithTestActivityRedirect =
92         SafetySourceData.Builder()
93             .setStatus(
94                 SafetySourceStatus.Builder(
95                         "Clickable disabled title",
96                         "Clickable disabled summary",
97                         SEVERITY_LEVEL_UNSPECIFIED
98                     )
99                     .setEnabled(false)
100                     .setPendingIntent(createTestActivityRedirectPendingIntent())
101                     .build()
102             )
103             .build()
104 
105     /** A [SafetySourceIssue] with a [SEVERITY_LEVEL_INFORMATION] and a redirecting [Action]. */
106     val informationIssue = defaultInformationIssueBuilder().build()
107 
108     /**
109      * A [SafetySourceIssue.Builder] with a [SEVERITY_LEVEL_INFORMATION] and a redirecting [Action].
110      */
111     fun defaultInformationIssueBuilder(
112         id: String = INFORMATION_ISSUE_ID,
113         title: String = "Information issue title",
114         summary: String = "Information issue summary"
115     ) =
116         SafetySourceIssue.Builder(id, title, summary, SEVERITY_LEVEL_INFORMATION, ISSUE_TYPE_ID)
117             .addAction(action())
118 
119     /** Creates an action with some defaults set. */
120     fun action(
121         id: String = INFORMATION_ISSUE_ACTION_ID,
122         label: String = "Review",
123         pendingIntent: PendingIntent = createTestActivityRedirectPendingIntent()
124     ) = Action.Builder(id, label, pendingIntent).build()
125 
126     /**
127      * A [SafetySourceIssue] with a [SEVERITY_LEVEL_INFORMATION] and a redirecting [Action]. With
128      * subtitle provided.
129      */
130     val informationIssueWithSubtitle =
131         SafetySourceIssue.Builder(
132                 INFORMATION_ISSUE_ID,
133                 "Information issue title",
134                 "Information issue summary",
135                 SEVERITY_LEVEL_INFORMATION,
136                 ISSUE_TYPE_ID
137             )
138             .setSubtitle("Information issue subtitle")
139             .addAction(action())
140             .build()
141 
142     /**
143      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
144      * a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus].
145      */
146     val unspecifiedWithIssue =
147         SafetySourceData.Builder()
148             .setStatus(
149                 SafetySourceStatus.Builder(
150                         "Unspecified title",
151                         "Unspecified summary",
152                         SEVERITY_LEVEL_UNSPECIFIED
153                     )
154                     .setPendingIntent(createTestActivityRedirectPendingIntent())
155                     .build()
156             )
157             .addIssue(informationIssue)
158             .build()
159 
160     /**
161      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
162      * a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus], to be used for a managed profile entry.
163      */
164     val unspecifiedWithIssueForWork =
165         SafetySourceData.Builder()
166             .setStatus(
167                 SafetySourceStatus.Builder(
168                         "Unspecified title for Work",
169                         "Unspecified summary",
170                         SEVERITY_LEVEL_UNSPECIFIED
171                     )
172                     .setPendingIntent(createTestActivityRedirectPendingIntent())
173                     .build()
174             )
175             .addIssue(informationIssue)
176             .build()
177 
178     /**
179      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
180      * a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus], to be used for a private profile entry.
181      */
182     val unspecifiedWithIssueForPrivate =
183         SafetySourceData.Builder()
184             .setStatus(
185                 SafetySourceStatus.Builder(
186                         "Unspecified title for Private",
187                         "Unspecified summary",
188                         SEVERITY_LEVEL_UNSPECIFIED
189                     )
190                     .setPendingIntent(createTestActivityRedirectPendingIntent())
191                     .build()
192             )
193             .addIssue(informationIssue)
194             .build()
195 
196     /** A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] [SafetySourceStatus]. */
197     val information =
198         SafetySourceData.Builder()
199             .setStatus(
200                 SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
201                     .setPendingIntent(createTestActivityRedirectPendingIntent())
202                     .build()
203             )
204             .build()
205 
206     /**
207      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] [SafetySourceStatus] and null
208      * pending intent.
209      */
210     val informationWithNullIntent =
211         SafetySourceData.Builder()
212             .setStatus(
213                 SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
214                     .setPendingIntent(null)
215                     .build()
216             )
217             .build()
218 
219     /**
220      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] [SafetySourceStatus] and an
221      * [IconAction] defined.
222      */
223     val informationWithIconAction =
224         SafetySourceData.Builder()
225             .setStatus(
226                 SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
227                     .setPendingIntent(createTestActivityRedirectPendingIntent())
228                     .setIconAction(
229                         IconAction(ICON_TYPE_INFO, createTestActivityRedirectPendingIntent())
230                     )
231                     .build()
232             )
233             .build()
234 
235     /**
236      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] [SafetySourceStatus] and an
237      * [IconAction] having a [ICON_TYPE_GEAR].
238      */
239     val informationWithGearIconAction =
240         SafetySourceData.Builder()
241             .setStatus(
242                 SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
243                     .setPendingIntent(createTestActivityRedirectPendingIntent())
244                     .setIconAction(
245                         IconAction(ICON_TYPE_GEAR, createTestActivityRedirectPendingIntent())
246                     )
247                     .build()
248             )
249             .build()
250 
251     /**
252      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
253      * [SafetySourceStatus].
254      */
255     val informationWithIssue =
256         SafetySourceData.Builder()
257             .setStatus(
258                 SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
259                     .setPendingIntent(createTestActivityRedirectPendingIntent())
260                     .build()
261             )
262             .addIssue(informationIssue)
263             .build()
264 
265     /**
266      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting a [SafetySourceIssue]
267      * having a [SafetySourceIssue.attributionTitle] and [SafetySourceStatus].
268      */
269     val informationWithIssueWithAttributionTitle: SafetySourceData
270         @RequiresApi(UPSIDE_DOWN_CAKE)
271         get() =
272             SafetySourceData.Builder()
273                 .setStatus(
274                     SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
275                         .setPendingIntent(createTestActivityRedirectPendingIntent())
276                         .build()
277                 )
278                 .addIssue(
279                     defaultInformationIssueBuilder()
280                         .setAttributionTitle("Attribution Title")
281                         .build()
282                 )
283                 .build()
284 
285     /**
286      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
287      * [SafetySourceStatus], to be used for a managed profile entry.
288      */
289     val informationWithIssueForWork =
290         SafetySourceData.Builder()
291             .setStatus(
292                 SafetySourceStatus.Builder(
293                         "Ok title for Work",
294                         "Ok summary",
295                         SEVERITY_LEVEL_INFORMATION
296                     )
297                     .setPendingIntent(createTestActivityRedirectPendingIntent())
298                     .build()
299             )
300             .addIssue(informationIssue)
301             .build()
302 
303     /**
304      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
305      * [SafetySourceStatus], to be used for a private profile entry.
306      */
307     val informationWithIssueForPrivate =
308         SafetySourceData.Builder()
309             .setStatus(
310                 SafetySourceStatus.Builder(
311                         "Ok title for Private",
312                         "Ok summary",
313                         SEVERITY_LEVEL_INFORMATION
314                     )
315                     .setPendingIntent(createTestActivityRedirectPendingIntent())
316                     .build()
317             )
318             .addIssue(informationIssue)
319             .build()
320 
321     /**
322      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
323      * [SafetySourceStatus].
324      */
325     val informationWithSubtitleIssue =
326         SafetySourceData.Builder()
327             .setStatus(
328                 SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
329                     .setPendingIntent(createTestActivityRedirectPendingIntent())
330                     .build()
331             )
332             .addIssue(informationIssueWithSubtitle)
333             .build()
334 
335     /**
336      * A [SafetySourceIssue.Builder] with a [SEVERITY_LEVEL_RECOMMENDATION] and a redirecting
337      * [Action].
338      */
339     fun defaultRecommendationIssueBuilder(
340         title: String = "Recommendation issue title",
341         summary: String = "Recommendation issue summary",
342         confirmationDialog: Boolean = false
343     ) =
344         SafetySourceIssue.Builder(
345                 RECOMMENDATION_ISSUE_ID,
346                 title,
347                 summary,
348                 SEVERITY_LEVEL_RECOMMENDATION,
349                 ISSUE_TYPE_ID
350             )
351             .addAction(
352                 Action.Builder(
353                         RECOMMENDATION_ISSUE_ACTION_ID,
354                         "See issue",
355                         createTestActivityRedirectPendingIntent()
356                     )
357                     .apply {
358                         if (confirmationDialog && SdkLevel.isAtLeastU()) {
359                             setConfirmationDialogDetails(CONFIRMATION_DETAILS)
360                         }
361                     }
362                     .build()
363             )
364 
365     /**
366      * A [SafetySourceIssue] with a [SEVERITY_LEVEL_RECOMMENDATION], general category and a
367      * redirecting [Action].
368      */
369     val recommendationGeneralIssue = defaultRecommendationIssueBuilder().build()
370 
371     /**
372      * A [SafetySourceIssue] with a [SEVERITY_LEVEL_RECOMMENDATION], general category, redirecting
373      * [Action] and with deduplication id.
374      */
375     @RequiresApi(UPSIDE_DOWN_CAKE)
recommendationIssueWithDeduplicationIdnull376     fun recommendationIssueWithDeduplicationId(deduplicationId: String) =
377         defaultRecommendationIssueBuilder().setDeduplicationId(deduplicationId).build()
378 
379     val recommendationIssueWithActionConfirmation: SafetySourceIssue
380         @RequiresApi(UPSIDE_DOWN_CAKE)
381         get() = defaultRecommendationIssueBuilder(confirmationDialog = true).build()
382 
383     /**
384      * A [SafetySourceIssue] with a [SEVERITY_LEVEL_RECOMMENDATION], account category and a
385      * redirecting [Action].
386      */
387     val recommendationAccountIssue =
388         defaultRecommendationIssueBuilder()
389             .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT)
390             .build()
391 
392     /**
393      * A [SafetySourceIssue] with a [SEVERITY_LEVEL_RECOMMENDATION], device category and a
394      * redirecting [Action].
395      */
396     val recommendationDeviceIssue =
397         defaultRecommendationIssueBuilder()
398             .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
399             .build()
400 
401     private val dismissIssuePendingIntent =
402         broadcastPendingIntent(
403             Intent(ACTION_DISMISS_ISSUE).putExtra(EXTRA_SOURCE_ID, SINGLE_SOURCE_ID)
404         )
405 
406     /**
407      * A [SafetySourceIssue] with a [SEVERITY_LEVEL_RECOMMENDATION] and a dismiss [PendingIntent].
408      */
409     val recommendationIssueWithDismissPendingIntent =
410         defaultRecommendationIssueBuilder()
411             .setOnDismissPendingIntent(dismissIssuePendingIntent)
412             .build()
413 
414     /** A [SafetySourceData.Builder] with a [SEVERITY_LEVEL_RECOMMENDATION] status. */
415     fun defaultRecommendationDataBuilder() =
416         SafetySourceData.Builder()
417             .setStatus(
418                 SafetySourceStatus.Builder(
419                         "Recommendation title",
420                         "Recommendation summary",
421                         SEVERITY_LEVEL_RECOMMENDATION
422                     )
423                     .setPendingIntent(createTestActivityRedirectPendingIntent())
424                     .build()
425             )
426 
427     /**
428      * A [SafetySourceData] with a [SEVERITY_LEVEL_RECOMMENDATION] redirecting [SafetySourceIssue]
429      * and [SafetySourceStatus], only containing a general issue.
430      */
431     val recommendationWithGeneralIssue =
432         defaultRecommendationDataBuilder().addIssue(recommendationGeneralIssue).build()
433 
434     val recommendationWithIssueWithActionConfirmation: SafetySourceData
435         @RequiresApi(UPSIDE_DOWN_CAKE)
436         get() =
437             defaultRecommendationDataBuilder()
438                 .addIssue(recommendationIssueWithActionConfirmation)
439                 .build()
440 
441     /**
442      * A [SafetySourceData] with a [SEVERITY_LEVEL_RECOMMENDATION] redirecting [SafetySourceIssue]
443      * and [SafetySourceStatus], only containing an account issue.
444      */
445     val recommendationWithAccountIssue =
446         defaultRecommendationDataBuilder().addIssue(recommendationAccountIssue).build()
447 
448     /**
449      * A [SafetySourceData] with a [SEVERITY_LEVEL_RECOMMENDATION] redirecting [SafetySourceIssue]
450      * and [SafetySourceStatus], only containing a device issue.
451      */
452     val recommendationWithDeviceIssue =
453         defaultRecommendationDataBuilder().addIssue(recommendationDeviceIssue).build()
454 
455     /**
456      * A [SafetySourceData] with a [SEVERITY_LEVEL_RECOMMENDATION] [SafetySourceIssue] that has a
457      * dismiss [PendingIntent], and [SafetySourceStatus].
458      */
459     val recommendationDismissPendingIntentIssue =
460         defaultRecommendationDataBuilder()
461             .addIssue(recommendationIssueWithDismissPendingIntent)
462             .build()
463 
464     /** A [PendingIntent] used by the resolving [Action] in [criticalResolvingGeneralIssue]. */
465     val criticalIssueActionPendingIntent = resolvingActionPendingIntent()
466 
467     /** A [PendingIntent] used by the resolving [Action] in [criticalResolvingGeneralIssue]. */
468     fun criticalIssueActionPendingIntent(sourceId: String) =
469         resolvingActionPendingIntent(sourceId = sourceId)
470 
471     /**
472      * Returns a [PendingIntent] for a resolving [Action] with the given [sourceId], [sourceIssueId]
473      * and [sourceIssueActionId]. Default values are the same as those used by
474      * [criticalIssueActionPendingIntent]. *
475      */
476     fun resolvingActionPendingIntent(
477         sourceId: String = SINGLE_SOURCE_ID,
478         sourceIssueId: String = CRITICAL_ISSUE_ID,
479         sourceIssueActionId: String = CRITICAL_ISSUE_ACTION_ID
480     ) =
481         broadcastPendingIntent(
482             Intent(ACTION_RESOLVE_ACTION)
483                 .putExtra(EXTRA_SOURCE_ID, sourceId)
484                 .putExtra(EXTRA_SOURCE_ISSUE_ID, sourceIssueId)
485                 .putExtra(EXTRA_SOURCE_ISSUE_ACTION_ID, sourceIssueActionId)
486                 // Identifier is set because intent extras do not disambiguate PendingIntents
487                 .setIdentifier(sourceId + sourceIssueId + sourceIssueActionId)
488         )
489 
490     /** A resolving Critical [Action] */
491     val criticalResolvingAction =
492         Action.Builder(CRITICAL_ISSUE_ACTION_ID, "Solve issue", criticalIssueActionPendingIntent)
493             .setWillResolve(true)
494             .build()
495 
496     /** A resolving Critical [Action] */
497     private fun criticalResolvingAction(sourceId: String) =
498         Action.Builder(
499                 CRITICAL_ISSUE_ACTION_ID,
500                 "Solve issue",
501                 criticalIssueActionPendingIntent(sourceId = sourceId)
502             )
503             .setWillResolve(true)
504             .build()
505 
506     /** A resolving Critical [Action] with confirmation */
507     val criticalResolvingActionWithConfirmation: SafetySourceIssue.Action
508         @RequiresApi(UPSIDE_DOWN_CAKE)
509         get() =
510             Action.Builder(
511                     CRITICAL_ISSUE_ACTION_ID,
512                     "Solve issue",
513                     criticalIssueActionPendingIntent
514                 )
515                 .setWillResolve(true)
516                 .setConfirmationDialogDetails(CONFIRMATION_DETAILS)
517                 .build()
518 
519     /** An action that redirects to [TestActivity] */
520     val testActivityRedirectAction =
521         Action.Builder(
522                 CRITICAL_ISSUE_ACTION_ID,
523                 "Redirect",
524                 createTestActivityRedirectPendingIntent()
525             )
526             .build()
527 
528     /** A resolving Critical [Action] that declares a success message */
529     val criticalResolvingActionWithSuccessMessage =
530         Action.Builder(CRITICAL_ISSUE_ACTION_ID, "Solve issue", criticalIssueActionPendingIntent)
531             .setWillResolve(true)
532             .setSuccessMessage("Issue solved")
533             .build()
534 
535     /** A resolving Critical [Action] that declares a success message */
536     private fun criticalResolvingActionWithSuccessMessage(sourceId: String) =
537         Action.Builder(
538                 CRITICAL_ISSUE_ACTION_ID,
539                 "Solve issue",
540                 criticalIssueActionPendingIntent(sourceId = sourceId)
541             )
542             .setWillResolve(true)
543             .setSuccessMessage("Issue solved")
544             .build()
545 
546     /** A [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action]. */
547     val criticalResolvingIssueWithSuccessMessage =
548         SafetySourceIssue.Builder(
549                 CRITICAL_ISSUE_ID,
550                 "Critical issue title",
551                 "Critical issue summary",
552                 SEVERITY_LEVEL_CRITICAL_WARNING,
553                 ISSUE_TYPE_ID
554             )
555             .addAction(criticalResolvingActionWithSuccessMessage)
556             .build()
557 
558     /** A [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action]. */
559     private fun criticalResolvingIssueWithSuccessMessage(sourceId: String) =
560         SafetySourceIssue.Builder(
561                 CRITICAL_ISSUE_ID,
562                 "Critical issue title",
563                 "Critical issue summary",
564                 SEVERITY_LEVEL_CRITICAL_WARNING,
565                 ISSUE_TYPE_ID
566             )
567             .addAction(criticalResolvingActionWithSuccessMessage(sourceId = sourceId))
568             .build()
569 
570     /**
571      * Another [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a redirecting
572      * [Action].
573      */
574     val criticalRedirectingIssue =
575         SafetySourceIssue.Builder(
576                 CRITICAL_ISSUE_ID,
577                 "Critical issue title 2",
578                 "Critical issue summary 2",
579                 SEVERITY_LEVEL_CRITICAL_WARNING,
580                 ISSUE_TYPE_ID
581             )
582             .addAction(
583                 Action.Builder(
584                         CRITICAL_ISSUE_ACTION_ID,
585                         "Go solve issue",
586                         createTestActivityRedirectPendingIntent()
587                     )
588                     .build()
589             )
590             .build()
591 
592     /**
593      * Another [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] an [Action] that
594      * redirects to [TestActivity].
595      */
596     private val criticalIssueWithTestActivityRedirectAction =
597         defaultCriticalResolvingIssueBuilder()
598             .clearActions()
599             .addAction(testActivityRedirectAction)
600             .build()
601 
602     val criticalResolvingIssueWithConfirmation: SafetySourceIssue
603         @RequiresApi(UPSIDE_DOWN_CAKE)
604         get() =
605             defaultCriticalResolvingIssueBuilder()
606                 .clearActions()
607                 .addAction(criticalResolvingActionWithConfirmation)
608                 .build()
609 
610     /**
611      * [SafetySourceIssue.Builder] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action]
612      * .
613      */
614     fun defaultCriticalResolvingIssueBuilder(
615         issueId: String = CRITICAL_ISSUE_ID,
616         sourceId: String = SINGLE_SOURCE_ID,
617     ) =
618         SafetySourceIssue.Builder(
619                 issueId,
620                 "Critical issue title",
621                 "Critical issue summary",
622                 SEVERITY_LEVEL_CRITICAL_WARNING,
623                 ISSUE_TYPE_ID
624             )
625             .addAction(criticalResolvingAction(sourceId))
626 
627     /**
628      * General [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action]
629      * .
630      */
631     val criticalResolvingGeneralIssue = defaultCriticalResolvingIssueBuilder().build()
632 
633     /**
634      * General [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action]
635      * .
636      */
637     private fun criticalResolvingGeneralIssue(sourceId: String) =
638         defaultCriticalResolvingIssueBuilder(sourceId = sourceId).build()
639 
640     /**
641      * General [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and with deduplication
642      * info and a resolving [Action].
643      */
644     @RequiresApi(UPSIDE_DOWN_CAKE)
645     fun criticalIssueWithDeduplicationId(deduplicationId: String) =
646         defaultCriticalResolvingIssueBuilder().setDeduplicationId(deduplicationId).build()
647 
648     /**
649      * Account related [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving
650      * [Action].
651      */
652     val criticalResolvingAccountIssue =
653         defaultCriticalResolvingIssueBuilder()
654             .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT)
655             .build()
656 
657     /**
658      * Device related [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving
659      * [Action].
660      */
661     val criticalResolvingDeviceIssue =
662         defaultCriticalResolvingIssueBuilder()
663             .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
664             .build()
665 
666     /**
667      * Device related [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving
668      * [Action].
669      */
670     private fun criticalResolvingDeviceIssue(sourceId: String) =
671         defaultCriticalResolvingIssueBuilder(sourceId = sourceId)
672             .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
673             .build()
674 
675     /** A [SafetySourceData.Builder] with a [SEVERITY_LEVEL_CRITICAL_WARNING] status. */
676     fun defaultCriticalDataBuilder() =
677         SafetySourceData.Builder()
678             .setStatus(
679                 SafetySourceStatus.Builder(
680                         "Critical title",
681                         "Critical summary",
682                         SEVERITY_LEVEL_CRITICAL_WARNING
683                     )
684                     .setPendingIntent(createTestActivityRedirectPendingIntent())
685                     .build()
686             )
687 
688     /**
689      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] having a resolving
690      * [SafetySourceIssue] with a [SafetySourceIssue.attributionTitle] and success message.
691      */
692     val criticalWithIssueWithAttributionTitle: SafetySourceData
693         @RequiresApi(UPSIDE_DOWN_CAKE)
694         get() =
695             defaultCriticalDataBuilder()
696                 .addIssue(
697                     defaultCriticalResolvingIssueBuilder()
698                         .setAttributionTitle("Attribution Title")
699                         .clearActions()
700                         .addAction(criticalResolvingActionWithSuccessMessage)
701                         .build()
702                 )
703                 .build()
704 
705     /**
706      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] having a redirecting
707      * [SafetySourceIssue] with a [SafetySourceIssue.attributionTitle] and confirmation.
708      */
709     val criticalWithIssueWithConfirmationWithAttributionTitle: SafetySourceData
710         @RequiresApi(UPSIDE_DOWN_CAKE)
711         get() =
712             defaultCriticalDataBuilder()
713                 .addIssue(
714                     defaultCriticalResolvingIssueBuilder()
715                         .setAttributionTitle("Attribution Title")
716                         .clearActions()
717                         .addAction(criticalResolvingActionWithConfirmation)
718                         .build()
719                 )
720                 .build()
721 
722     /**
723      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] having a redirecting
724      * [SafetySourceIssue] with a [SafetySourceIssue.attributionTitle].
725      */
726     val criticalWithTestActivityRedirectWithAttributionTitle: SafetySourceData
727         @RequiresApi(UPSIDE_DOWN_CAKE)
728         get() =
729             defaultCriticalDataBuilder()
730                 .addIssue(
731                     defaultCriticalResolvingIssueBuilder()
732                         .setAttributionTitle("Attribution Title")
733                         .clearActions()
734                         .addAction(testActivityRedirectAction)
735                         .build()
736                 )
737                 .build()
738 
739     /**
740      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving general
741      * [SafetySourceIssue] and [SafetySourceStatus].
742      */
743     val criticalWithResolvingGeneralIssue =
744         defaultCriticalDataBuilder().addIssue(criticalResolvingGeneralIssue).build()
745 
746     /**
747      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving general
748      * [SafetySourceIssue] and [SafetySourceStatus].
749      */
750     fun criticalWithResolvingGeneralIssue(sourceId: String) =
751         defaultCriticalDataBuilder()
752             .addIssue(criticalResolvingGeneralIssue(sourceId = sourceId))
753             .build()
754 
755     /**
756      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving general
757      * [SafetySourceIssue] and [SafetySourceStatus], with confirmation dialog.
758      */
759     val criticalWithResolvingGeneralIssueWithConfirmation: SafetySourceData
760         @RequiresApi(UPSIDE_DOWN_CAKE)
761         get() =
762             defaultCriticalDataBuilder().addIssue(criticalResolvingIssueWithConfirmation).build()
763 
764     /**
765      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] with a [SafetySourceIssue] that
766      * redirects to the [TestActivity].
767      */
768     val criticalWithTestActivityRedirectIssue =
769         defaultCriticalDataBuilder().addIssue(criticalIssueWithTestActivityRedirectAction).build()
770 
771     /**
772      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving account related
773      * [SafetySourceIssue] and [SafetySourceStatus].
774      */
775     val criticalWithResolvingAccountIssue =
776         defaultCriticalDataBuilder().addIssue(criticalResolvingAccountIssue).build()
777 
778     /**
779      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving device related
780      * [SafetySourceIssue] and [SafetySourceStatus].
781      */
782     val criticalWithResolvingDeviceIssue =
783         defaultCriticalDataBuilder().addIssue(criticalResolvingDeviceIssue).build()
784 
785     /**
786      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving device related
787      * [SafetySourceIssue] and [SafetySourceStatus].
788      */
789     fun criticalWithResolvingDeviceIssue(sourceId: String) =
790         defaultCriticalDataBuilder()
791             .addIssue(criticalResolvingDeviceIssue(sourceId = sourceId))
792             .build()
793 
794     /**
795      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving device related
796      * [SafetySourceIssue] and [SafetySourceStatus] and a recommendation issue.
797      */
798     val criticalWithResolvingDeviceIssueAndRecommendationIssue =
799         defaultCriticalDataBuilder()
800             .addIssue(criticalResolvingDeviceIssue)
801             .addIssue(recommendationAccountIssue)
802             .build()
803 
804     /**
805      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving [SafetySourceIssue]
806      * and [SafetySourceStatus].
807      */
808     val criticalWithResolvingIssueWithSuccessMessage =
809         SafetySourceData.Builder()
810             .setStatus(
811                 SafetySourceStatus.Builder(
812                         "Critical title",
813                         "Critical summary",
814                         SEVERITY_LEVEL_CRITICAL_WARNING
815                     )
816                     .setPendingIntent(createTestActivityRedirectPendingIntent())
817                     .build()
818             )
819             .addIssue(criticalResolvingIssueWithSuccessMessage)
820             .build()
821 
822     /**
823      * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving [SafetySourceIssue]
824      * and [SafetySourceStatus].
825      */
826     fun criticalWithResolvingIssueWithSuccessMessage(sourceId: String) =
827         SafetySourceData.Builder()
828             .setStatus(
829                 SafetySourceStatus.Builder(
830                         "Critical title",
831                         "Critical summary",
832                         SEVERITY_LEVEL_CRITICAL_WARNING
833                     )
834                     .setPendingIntent(createTestActivityRedirectPendingIntent())
835                     .build()
836             )
837             .addIssue(criticalResolvingIssueWithSuccessMessage(sourceId = sourceId))
838             .build()
839 
840     /**
841      * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
842      * [SEVERITY_LEVEL_CRITICAL_WARNING] [SafetySourceStatus].
843      */
844     val criticalWithInformationIssue =
845         defaultCriticalDataBuilder().addIssue(informationIssue).build()
846 
847     /**
848      * Another [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] redirecting
849      * [SafetySourceIssue] and [SafetySourceStatus].
850      */
851     val criticalWithRedirectingIssue =
852         SafetySourceData.Builder()
853             .setStatus(
854                 SafetySourceStatus.Builder(
855                         "Critical title 2",
856                         "Critical summary 2",
857                         SEVERITY_LEVEL_CRITICAL_WARNING
858                     )
859                     .setPendingIntent(createTestActivityRedirectPendingIntent())
860                     .build()
861             )
862             .addIssue(criticalRedirectingIssue)
863             .build()
864 
865     /**
866      * A function to generate simple [SafetySourceData] with the given entry [severityLevel] and
867      * [entrySummary], and an optional issue with the same [severityLevel].
868      */
869     fun buildSafetySourceDataWithSummary(
870         severityLevel: Int,
871         entrySummary: String,
872         withIssue: Boolean = false,
873         entryTitle: String = "Entry title"
874     ) =
875         SafetySourceData.Builder()
876             .setStatus(
877                 SafetySourceStatus.Builder(entryTitle, entrySummary, severityLevel)
878                     .setPendingIntent(createTestActivityRedirectPendingIntent())
879                     .build()
880             )
881             .apply {
882                 if (withIssue) {
883                     addIssue(
884                         SafetySourceIssue.Builder(
885                                 "issue_id",
886                                 "Issue title",
887                                 "Issue summary",
888                                 max(severityLevel, SEVERITY_LEVEL_INFORMATION),
889                                 ISSUE_TYPE_ID
890                             )
891                             .addAction(
892                                 Action.Builder(
893                                         "action_id",
894                                         "Action",
895                                         createTestActivityRedirectPendingIntent()
896                                     )
897                                     .build()
898                             )
899                             .build()
900                     )
901                 }
902             }
903             .build()
904 
broadcastPendingIntentnull905     private fun broadcastPendingIntent(intent: Intent): PendingIntent =
906         PendingIntent.getBroadcast(
907             context,
908             0,
909             intent.addFlags(FLAG_RECEIVER_FOREGROUND).setPackage(context.packageName),
910             PendingIntent.FLAG_IMMUTABLE
911         )
912 
913     companion object {
914         /** Issue ID for [informationIssue]. */
915         const val INFORMATION_ISSUE_ID = "information_issue_id"
916 
917         /** Action ID for the redirecting action in [informationIssue]. */
918         const val INFORMATION_ISSUE_ACTION_ID = "information_issue_action_id"
919 
920         /** Issue ID for a recommendation issue */
921         const val RECOMMENDATION_ISSUE_ID = "recommendation_issue_id"
922 
923         /** Action ID for the redirecting action in recommendation issue. */
924         const val RECOMMENDATION_ISSUE_ACTION_ID = "recommendation_issue_action_id"
925 
926         /** Issue ID for the critical issues in this file. */
927         const val CRITICAL_ISSUE_ID = "critical_issue_id"
928 
929         /** Action ID for the critical actions in this file. */
930         const val CRITICAL_ISSUE_ACTION_ID = "critical_issue_action_id"
931 
932         /** Issue type ID for all the issues in this file */
933         // LINT.IfChange(issue_type_id)
934         const val ISSUE_TYPE_ID = "issue_type_id"
935         // LINT.ThenChange(/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterInteractionLoggingHostTest.kt:issue_type_id)
936 
937         const val CONFIRMATION_TITLE = "Confirmation title"
938         const val CONFIRMATION_TEXT = "Confirmation text"
939         const val CONFIRMATION_YES = "Confirmation yes"
940         const val CONFIRMATION_NO = "Confirmation no"
941         val CONFIRMATION_DETAILS: ConfirmationDialogDetails
942             @RequiresApi(UPSIDE_DOWN_CAKE)
943             get() =
944                 ConfirmationDialogDetails(
945                     CONFIRMATION_TITLE,
946                     CONFIRMATION_TEXT,
947                     CONFIRMATION_YES,
948                     CONFIRMATION_NO
949                 )
950 
951         /** A [SafetyEvent] to push arbitrary changes to Safety Center. */
952         val EVENT_SOURCE_STATE_CHANGED =
953             SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build()
954 
955         /** Returns a [SafetySourceData] object containing only the given [issues]. */
956         fun issuesOnly(vararg issues: SafetySourceIssue): SafetySourceData {
957             val builder = SafetySourceData.Builder()
958             issues.forEach { builder.addIssue(it) }
959             return builder.build()
960         }
961 
962         /** Returns an [Intent] that redirects to the [TestActivity] page. */
963         fun createTestActivityIntent(context: Context, explicit: Boolean = true): Intent =
964             if (explicit) {
965                 Intent(ACTION_TEST_ACTIVITY).setPackage(context.packageName)
966             } else {
967                 val intent = Intent(ACTION_TEST_ACTIVITY_EXPORTED)
968                 // We have seen some flakiness where implicit intents find multiple receivers
969                 // and the ResolveActivity pops up.  A test cannot handle this, so crash.  Most
970                 // likely the cause is other test's APKs being left hanging around by flaky
971                 // test infrastructure.
972                 intent.flags = intent.flags or Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT
973                 intent
974             }
975 
976         /** Returns a [PendingIntent] that redirects to the given [Intent]. */
977         fun createRedirectPendingIntent(context: Context, intent: Intent): PendingIntent {
978             return PendingIntent.getActivity(
979                 context,
980                 0 /* requestCode */,
981                 intent,
982                 PendingIntent.FLAG_IMMUTABLE
983             )
984         }
985     }
986 }
987