1 /* <lambda>null2 * Copyright (C) 2020 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.R 19 import android.app.Notification 20 import android.app.PendingIntent 21 import android.app.Person 22 import android.app.cts.CtsAppTestUtils.platformNull 23 import android.app.cts.NotificationTemplateTestBase 24 import android.content.Intent 25 import android.content.pm.PackageManager 26 import android.graphics.Bitmap 27 import android.graphics.Canvas 28 import android.graphics.Color 29 import android.graphics.drawable.Icon 30 import android.net.Uri 31 import android.util.Log 32 import android.view.View 33 import android.widget.ImageView 34 import android.widget.TextView 35 import androidx.annotation.ColorInt 36 import androidx.test.filters.SmallTest 37 import androidx.test.runner.AndroidJUnit4 38 import com.android.compatibility.common.util.CddTest 39 import com.google.common.truth.Truth.assertThat 40 import kotlin.test.assertFailsWith 41 import org.junit.Before 42 import org.junit.Test 43 import org.junit.runner.RunWith 44 45 @RunWith(AndroidJUnit4::class) 46 class NotificationTemplateTest : NotificationTemplateTestBase() { 47 48 @Before 49 public fun setUp() { 50 assertThat(context.applicationInfo.targetSdkVersion).isGreaterThan(30) 51 } 52 53 @Test 54 fun testWideIcon_inCollapsedState_cappedTo16By9() { 55 val icon = createBitmap(200, 100) 56 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 57 .setSmallIcon(R.drawable.ic_media_play) 58 .setContentTitle("Title") 59 .setLargeIcon(icon) 60 .createContentView() 61 checkIconView(views) { iconView -> 62 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 63 assertThat(iconView.width.toFloat()) 64 .isWithin(1f) 65 .of((iconView.height * 16 / 9).toFloat()) 66 } 67 } 68 69 @Test 70 fun testWideIcon_inCollapsedState_canShowExact4By3() { 71 val icon = createBitmap(40, 30) 72 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 73 .setSmallIcon(R.drawable.ic_media_play) 74 .setContentTitle("Title") 75 .setLargeIcon(icon) 76 .createContentView() 77 checkIconView(views) { iconView -> 78 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 79 assertThat(iconView.width.toFloat()) 80 .isWithin(1f) 81 .of((iconView.height * 4 / 3).toFloat()) 82 } 83 } 84 85 @Test 86 fun testWideIcon_inCollapsedState_canShowUriIcon() { 87 val uri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png") 88 val icon = Icon.createWithContentUri(uri) 89 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 90 .setSmallIcon(R.drawable.ic_media_play) 91 .setContentTitle("Title") 92 .setLargeIcon(icon) 93 .createContentView() 94 checkIconView(views) { iconView -> 95 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 96 assertThat(iconView.width.toFloat()) 97 .isWithin(1f) 98 .of((iconView.height * 4 / 3).toFloat()) 99 } 100 } 101 102 @Test 103 fun testWideIcon_inCollapsedState_neverNarrowerThanSquare() { 104 val icon = createBitmap(200, 300) 105 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 106 .setSmallIcon(R.drawable.ic_media_play) 107 .setContentTitle("Title") 108 .setLargeIcon(icon) 109 .createContentView() 110 checkIconView(views) { iconView -> 111 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 112 assertThat(iconView.width).isEqualTo(iconView.height) 113 } 114 } 115 116 @Test 117 fun testWideIcon_inBigBaseState_cappedTo16By9() { 118 val icon = createBitmap(200, 100) 119 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 120 .setSmallIcon(R.drawable.ic_media_play) 121 .setContentTitle("Title") 122 .setLargeIcon(icon) 123 .createBigContentView() 124 checkIconView(views) { iconView -> 125 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 126 assertThat(iconView.width.toFloat()) 127 .isWithin(1f) 128 .of((iconView.height * 16 / 9).toFloat()) 129 } 130 } 131 132 @Test 133 fun testWideIcon_inBigBaseState_canShowExact4By3() { 134 val icon = createBitmap(40, 30) 135 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 136 .setSmallIcon(R.drawable.ic_media_play) 137 .setContentTitle("Title") 138 .setLargeIcon(icon) 139 .createBigContentView() 140 checkIconView(views) { iconView -> 141 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 142 assertThat(iconView.width.toFloat()) 143 .isWithin(1f) 144 .of((iconView.height * 4 / 3).toFloat()) 145 } 146 } 147 148 @Test 149 fun testWideIcon_inBigBaseState_neverNarrowerThanSquare() { 150 val icon = createBitmap(200, 300) 151 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 152 .setSmallIcon(R.drawable.ic_media_play) 153 .setContentTitle("Title") 154 .setLargeIcon(icon) 155 .createBigContentView() 156 checkIconView(views) { iconView -> 157 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 158 assertThat(iconView.width).isEqualTo(iconView.height) 159 } 160 } 161 162 @Test 163 fun testWideIcon_inBigPicture_cappedTo16By9() { 164 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 165 return 166 } 167 168 val picture = createBitmap(40, 30) 169 val icon = createBitmap(200, 100) 170 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 171 .setSmallIcon(R.drawable.ic_media_play) 172 .setContentTitle("Title") 173 .setLargeIcon(icon) 174 .setStyle(Notification.BigPictureStyle().bigPicture(picture)) 175 .createBigContentView() 176 checkIconView(views) { iconView -> 177 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 178 assertThat(iconView.width.toFloat()) 179 .isWithin(1f) 180 .of((iconView.height * 16 / 9).toFloat()) 181 } 182 } 183 184 @Test 185 fun testWideIcon_inBigPicture_canShowExact4By3() { 186 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 187 return 188 } 189 190 val picture = createBitmap(40, 30) 191 val icon = createBitmap(40, 30) 192 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 193 .setSmallIcon(R.drawable.ic_media_play) 194 .setContentTitle("Title") 195 .setLargeIcon(icon) 196 .setStyle(Notification.BigPictureStyle().bigPicture(picture)) 197 .createBigContentView() 198 checkIconView(views) { iconView -> 199 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 200 assertThat(iconView.width.toFloat()) 201 .isWithin(1f) 202 .of((iconView.height * 4 / 3).toFloat()) 203 } 204 } 205 206 @Test 207 fun testWideIcon_inBigPicture_neverNarrowerThanSquare() { 208 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 209 return 210 } 211 212 val picture = createBitmap(40, 30) 213 val icon = createBitmap(200, 300) 214 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 215 .setSmallIcon(R.drawable.ic_media_play) 216 .setContentTitle("Title") 217 .setLargeIcon(icon) 218 .setStyle(Notification.BigPictureStyle().bigPicture(picture)) 219 .createBigContentView() 220 checkIconView(views) { iconView -> 221 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 222 assertThat(iconView.width).isEqualTo(iconView.height) 223 } 224 } 225 226 @Test 227 fun testWideIcon_inBigText_cappedTo16By9() { 228 val icon = createBitmap(200, 100) 229 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 230 .setSmallIcon(R.drawable.ic_media_play) 231 .setContentTitle("Title") 232 .setLargeIcon(icon) 233 .setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent")) 234 .createBigContentView() 235 checkIconView(views) { iconView -> 236 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 237 assertThat(iconView.width.toFloat()) 238 .isWithin(1f) 239 .of((iconView.height * 16 / 9).toFloat()) 240 } 241 } 242 243 @Test 244 fun testWideIcon_inBigText_canShowExact4By3() { 245 val icon = createBitmap(40, 30) 246 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 247 .setSmallIcon(R.drawable.ic_media_play) 248 .setContentTitle("Title") 249 .setLargeIcon(icon) 250 .setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent")) 251 .createBigContentView() 252 checkIconView(views) { iconView -> 253 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 254 assertThat(iconView.width.toFloat()) 255 .isWithin(1f) 256 .of((iconView.height * 4 / 3).toFloat()) 257 } 258 } 259 260 @Test 261 fun testWideIcon_inBigText_neverNarrowerThanSquare() { 262 val icon = createBitmap(200, 300) 263 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 264 .setSmallIcon(R.drawable.ic_media_play) 265 .setContentTitle("Title") 266 .setLargeIcon(icon) 267 .setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent")) 268 .createBigContentView() 269 checkIconView(views) { iconView -> 270 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 271 assertThat(iconView.width).isEqualTo(iconView.height) 272 } 273 } 274 275 @Test 276 fun testBigPictureStyle_populatesExtrasCompatibly() { 277 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 278 return 279 } 280 281 val bitmap = createBitmap(40, 30) 282 val uri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png") 283 val iconWithUri = Icon.createWithContentUri(uri) 284 val iconWithBitmap = Icon.createWithBitmap(bitmap) 285 val style = Notification.BigPictureStyle() 286 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 287 .setSmallIcon(R.drawable.ic_media_play) 288 .setContentTitle("Title") 289 .setStyle(style) 290 291 style.bigPicture(bitmap) 292 builder.build().let { 293 assertThat( 294 it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)!!.sameAs(bitmap) 295 ).isTrue() 296 assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull() 297 } 298 299 style.bigPicture(iconWithUri) 300 builder.build().let { 301 assertThat(it.extras.get(Notification.EXTRA_PICTURE)).isNull() 302 assertThat(it.extras.getParcelable<Icon>(Notification.EXTRA_PICTURE_ICON)) 303 .isSameInstanceAs(iconWithUri) 304 } 305 306 style.bigPicture(iconWithBitmap) 307 builder.build().let { 308 assertThat( 309 it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)!!.sameAs(bitmap) 310 ).isTrue() 311 assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull() 312 } 313 } 314 315 @Test 316 @CddTest(requirement = "3.8.3.1/C-2-1") 317 fun testBigPictureStyle_bigPictureUriIcon() { 318 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 319 return 320 } 321 322 val pictureUri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png") 323 val pictureIcon = Icon.createWithContentUri(pictureUri) 324 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 325 .setSmallIcon(R.drawable.ic_media_play) 326 .setContentTitle("Title") 327 .setStyle(Notification.BigPictureStyle().bigPicture(pictureIcon)) 328 checkViews(builder.createBigContentView()) { 329 val pictureView = requireViewByIdName<ImageView>("big_picture") 330 assertThat(pictureView.visibility).isEqualTo(View.VISIBLE) 331 assertThat(pictureView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP) 332 } 333 } 334 335 @Test 336 @CddTest(requirement = "3.8.3.1/C-2-1") 337 fun testPromoteBigPicture_withBigPictureUriIcon() { 338 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 339 return 340 } 341 342 val pictureUri = Uri.parse("content://android.app.stubs.assets/picture_800_by_600.png") 343 val pictureIcon = Icon.createWithContentUri(pictureUri) 344 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 345 .setSmallIcon(R.drawable.ic_media_play) 346 .setContentTitle("Title") 347 .setStyle( 348 Notification.BigPictureStyle() 349 .bigPicture(pictureIcon) 350 .showBigPictureWhenCollapsed(true) 351 ) 352 checkIconView(builder.createContentView()) { iconView -> 353 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 354 assertThat(iconView.width.toFloat()) 355 .isWithin(1f) 356 .of((iconView.height * 4 / 3).toFloat()) 357 assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP) 358 } 359 } 360 361 @Test 362 fun testPromoteBigPicture_withoutLargeIcon() { 363 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 364 return 365 } 366 367 val picture = createBitmap(40, 30) 368 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 369 .setSmallIcon(R.drawable.ic_media_play) 370 .setContentTitle("Title") 371 .setStyle( 372 Notification.BigPictureStyle() 373 .bigPicture(picture) 374 .showBigPictureWhenCollapsed(true) 375 ) 376 checkIconView(builder.createContentView()) { iconView -> 377 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 378 assertThat(iconView.width.toFloat()) 379 .isWithin(1f) 380 .of((iconView.height * 4 / 3).toFloat()) 381 assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP) 382 } 383 checkIconView(builder.createBigContentView()) { iconView -> 384 assertThat(iconView.visibility).isEqualTo(View.GONE) 385 } 386 } 387 388 @Test 389 fun testPromoteBigPicture_withLargeIcon() { 390 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 391 return 392 } 393 394 val picture = createBitmap(40, 30) 395 val icon = createBitmap(80, 65) 396 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 397 .setSmallIcon(R.drawable.ic_media_play) 398 .setContentTitle("Title") 399 .setLargeIcon(icon) 400 .setStyle( 401 Notification.BigPictureStyle() 402 .bigPicture(picture) 403 .showBigPictureWhenCollapsed(true) 404 ) 405 406 checkIconView(builder.createContentView()) { iconView -> 407 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 408 assertThat(iconView.width.toFloat()) 409 .isWithin(1f) 410 .of((iconView.height * 4 / 3).toFloat()) 411 assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP) 412 } 413 checkIconView(builder.createBigContentView()) { iconView -> 414 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 415 assertThat(iconView.width.toFloat()) 416 .isWithin(1f) 417 .of((iconView.height * 80 / 65).toFloat()) 418 assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP) 419 } 420 } 421 422 @Test 423 @CddTest(requirement = "3.8.3.1/C-2-1") 424 fun testPromoteBigPicture_withBigLargeIcon() { 425 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 426 return 427 } 428 429 val picture = createBitmap(40, 30) 430 val inputWidth = 400 431 val inputHeight = 300 432 val bigIcon = createBitmap(inputWidth, inputHeight) 433 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 434 .setSmallIcon(R.drawable.ic_media_play) 435 .setContentTitle("Title") 436 .setStyle( 437 Notification.BigPictureStyle() 438 .bigPicture(picture) 439 .bigLargeIcon(bigIcon) 440 .showBigPictureWhenCollapsed(true) 441 ) 442 443 checkIconView(builder.createContentView()) { iconView -> 444 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 445 assertThat(iconView.width.toFloat()) 446 .isWithin(1f) 447 .of((iconView.height * 4 / 3).toFloat()) 448 assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP) 449 } 450 checkIconView(builder.createBigContentView()) { iconView -> 451 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 452 assertThat(iconView.width.toFloat()) 453 .isWithin(1f) 454 .of((iconView.height * 4 / 3).toFloat()) 455 assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP) 456 } 457 assertThat( 458 builder.build().extras.getParcelable<Bitmap>( 459 Notification.EXTRA_PICTURE 460 )!!.sameAs(picture) 461 ).isTrue() 462 } 463 464 @Test 465 @CddTest(requirement = "3.8.3.1/C-2-1") 466 fun testBigPicture_withBigLargeIcon_withContentUri() { 467 if (skipIfPlatformDoesNotSupportNotificationStyles()) { 468 return 469 } 470 471 val iconUri = Uri.parse("content://android.app.stubs.assets/picture_800_by_600.png") 472 val icon = Icon.createWithContentUri(iconUri) 473 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 474 .setSmallIcon(R.drawable.ic_media_play) 475 .setContentTitle("Title") 476 .setStyle(Notification.BigPictureStyle().bigLargeIcon(icon)) 477 checkIconView(builder.createBigContentView()) { iconView -> 478 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 479 assertThat(iconView.width.toFloat()) 480 .isWithin(1f) 481 .of((iconView.height * 4 / 3).toFloat()) 482 assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP) 483 } 484 } 485 486 @Test 487 @SmallTest 488 fun testBaseTemplate_hasExpandedStateWithoutActions() { 489 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 490 .setSmallIcon(R.drawable.ic_media_play) 491 .setContentTitle("Title") 492 .createBigContentView() 493 assertThat(views).isNotNull() 494 } 495 496 @Test 497 fun testDecoratedCustomViewStyle_collapsedState() { 498 val customContent = makeCustomContent() 499 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 500 .setSmallIcon(R.drawable.ic_media_play) 501 .setContentTitle("Title") 502 .setCustomContentView(customContent) 503 .setStyle(Notification.DecoratedCustomViewStyle()) 504 .createContentView() 505 checkViews(views) { 506 // first check that the custom view is actually shown 507 val customTextView = requireViewByIdName<TextView>("text1") 508 assertThat(customTextView.visibility).isEqualTo(View.VISIBLE) 509 assertThat(customTextView.text).isEqualTo("Example Text") 510 511 // check that the icon shows 512 val iconView = requireViewByIdName<ImageView>("icon") 513 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 514 } 515 } 516 517 @Test 518 fun testDecoratedCustomViewStyle_expandedState() { 519 val customContent = makeCustomContent() 520 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 521 .setSmallIcon(R.drawable.ic_media_play) 522 .setContentTitle("Title") 523 .setCustomBigContentView(customContent) 524 .setStyle(Notification.DecoratedCustomViewStyle()) 525 .createBigContentView() 526 checkViews(views) { 527 // first check that the custom view is actually shown 528 val customTextView = requireViewByIdName<TextView>("text1") 529 assertThat(customTextView.visibility).isEqualTo(View.VISIBLE) 530 assertThat(customTextView.text).isEqualTo("Example Text") 531 532 // check that the app name text shows 533 val appNameView = requireViewByIdName<TextView>("app_name_text") 534 assertThat(appNameView.visibility).isEqualTo(View.VISIBLE) 535 536 // check that the icon shows 537 val iconView = requireViewByIdName<ImageView>("icon") 538 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 539 } 540 } 541 542 @Test 543 fun testCustomViewNotification_collapsedState_isDecorated() { 544 val customContent = makeCustomContent() 545 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 546 .setSmallIcon(R.drawable.ic_media_play) 547 .setContentTitle("Title") 548 .setCustomContentView(customContent) 549 .createContentView() 550 checkViews(views) { 551 // first check that the custom view is actually shown 552 val customTextView = requireViewByIdName<TextView>("text1") 553 assertThat(customTextView.visibility).isEqualTo(View.VISIBLE) 554 555 assertThat(customTextView.text).isEqualTo("Example Text") 556 557 // check that the icon shows 558 val iconView = requireViewByIdName<ImageView>("icon") 559 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 560 } 561 } 562 563 @Test 564 fun testCustomViewNotification_expandedState_isDecorated() { 565 val customContent = makeCustomContent() 566 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 567 .setSmallIcon(R.drawable.ic_media_play) 568 .setContentTitle("Title") 569 .setCustomBigContentView(customContent) 570 .createBigContentView() 571 checkViews(views) { 572 // first check that the custom view is actually shown 573 val customTextView = requireViewByIdName<TextView>("text1") 574 assertThat(customTextView.visibility).isEqualTo(View.VISIBLE) 575 assertThat(customTextView.text).isEqualTo("Example Text") 576 577 // check that the app name text shows 578 val appNameView = requireViewByIdName<TextView>("app_name_text") 579 assertThat(appNameView.visibility).isEqualTo(View.VISIBLE) 580 581 // check that the icon shows 582 val iconView = requireViewByIdName<ImageView>("icon") 583 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 584 } 585 } 586 587 @Test 588 fun testCustomViewNotification_headsUpState_isDecorated() { 589 val customContent = makeCustomContent() 590 val views = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 591 .setSmallIcon(R.drawable.ic_media_play) 592 .setContentTitle("Title") 593 .setCustomHeadsUpContentView(customContent) 594 .createHeadsUpContentView() 595 checkViews(views) { 596 // first check that the custom view is actually shown 597 val customTextView = requireViewByIdName<TextView>("text1") 598 assertThat(customTextView.visibility).isEqualTo(View.VISIBLE) 599 assertThat(customTextView.text).isEqualTo("Example Text") 600 601 // check that the icon shows 602 val iconView = requireViewByIdName<ImageView>("icon") 603 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 604 } 605 } 606 607 @Test 608 @SmallTest 609 fun testCallStyle_forIncomingCall_validatesArguments() { 610 val namedPerson = Person.Builder().setName("Named Person").build() 611 val namelessPerson = Person.Builder().setName("").build() 612 assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") { 613 Notification.CallStyle.forIncomingCall(platformNull(), pendingIntent, pendingIntent) 614 } 615 assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") { 616 Notification.CallStyle.forIncomingCall(namelessPerson, pendingIntent, pendingIntent) 617 } 618 assertFailsWith(NullPointerException::class, "declineIntent is required") { 619 Notification.CallStyle.forIncomingCall(namedPerson, platformNull(), pendingIntent) 620 } 621 assertFailsWith(NullPointerException::class, "answerIntent is required") { 622 Notification.CallStyle.forIncomingCall(namedPerson, pendingIntent, platformNull()) 623 } 624 Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 625 .setStyle( 626 Notification.CallStyle 627 .forIncomingCall(namedPerson, pendingIntent, pendingIntent) 628 ) 629 .build() 630 } 631 632 @Test 633 fun testCallStyle_forIncomingCall_hasCorrectActions() { 634 val namedPerson = Person.Builder().setName("Named Person").build() 635 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 636 .setSmallIcon(R.drawable.ic_media_play) 637 .setStyle( 638 Notification.CallStyle 639 .forIncomingCall(namedPerson, pendingIntent, pendingIntent) 640 ) 641 assertThat(builder.build()).isNotNull() 642 val answerText = context.getString(getAndroidRString("call_notification_answer_action")) 643 val declineText = context.getString(getAndroidRString("call_notification_decline_action")) 644 val hangUpText = context.getString(getAndroidRString("call_notification_hang_up_action")) 645 val views = builder.createBigContentView() 646 checkViews(views) { 647 assertThat(requireViewWithTextContaining(answerText).visibility).isEqualTo(View.VISIBLE) 648 assertThat( 649 requireViewWithTextContaining(declineText).visibility 650 ).isEqualTo(View.VISIBLE) 651 assertThat(findViewWithTextContaining(hangUpText)).isNull() 652 } 653 } 654 655 @Test 656 fun testCallStyle_forIncomingCall_isVideo_hasCorrectActions() { 657 val namedPerson = Person.Builder().setName("Named Person").build() 658 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 659 .setSmallIcon(R.drawable.ic_media_play) 660 .setStyle( 661 Notification.CallStyle 662 .forIncomingCall(namedPerson, pendingIntent, pendingIntent) 663 .setIsVideo(true) 664 ) 665 val notification = builder.build() 666 assertThat(notification).isNotNull() 667 assertThat(notification.extras.getBoolean(Notification.EXTRA_CALL_IS_VIDEO)).isTrue() 668 val answerText = context.getString( 669 getAndroidRString("call_notification_answer_video_action") 670 ) 671 val declineText = context.getString(getAndroidRString("call_notification_decline_action")) 672 val hangUpText = context.getString(getAndroidRString("call_notification_hang_up_action")) 673 val views = builder.createBigContentView() 674 checkViews(views) { 675 assertThat(requireViewWithTextContaining(answerText).visibility).isEqualTo(View.VISIBLE) 676 assertThat( 677 requireViewWithTextContaining(declineText).visibility 678 ).isEqualTo(View.VISIBLE) 679 assertThat(findViewWithTextContaining(hangUpText)).isNull() 680 } 681 } 682 683 @Test 684 @SmallTest 685 fun testCallStyle_forOngoingCall_validatesArguments() { 686 val namedPerson = Person.Builder().setName("Named Person").build() 687 val namelessPerson = Person.Builder().setName("").build() 688 assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") { 689 Notification.CallStyle.forOngoingCall(platformNull(), pendingIntent) 690 } 691 assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") { 692 Notification.CallStyle.forOngoingCall(namelessPerson, pendingIntent) 693 } 694 assertFailsWith(NullPointerException::class, "hangUpIntent is required") { 695 Notification.CallStyle.forOngoingCall(namedPerson, platformNull()) 696 } 697 Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 698 .setStyle(Notification.CallStyle.forOngoingCall(namedPerson, pendingIntent)) 699 .build() 700 } 701 702 @Test 703 fun testCallStyle_forOngoingCall_hasCorrectActions() { 704 val namedPerson = Person.Builder().setName("Named Person").build() 705 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 706 .setSmallIcon(R.drawable.ic_media_play) 707 .setStyle(Notification.CallStyle.forOngoingCall(namedPerson, pendingIntent)) 708 assertThat(builder.build()).isNotNull() 709 val answerText = context.getString(getAndroidRString("call_notification_answer_action")) 710 val declineText = context.getString(getAndroidRString("call_notification_decline_action")) 711 val hangUpText = context.getString(getAndroidRString("call_notification_hang_up_action")) 712 val views = builder.createBigContentView() 713 checkViews(views) { 714 assertThat(findViewWithTextContaining(answerText)).isNull() 715 assertThat(findViewWithTextContaining(declineText)).isNull() 716 assertThat(requireViewWithTextContaining(hangUpText).visibility).isEqualTo(View.VISIBLE) 717 } 718 } 719 720 @Test 721 @SmallTest 722 fun testCallStyle_forScreeningCall_validatesArguments() { 723 val namedPerson = Person.Builder().setName("Named Person").build() 724 val namelessPerson = Person.Builder().setName("").build() 725 assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") { 726 Notification.CallStyle.forScreeningCall(platformNull(), pendingIntent, pendingIntent) 727 } 728 assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") { 729 Notification.CallStyle.forScreeningCall(namelessPerson, pendingIntent, pendingIntent) 730 } 731 assertFailsWith(NullPointerException::class, "hangUpIntent is required") { 732 Notification.CallStyle.forScreeningCall(namedPerson, platformNull(), pendingIntent) 733 } 734 assertFailsWith(NullPointerException::class, "answerIntent is required") { 735 Notification.CallStyle.forScreeningCall(namedPerson, pendingIntent, platformNull()) 736 } 737 Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 738 .setStyle( 739 Notification.CallStyle 740 .forScreeningCall(namedPerson, pendingIntent, pendingIntent) 741 ) 742 .build() 743 } 744 745 @Test 746 fun testCallStyle_forScreeningCall_hasCorrectActions() { 747 val namedPerson = Person.Builder().setName("Named Person").build() 748 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 749 .setSmallIcon(R.drawable.ic_media_play) 750 .setStyle( 751 Notification.CallStyle 752 .forScreeningCall(namedPerson, pendingIntent, pendingIntent) 753 ) 754 assertThat(builder.build()).isNotNull() 755 val answerText = context.getString(getAndroidRString("call_notification_answer_action")) 756 val declineText = context.getString(getAndroidRString("call_notification_decline_action")) 757 val hangUpText = context.getString(getAndroidRString("call_notification_hang_up_action")) 758 val views = builder.createBigContentView() 759 checkViews(views) { 760 assertThat(requireViewWithTextContaining(answerText).visibility).isEqualTo(View.VISIBLE) 761 assertThat(findViewWithTextContaining(declineText)).isNull() 762 assertThat(requireViewWithTextContaining(hangUpText).visibility).isEqualTo(View.VISIBLE) 763 } 764 } 765 766 @Test 767 fun testCallStyle_hidesVerification_whenNotProvided() { 768 val person = Person.Builder().setName("Person").build() 769 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 770 .setSmallIcon(R.drawable.ic_media_play) 771 .setStyle( 772 Notification.CallStyle 773 .forIncomingCall(person, pendingIntent, pendingIntent) 774 ) 775 776 val notification = builder.build() 777 val extras = notification.extras 778 assertThat(extras.containsKey(Notification.EXTRA_VERIFICATION_TEXT)).isFalse() 779 assertThat(extras.containsKey(Notification.EXTRA_VERIFICATION_ICON)).isFalse() 780 781 val views = builder.createBigContentView() 782 checkViews(views) { 783 val textView = requireViewByIdName<TextView>("verification_text") 784 assertThat(textView.visibility).isEqualTo(View.GONE) 785 786 val iconView = requireViewByIdName<ImageView>("verification_icon") 787 assertThat(iconView.visibility).isEqualTo(View.GONE) 788 } 789 } 790 791 @Test 792 fun testCallStyle_showsVerification_whenProvided() { 793 val person = Person.Builder().setName("Person").build() 794 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 795 .setSmallIcon(R.drawable.ic_media_play) 796 .setStyle( 797 Notification.CallStyle 798 .forIncomingCall(person, pendingIntent, pendingIntent) 799 .setVerificationIcon(Icon.createWithResource(context, R.drawable.ic_info)) 800 .setVerificationText("Verified!") 801 ) 802 803 val notification = builder.build() 804 val extras = notification.extras 805 assertThat(extras.getCharSequence(Notification.EXTRA_VERIFICATION_TEXT)) 806 .isEqualTo("Verified!") 807 assertThat(extras.getParcelable<Icon>(Notification.EXTRA_VERIFICATION_ICON)?.resId) 808 .isEqualTo(R.drawable.ic_info) 809 810 val views = builder.createBigContentView() 811 checkViews(views) { 812 val textView = requireViewByIdName<TextView>("verification_text") 813 assertThat(textView.visibility).isEqualTo(View.VISIBLE) 814 assertThat(textView.text).isEqualTo("Verified!") 815 816 val iconView = requireViewByIdName<ImageView>("verification_icon") 817 assertThat(iconView.visibility).isEqualTo(View.VISIBLE) 818 } 819 } 820 821 @Test 822 fun testCallStyle_ignoresCustomColors_whenNotColorized() { 823 if (!context.resources.getBoolean(getAndroidRBool( 824 "config_callNotificationActionColorsRequireColorized" 825 ))) { 826 Log.i( 827 TAG, 828 "Skipping: testCallStyle_ignoresCustomColors_whenNotColorized" + 829 " - Test will not run when config disabled." 830 ) 831 return 832 } 833 val person = Person.Builder().setName("Person").build() 834 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 835 .setSmallIcon(R.drawable.ic_media_play) 836 .setColor(Color.WHITE) 837 .setStyle( 838 Notification.CallStyle 839 .forIncomingCall(person, pendingIntent, pendingIntent) 840 .setAnswerButtonColorHint(Color.BLUE) 841 .setDeclineButtonColorHint(Color.MAGENTA) 842 ) 843 844 val notification = builder.build() 845 assertThat(notification.extras.getInt(Notification.EXTRA_ANSWER_COLOR, -1)) 846 .isEqualTo(Color.BLUE) 847 assertThat(notification.extras.getInt(Notification.EXTRA_DECLINE_COLOR, -1)) 848 .isEqualTo(Color.MAGENTA) 849 850 val answerText = context.getString(getAndroidRString("call_notification_answer_action")) 851 val declineText = context.getString(getAndroidRString("call_notification_decline_action")) 852 val views = builder.createBigContentView() 853 checkViews(views) { 854 assertThat( 855 requireViewWithTextContaining(answerText).bgContainsColor(Color.BLUE) 856 ).isFalse() 857 assertThat( 858 requireViewWithTextContaining(declineText).bgContainsColor(Color.MAGENTA) 859 ).isFalse() 860 } 861 } 862 863 @Test 864 fun testCallStyle_usesCustomColors_whenColorized() { 865 val person = Person.Builder().setName("Person").build() 866 val builder = Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 867 .setSmallIcon(R.drawable.ic_media_play) 868 .setColorized(true) 869 .setColor(Color.WHITE) 870 .setStyle( 871 Notification.CallStyle 872 .forIncomingCall(person, pendingIntent, pendingIntent) 873 .setAnswerButtonColorHint(Color.BLUE) 874 .setDeclineButtonColorHint(Color.MAGENTA) 875 ) 876 877 val notification = builder.build() 878 assertThat(notification.extras.getInt(Notification.EXTRA_ANSWER_COLOR, -1)) 879 .isEqualTo(Color.BLUE) 880 assertThat(notification.extras.getInt(Notification.EXTRA_DECLINE_COLOR, -1)) 881 .isEqualTo(Color.MAGENTA) 882 883 // Setting this flag ensures that createBigContentView allows colorization. 884 notification.flags = notification.flags or Notification.FLAG_FOREGROUND_SERVICE 885 val answerText = context.getString(getAndroidRString("call_notification_answer_action")) 886 val declineText = context.getString(getAndroidRString("call_notification_decline_action")) 887 val views = builder.createBigContentView() 888 checkViews(views) { 889 // TODO(b/184896890): diagnose/fix flaky bgContainsColor method 890 assertThat( 891 requireViewWithTextContaining(answerText).bgContainsColor(Color.BLUE) 892 ) // .isTrue() 893 assertThat( 894 requireViewWithTextContaining(declineText).bgContainsColor(Color.MAGENTA) 895 ) // .isTrue() 896 } 897 } 898 899 private fun View.bgContainsColor(@ColorInt color: Int): Boolean { 900 val background = background ?: return false 901 val bitmap = createBitmap(width, height) 902 val canvas = Canvas(bitmap) 903 background.draw(canvas) 904 val maskedColor = color and 0x00ffffff 905 for (x in 0 until bitmap.width) { 906 for (y in 0 until bitmap.height) { 907 if (bitmap.getPixel(x, y) and 0x00ffffff == maskedColor) { 908 return true 909 } 910 } 911 } 912 return false 913 } 914 915 private val pendingIntent by lazy { 916 PendingIntent.getBroadcast(context, 0, Intent("test"), PendingIntent.FLAG_IMMUTABLE) 917 } 918 919 /** 920 * Assume that we're running on the platform that supports styled notifications. 921 * 922 * If the current platform does not support notification styles, skip this test without failure. 923 */ 924 private fun skipIfPlatformDoesNotSupportNotificationStyles(): Boolean { 925 return context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) || 926 context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) 927 } 928 929 companion object { 930 val TAG = NotificationTemplateTest::class.java.simpleName 931 const val NOTIFICATION_CHANNEL_ID = "NotificationTemplateTest" 932 } 933 } 934