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