1 /* 2 * Copyright (C) 2023 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 package android.app.notification.current.cts 17 18 import android.Manifest.permission.POST_NOTIFICATIONS 19 import android.Manifest.permission.RECEIVE_SENSITIVE_NOTIFICATIONS 20 import android.app.AppOpsManager 21 import android.app.Notification 22 import android.app.Notification.CATEGORY_MESSAGE 23 import android.app.Notification.EXTRA_MESSAGES 24 import android.app.Notification.EXTRA_SUB_TEXT 25 import android.app.Notification.EXTRA_TEXT 26 import android.app.Notification.EXTRA_TEXT_LINES 27 import android.app.Notification.EXTRA_TITLE 28 import android.app.Notification.InboxStyle 29 import android.app.Notification.MessagingStyle 30 import android.app.Notification.MessagingStyle.Message 31 import android.app.NotificationManager 32 import android.app.PendingIntent 33 import android.app.Person 34 import android.app.stubs.R 35 import android.app.stubs.shared.NotificationHelper.SEARCH_TYPE 36 import android.companion.CompanionDeviceManager 37 import android.content.Intent 38 import android.content.pm.ApplicationInfo 39 import android.content.pm.PackageManager 40 import android.graphics.drawable.Icon 41 import android.net.MacAddress 42 import android.os.Bundle 43 import android.os.Parcelable 44 import android.os.Process 45 import android.permission.cts.PermissionUtils 46 import android.platform.test.annotations.RequiresFlagsDisabled 47 import android.platform.test.annotations.RequiresFlagsEnabled 48 import android.platform.test.flag.junit.DeviceFlagsValueProvider 49 import android.service.notification.Adjustment 50 import android.service.notification.Adjustment.KEY_IMPORTANCE 51 import android.service.notification.Adjustment.KEY_RANKING_SCORE 52 import android.service.notification.Flags 53 import android.service.notification.NotificationListenerService 54 import android.service.notification.StatusBarNotification 55 import androidx.test.runner.AndroidJUnit4 56 import com.android.compatibility.common.util.CddTest 57 import com.android.compatibility.common.util.SystemUtil.runShellCommand 58 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity 59 import com.google.common.truth.Truth.assertWithMessage 60 import org.junit.Assert 61 import org.junit.Assert.assertEquals 62 import org.junit.Assert.assertTrue 63 import org.junit.Assume.assumeFalse 64 import org.junit.Before 65 import org.junit.Rule 66 import org.junit.Test 67 import org.junit.runner.RunWith 68 69 // TODO: b/301960090: Add tests with real NAS 70 /** 71 * These tests ensure that untrusted notification listeners get a redacted version of notifications, 72 * if said notifications have sensitive content. 73 */ 74 @RunWith(AndroidJUnit4::class) 75 class SensitiveNotificationRedactionTest : BaseNotificationManagerTest() { 76 private val groupKey = "SensitiveNotificationRedactionTest begun at " + 77 System.currentTimeMillis() 78 79 @JvmField 80 @Rule 81 val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()!! 82 83 @Before 84 @Throws(Exception::class) setUpnull85 fun setUp() { 86 PermissionUtils.grantPermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS) 87 88 setUpNotifListener() 89 mAssistant = mNotificationHelper.enableAssistant(mContext.packageName) 90 mAssistant.mMarkSensitiveContent = true 91 mAssistant.mSmartReplies = 92 ArrayList<CharSequence>(listOf(OTP_MESSAGE_BASIC as CharSequence)) 93 mAssistant.mSmartActions = ArrayList<Notification.Action>(listOf(createAction())) 94 } 95 sendNotificationnull96 fun sendNotification( 97 text: String = OTP_MESSAGE_BASIC, 98 title: String = OTP_MESSAGE_BASIC, 99 subtext: String = OTP_MESSAGE_BASIC, 100 category: String = CATEGORY_MESSAGE, 101 actions: List<Notification.Action>? = null, 102 style: Notification.Style? = null, 103 extras: Bundle? = null, 104 tag: String = groupKey 105 ) { 106 val intent = Intent(Intent.ACTION_MAIN) 107 intent.setFlags( 108 Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP 109 or Intent.FLAG_ACTIVITY_CLEAR_TOP 110 ) 111 intent.setAction(Intent.ACTION_MAIN) 112 intent.setPackage(mContext.getPackageName()) 113 114 val nb = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 115 nb.setContentText(text) 116 nb.setContentTitle(title) 117 nb.setSubText(subtext) 118 nb.setCategory(category) 119 nb.setSmallIcon(R.drawable.black) 120 nb.setLargeIcon(Icon.createWithResource(mContext, R.drawable.black)) 121 nb.setContentIntent(createTestPendingIntent()) 122 nb.setGroup(groupKey) 123 if (actions != null) { 124 nb.setActions(*actions.toTypedArray()) 125 } 126 if (style != null) { 127 nb.setStyle(style) 128 } 129 if (extras != null) { 130 nb.addExtras(extras) 131 } 132 mNotificationManager.notify(tag, NOTIFICATION_ID, nb.build()) 133 } 134 createTestPendingIntentnull135 private fun createTestPendingIntent(): PendingIntent { 136 val intent = Intent(Intent.ACTION_MAIN) 137 intent.setFlags( 138 Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP 139 or Intent.FLAG_ACTIVITY_CLEAR_TOP 140 ) 141 intent.setAction(Intent.ACTION_MAIN) 142 intent.setPackage(mContext.getPackageName()) 143 144 return PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE) 145 } 146 createActionnull147 private fun createAction(): Notification.Action { 148 val pendingIntent = createTestPendingIntent() 149 return Notification.Action.Builder( 150 Icon.createWithResource(mContext, R.drawable.black), 151 OTP_MESSAGE_BASIC, 152 pendingIntent 153 ).build() 154 } 155 waitForNotificationnull156 private fun waitForNotification( 157 searchType: SEARCH_TYPE = SEARCH_TYPE.POSTED, 158 tag: String = groupKey 159 ): StatusBarNotification { 160 val sbn = mNotificationHelper.findPostedNotification(tag, NOTIFICATION_ID, searchType) 161 assertWithMessage("Expected to find a notification with tag $tag") 162 .that(sbn).isNotNull() 163 return sbn!! 164 } 165 166 @Test 167 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testTextFieldsRedactednull168 fun testTextFieldsRedacted() { 169 val style = InboxStyle() 170 style.addLine(OTP_MESSAGE_BASIC) 171 172 sendNotification(style = style) 173 val sbn = waitForNotification() 174 175 val title = sbn.notification.extras.getCharSequence(EXTRA_TITLE)!! 176 val aInfo: ApplicationInfo = mPackageManager 177 .getApplicationInfo(mContext.packageName, 0) 178 val pkgLabel = aInfo.loadLabel(mPackageManager).toString() 179 assertWithMessage("Expected title to be $pkgLabel, but was $title") 180 .that(title).isEqualTo(title) 181 182 assertNotificationTextRedacted(sbn) 183 184 val subtext = sbn.notification.extras.getCharSequence(EXTRA_SUB_TEXT) 185 assertWithMessage("Expected subtext to be null, but it was $subtext").that(subtext).isNull() 186 187 val textLines = sbn.notification.extras.getCharSequenceArray(EXTRA_TEXT_LINES) 188 assertWithMessage("Expected text lines to be null, but it was ${textLines?.toList()}") 189 .that(textLines).isNull() 190 } 191 192 @Test 193 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testActionsRedactednull194 fun testActionsRedacted() { 195 val intent = Intent(Intent.ACTION_MAIN) 196 intent.setFlags( 197 Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP 198 or Intent.FLAG_ACTIVITY_CLEAR_TOP 199 ) 200 intent.setAction(Intent.ACTION_MAIN) 201 intent.setPackage(mContext.getPackageName()) 202 203 val pendingIntent = PendingIntent.getActivity( 204 mContext, 205 0, 206 intent, 207 PendingIntent.FLAG_MUTABLE 208 ) 209 sendNotification(actions = listOf(createAction())) 210 val sbn = waitForNotification() 211 val action = sbn.notification.actions.firstOrNull() 212 assertWithMessage("expected notification to have an action").that(action).isNotNull() 213 assertWithMessage("expected notification action title not to contain otp:${action!!.title}") 214 .that(action.title.toString()).doesNotContain(OTP_CODE) 215 } 216 217 @Test 218 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testMessagesRedactednull219 fun testMessagesRedacted() { 220 val empty = Person.Builder().setName(PERSON_NAME).build() 221 val message = Message(OTP_MESSAGE_BASIC, System.currentTimeMillis(), empty) 222 val style = MessagingStyle(empty).apply { 223 addMessage(message) 224 addMessage(message) 225 } 226 sendNotification(style = style) 227 val sbn = waitForNotification() 228 val messages = Message.getMessagesFromBundleArray( 229 sbn.notification.extras.getParcelableArray(EXTRA_MESSAGES, Parcelable::class.java) 230 ) 231 assertWithMessage("expected notification to have exactly one message") 232 .that(messages.size).isEqualTo(1) 233 assertWithMessage("expected single message not to contain otp: ${messages[0].text}") 234 .that(messages[0].text.toString()).doesNotContain(OTP_CODE) 235 assertWithMessage("expected message person to be redacted: ${messages[0].senderPerson}") 236 .that(messages[0].senderPerson?.name.toString()).isNotEqualTo(PERSON_NAME) 237 } 238 239 @Test 240 @RequiresFlagsEnabled( 241 Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS, 242 Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_BIG_TEXT_STYLE 243 ) testBigTextRedactednull244 fun testBigTextRedacted() { 245 val style = Notification.BigTextStyle() 246 val bigText = "BIG TEXT" 247 val bigTitleText = "BIG TITLE TEXT" 248 val summaryText = "summary text" 249 style.bigText(bigText) 250 style.setBigContentTitle(bigTitleText) 251 style.setSummaryText(summaryText) 252 sendNotification(style = style) 253 val sbn = waitForNotification() 254 val extras = sbn.notification.extras 255 val testBigText = extras.getCharSequence(Notification.EXTRA_BIG_TEXT).toString() 256 val testBigTitleText = extras.getCharSequence(Notification.EXTRA_TITLE_BIG).toString() 257 val testSummaryText = extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT).toString() 258 assertWithMessage("expected big text to be redacted: $testBigText") 259 .that(testBigText).doesNotContain(bigText) 260 assertWithMessage("expected big title text to be redacted: $testBigTitleText") 261 .that(testBigTitleText).doesNotContain(bigTitleText) 262 assertWithMessage("expected summary text to be redacted: $testSummaryText") 263 .that(testSummaryText).doesNotContain(summaryText) 264 } 265 266 @Test 267 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testCustomExtrasNotRedactednull268 fun testCustomExtrasNotRedacted() { 269 val customExtra = Bundle() 270 customExtra.putBoolean(groupKey, true) 271 sendNotification(extras = customExtra) 272 val sbn = waitForNotification() 273 274 // Assert the notification is redacted 275 assertNotificationTextRedacted(sbn) 276 277 // Assert the custom extra is still present 278 279 assertWithMessage("Expected custom extra to still be present, but it wasn't") 280 .that(sbn.notification.extras.getBoolean(groupKey, false)).isTrue() 281 } 282 283 @Test 284 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testRankingRedactedInPostnull285 fun testRankingRedactedInPost() { 286 mListener.mRankingMap = null 287 sendNotification() 288 val sbn = waitForNotification() 289 assertWithMessage("Expected to receive a ranking map") 290 .that(mListener.mRankingMap).isNotNull() 291 assertRankingRedacted(sbn.key, mListener.mRankingMap) 292 } 293 294 @Test 295 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testRankingRedactedInUpdatenull296 fun testRankingRedactedInUpdate() { 297 sendNotification() 298 val sbn = waitForNotification() 299 for (key in mListener.mRankingMap.orderedKeys) { 300 val ranking = NotificationListenerService.Ranking() 301 mListener.mRankingMap.getRanking(key, ranking) 302 } 303 mListener.mRankingMap = null 304 val b = Bundle().apply { 305 putInt(KEY_IMPORTANCE, NotificationManager.IMPORTANCE_MAX) 306 putFloat(KEY_RANKING_SCORE, 1.0f) 307 } 308 val latch = mListener.setRankingUpdateCountDown(1) 309 mAssistant.adjustNotification(Adjustment(sbn.packageName, sbn.key, b, "", sbn.user)) 310 latch.await() 311 assertWithMessage("Expected to receive a ranking map") 312 .that(mListener.mRankingMap).isNotNull() 313 assertRankingRedacted(sbn.key, mListener.mRankingMap) 314 } 315 assertRankingRedactednull316 private fun assertRankingRedacted( 317 key: String, 318 rankingMap: NotificationListenerService.RankingMap 319 ) { 320 val ranking = NotificationListenerService.Ranking() 321 val foundPostedNotifRanking = rankingMap.getRanking(key, ranking) 322 assertWithMessage("Expected to find a ranking with key $key") 323 .that(foundPostedNotifRanking).isTrue() 324 assertWithMessage("Expected smart actions to be empty").that(ranking.smartActions) 325 .isEmpty() 326 assertWithMessage("Expected smart replies to be empty").that(ranking.smartReplies) 327 .isEmpty() 328 } 329 330 @Test 331 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testGetActiveNotificationsRedactednull332 fun testGetActiveNotificationsRedacted() { 333 sendNotification() 334 val postedSbn = waitForNotification() 335 val activeSbn = mListener.getActiveNotifications(arrayOf(postedSbn.key)).first() 336 assertNotificationTextRedacted(activeSbn) 337 } 338 339 @Test 340 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testGetSnoozedNotificationsRedactednull341 fun testGetSnoozedNotificationsRedacted() { 342 sendNotification() 343 val postedSbn = waitForNotification() 344 mListener.snoozeNotification(postedSbn.key, SHORT_SLEEP_TIME_MS) 345 val snoozedSbn = waitForNotification(SEARCH_TYPE.SNOOZED) 346 // Allow the notification to be unsnoozed 347 Thread.sleep(SHORT_SLEEP_TIME_MS * 2) 348 assertNotificationTextRedacted(snoozedSbn) 349 } 350 351 @Test 352 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testListenerWithCdmAssociationGetsUnredactednull353 fun testListenerWithCdmAssociationGetsUnredacted() { 354 assumeFalse( 355 mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) || 356 mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) 357 ) 358 val cdmManager = mContext.getSystemService(CompanionDeviceManager::class.java)!! 359 val macAddress = MacAddress.fromString("00:00:00:00:00:AA") 360 try { 361 runShellCommand( 362 "cmd companiondevice associate " + 363 "${mContext.userId} ${mContext.packageName} $macAddress" 364 ) 365 // Trusted status is cached on helper enable, so disable + enable the listener 366 mNotificationHelper.disableListener(STUB_PACKAGE_NAME) 367 mNotificationHelper.enableListener(STUB_PACKAGE_NAME) 368 assertNotificationNotRedacted() 369 } finally { 370 runWithShellPermissionIdentity { 371 val assocInfo = cdmManager.allAssociations.find { 372 mContext.packageName.equals(it.packageName) 373 } 374 assertWithMessage("Expected to have an active cdm association") 375 .that(assocInfo).isNotNull() 376 cdmManager.disassociate(assocInfo!!.id) 377 } 378 } 379 } 380 381 @Test 382 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testListenerWithReceiveSensitiveNotificationsPermissionsGetsUnredactednull383 fun testListenerWithReceiveSensitiveNotificationsPermissionsGetsUnredacted() { 384 runWithShellPermissionIdentity( 385 { 386 // Trusted status is cached on helper enable, so disable + enable the listener 387 mNotificationHelper.disableListener(STUB_PACKAGE_NAME) 388 mNotificationHelper.enableListener(STUB_PACKAGE_NAME) 389 assertNotificationNotRedacted() 390 }, 391 RECEIVE_SENSITIVE_NOTIFICATIONS 392 ) 393 } 394 395 @Test 396 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testListenerWithReceiveSensitiveNotificationsAppOpGetsUnredactednull397 fun testListenerWithReceiveSensitiveNotificationsAppOpGetsUnredacted() { 398 val appOpsManager = mContext.getSystemService(AppOpsManager::class.java)!! 399 try { 400 runWithShellPermissionIdentity { 401 assertEquals( 402 AppOpsManager.MODE_IGNORED, 403 appOpsManager.checkOp( 404 AppOpsManager.OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS, 405 Process.myUid(), 406 STUB_PACKAGE_NAME 407 ) 408 ) 409 appOpsManager.setUidMode( 410 AppOpsManager.OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS, 411 Process.myUid(), 412 AppOpsManager.MODE_ALLOWED 413 ) 414 } 415 // Trusted status is cached on helper enable, so disable + enable the listener 416 mNotificationHelper.disableListener(STUB_PACKAGE_NAME) 417 mNotificationHelper.enableListener(STUB_PACKAGE_NAME) 418 assertNotificationNotRedacted() 419 } finally { 420 runWithShellPermissionIdentity { 421 appOpsManager.setUidMode( 422 AppOpsManager.OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS, 423 Process.myUid(), 424 AppOpsManager.MODE_IGNORED 425 ) 426 } 427 } 428 } 429 430 @Test 431 @RequiresFlagsDisabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testStandardListenerGetsUnredactedWhenFlagDisablednull432 fun testStandardListenerGetsUnredactedWhenFlagDisabled() { 433 assertNotificationNotRedacted() 434 } 435 436 // see packages/modules/ExtServices/java/tests/src/android/ext/services/notification/ 437 // NotificationOtpDetectionHelperTest.kt for more granular tests of these otp messages 438 @Test 439 @CddTest(requirement = "3.8.3.4/C-1-1") 440 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testE2ERedaction_shouldRedactnull441 fun testE2ERedaction_shouldRedact() { 442 assertTrue( 443 "Expected a notification assistant to be present", 444 mPreviousEnabledAssistant != null 445 ) 446 mNotificationHelper.disableAssistant(STUB_PACKAGE_NAME) 447 mNotificationHelper.enableOtherPkgAssistantIfNeeded(mPreviousEnabledAssistant) 448 // We just re-enabled the NAS. send one notification in order to start its process 449 sendNotification(text = "staring NAS process", title = "", subtext = "", tag = "start") 450 waitForNotification(tag = "start") 451 452 val shouldRedact = mutableListOf( 453 "123G5", 454 "123456F8", 455 "123ķ4", 456 "123Ŀ4", 457 "1-1-01 is the date of your code T3425", 458 "your code 54-234-3 was sent on 1-1-01", 459 "34-58-30", 460 "12-1-3089", 461 "G-3d523", 462 "G-FD-745", 463 "your code is:G-345821", 464 "your code is (G-345821", 465 "your code is \nG-345821", 466 "you code is G-345821.", 467 "you code is (G-345821)", 468 "c'est g4zy75", 469 "2109", 470 "3035", 471 "1899") 472 var notifNum = 0 473 val notRedactedFailures = StringBuilder("") 474 for (otp in shouldRedact) { 475 val tag = "$groupKey #$notifNum" 476 sendNotification(text = otp, title = "", subtext = "", tag = tag) 477 val sbn = waitForNotification(tag = tag) 478 val text = sbn.notification.extras.getCharSequence(EXTRA_TEXT)!!.toString() 479 if (text.contains(otp)) { 480 notRedactedFailures.append("otp \"$otp\" is in notification text \"$text\"\n") 481 } 482 notifNum += 1 483 } 484 485 if (notRedactedFailures.toString() != "") { 486 Assert.fail( 487 "The following codes were not redacted, but should have been:" + 488 "\n$notRedactedFailures" 489 ) 490 } 491 } 492 493 // see packages/modules/ExtServices/java/tests/src/android/ext/services/notification/ 494 // NotificationOtpDetectionHelperTest.kt for more granular tests of these otp messages 495 @Test 496 @CddTest(requirement = "3.8.3.4/C-1-1") 497 @RequiresFlagsEnabled(Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS) testE2ERedaction_shouldNotRedactnull498 fun testE2ERedaction_shouldNotRedact() { 499 assertTrue( 500 "Expected a notification assistant to be present", 501 mPreviousEnabledAssistant != null 502 ) 503 mNotificationHelper.disableAssistant(STUB_PACKAGE_NAME) 504 mNotificationHelper.enableOtherPkgAssistantIfNeeded(mPreviousEnabledAssistant) 505 // We just re-enabled the NAS. send one notification in order to start its process 506 sendNotification(text = "staring NAS process", title = "", subtext = "", tag = "start") 507 waitForNotification(tag = "start") 508 509 val shouldNotRedact = 510 mutableListOf( 511 "123G", 512 "123", 513 "12 345", 514 "123T56789", 515 "TEFHXES", 516 "01-01-2001", 517 "1-1-2001", 518 "1-1-01", 519 "6--7893", 520 "------", 521 "your code isG-345821", 522 "your code is G-345821for real", 523 "GVRXY 2", 524 "2009", 525 "1945", 526 ) 527 var notifNum = 0 528 val redactedFailures = StringBuilder("") 529 for (notOtp in shouldNotRedact) { 530 val tag = "$groupKey #$notifNum" 531 sendNotification(text = notOtp, title = "", subtext = "", tag = tag) 532 val sbn = waitForNotification(tag = tag) 533 val text = sbn.notification.extras.getCharSequence(EXTRA_TEXT)!!.toString() 534 if (!text.contains(notOtp)) { 535 redactedFailures.append( 536 "non-otp message \"$notOtp\" is not in notification text " + 537 "\"$text\"\n" 538 ) 539 } 540 notifNum += 1 541 } 542 543 if (redactedFailures.toString() != "") { 544 Assert.fail( 545 "The following codes were redacted, but should not have been:" + 546 "\n$redactedFailures" 547 ) 548 } 549 } 550 assertNotificationNotRedactednull551 private fun assertNotificationNotRedacted() { 552 sendNotification() 553 val sbn = waitForNotification() 554 val text = sbn.notification.extras.getCharSequence(EXTRA_TEXT)!!.toString() 555 assertWithMessage("Expected notification text to contain OTP code, but it did not: $text") 556 .that(text).contains(OTP_CODE) 557 } 558 assertNotificationTextRedactednull559 private fun assertNotificationTextRedacted(sbn: StatusBarNotification) { 560 val text = sbn.notification.extras.getCharSequence(EXTRA_TEXT)!!.toString() 561 assertWithMessage("Expected notification text not to contain OTP code, but it did: $text") 562 .that(text).doesNotContain(OTP_CODE) 563 } 564 565 companion object { 566 private const val OTP_CODE = "123645" 567 private const val OTP_MESSAGE_BASIC = "your one time code is 123645" 568 private const val PERSON_NAME = "Alan Smithee" 569 private const val NOTIFICATION_ID = 42 570 private const val SHORT_SLEEP_TIME_MS: Long = 100 571 } 572 } 573