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