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