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