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