blob: cd1de26dd664c63542bf1c8b62a35ddb540e5db0 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.cts
import android.R
import android.app.Notification
import android.app.PendingIntent
import android.app.Person
import android.app.cts.CtsAppTestUtils.platformNull
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.Icon
import android.net.Uri
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.test.filters.SmallTest
import com.android.compatibility.common.util.CddTest
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
class NotificationTemplateTest : NotificationTemplateTestBase() {
fun testWideIcon_inCollapsedState_cappedTo16By9() {
val icon = createBitmap(200, 100)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.createContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 16 / 9).toFloat())
}
}
fun testWideIcon_inCollapsedState_canShowExact4By3() {
val icon = createBitmap(400, 300)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.createContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
}
}
fun testWideIcon_inCollapsedState_canShowUriIcon() {
val uri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
val icon = Icon.createWithContentUri(uri)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.createContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
}
}
fun testWideIcon_inCollapsedState_neverNarrowerThanSquare() {
val icon = createBitmap(200, 300)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.createContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width).isEqualTo(iconView.height)
}
}
fun testWideIcon_inBigBaseState_cappedTo16By9() {
val icon = createBitmap(200, 100)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 16 / 9).toFloat())
}
}
fun testWideIcon_inBigBaseState_canShowExact4By3() {
val icon = createBitmap(400, 300)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
}
}
fun testWideIcon_inBigBaseState_neverNarrowerThanSquare() {
val icon = createBitmap(200, 300)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width).isEqualTo(iconView.height)
}
}
fun testWideIcon_inBigPicture_cappedTo16By9() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val picture = createBitmap(40, 30)
val icon = createBitmap(200, 100)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.setStyle(Notification.BigPictureStyle().bigPicture(picture))
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 16 / 9).toFloat())
}
}
fun testWideIcon_inBigPicture_canShowExact4By3() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val picture = createBitmap(40, 30)
val icon = createBitmap(400, 300)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.setStyle(Notification.BigPictureStyle().bigPicture(picture))
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
}
}
fun testWideIcon_inBigPicture_neverNarrowerThanSquare() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val picture = createBitmap(40, 30)
val icon = createBitmap(200, 300)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.setStyle(Notification.BigPictureStyle().bigPicture(picture))
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width).isEqualTo(iconView.height)
}
}
fun testWideIcon_inBigText_cappedTo16By9() {
val icon = createBitmap(200, 100)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent"))
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 16 / 9).toFloat())
}
}
fun testWideIcon_inBigText_canShowExact4By3() {
val icon = createBitmap(400, 300)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent"))
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
}
}
fun testWideIcon_inBigText_neverNarrowerThanSquare() {
val icon = createBitmap(200, 300)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent"))
.createBigContentView()
checkIconView(views) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width).isEqualTo(iconView.height)
}
}
fun testBigPictureStyle_populatesExtrasCompatibly() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val bitmap = createBitmap(40, 30)
val uri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
val iconWithUri = Icon.createWithContentUri(uri)
val iconWithBitmap = Icon.createWithBitmap(bitmap)
val style = Notification.BigPictureStyle()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setStyle(style)
style.bigPicture(bitmap)
builder.build().let {
assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)
!!.sameAs(bitmap)).isTrue()
assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull()
}
style.bigPicture(iconWithUri)
builder.build().let {
assertThat(it.extras.get(Notification.EXTRA_PICTURE)).isNull()
assertThat(it.extras.getParcelable<Icon>(Notification.EXTRA_PICTURE_ICON))
.isSameInstanceAs(iconWithUri)
}
style.bigPicture(iconWithBitmap)
builder.build().let {
assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)
!!.sameAs(bitmap)).isTrue()
assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull()
}
}
@CddTest(requirement = "3.8.3.1/C-2-1")
fun testBigPictureStyle_bigPictureUriIcon() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val pictureUri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
val pictureIcon = Icon.createWithContentUri(pictureUri)
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setStyle(Notification.BigPictureStyle().bigPicture(pictureIcon))
checkViews(builder.createBigContentView()) {
val pictureView = requireViewByIdName<ImageView>("big_picture")
assertThat(pictureView.visibility).isEqualTo(View.VISIBLE)
assertThat(pictureView.width.toFloat())
.isWithin(1f)
.of((pictureView.height * 4 / 3).toFloat())
assertThat(pictureView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP)
}
}
@CddTest(requirement = "3.8.3.1/C-2-1")
fun testPromoteBigPicture_withBigPictureUriIcon() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val pictureUri = Uri.parse("content://android.app.stubs.assets/picture_800_by_600.png")
val pictureIcon = Icon.createWithContentUri(pictureUri)
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setStyle(Notification.BigPictureStyle()
.bigPicture(pictureIcon)
.showBigPictureWhenCollapsed(true)
)
checkIconView(builder.createContentView()) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP)
}
}
fun testPromoteBigPicture_withoutLargeIcon() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val picture = createBitmap(40, 30)
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setStyle(Notification.BigPictureStyle()
.bigPicture(picture)
.showBigPictureWhenCollapsed(true)
)
checkIconView(builder.createContentView()) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP)
}
checkIconView(builder.createBigContentView()) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.GONE)
}
}
fun testPromoteBigPicture_withLargeIcon() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val picture = createBitmap(40, 30)
val icon = createBitmap(80, 65)
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setLargeIcon(icon)
.setStyle(Notification.BigPictureStyle()
.bigPicture(picture)
.showBigPictureWhenCollapsed(true)
)
checkIconView(builder.createContentView()) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP)
}
checkIconView(builder.createBigContentView()) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 80 / 65).toFloat())
assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP)
}
}
@CddTest(requirement = "3.8.3.1/C-2-1")
fun testPromoteBigPicture_withBigLargeIcon() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val picture = createBitmap(40, 30)
val inputWidth = 400
val inputHeight = 300
val bigIcon = createBitmap(inputWidth, inputHeight)
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setStyle(Notification.BigPictureStyle()
.bigPicture(picture)
.bigLargeIcon(bigIcon)
.showBigPictureWhenCollapsed(true)
)
checkIconView(builder.createContentView()) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP)
}
checkIconView(builder.createBigContentView()) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP)
}
assertThat(builder.build().extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)
!!.sameAs(picture)).isTrue()
}
@CddTest(requirement = "3.8.3.1/C-2-1")
fun testBigPicture_withBigLargeIcon_withContentUri() {
if (skipIfPlatformDoesNotSupportNotificationStyles()) {
return
}
val iconUri = Uri.parse("content://android.app.stubs.assets/picture_800_by_600.png")
val icon = Icon.createWithContentUri(iconUri)
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setStyle(Notification.BigPictureStyle().bigLargeIcon(icon))
checkIconView(builder.createBigContentView()) { iconView ->
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
assertThat(iconView.width.toFloat())
.isWithin(1f)
.of((iconView.height * 4 / 3).toFloat())
assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER_CROP)
}
}
@SmallTest
fun testBaseTemplate_hasExpandedStateWithoutActions() {
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.createBigContentView()
assertThat(views).isNotNull()
}
fun testDecoratedCustomViewStyle_collapsedState() {
val customContent = makeCustomContent()
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setCustomContentView(customContent)
.setStyle(Notification.DecoratedCustomViewStyle())
.createContentView()
checkViews(views) {
// first check that the custom view is actually shown
val customTextView = requireViewByIdName<TextView>("text1")
assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
assertThat(customTextView.text).isEqualTo("Example Text")
// check that the icon shows
val iconView = requireViewByIdName<ImageView>("icon")
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
}
}
fun testDecoratedCustomViewStyle_expandedState() {
val customContent = makeCustomContent()
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setCustomBigContentView(customContent)
.setStyle(Notification.DecoratedCustomViewStyle())
.createBigContentView()
checkViews(views) {
// first check that the custom view is actually shown
val customTextView = requireViewByIdName<TextView>("text1")
assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
assertThat(customTextView.text).isEqualTo("Example Text")
// check that the app name text shows
val appNameView = requireViewByIdName<TextView>("app_name_text")
assertThat(appNameView.visibility).isEqualTo(View.VISIBLE)
// check that the icon shows
val iconView = requireViewByIdName<ImageView>("icon")
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
}
}
fun testCustomViewNotification_collapsedState_isDecorated() {
val customContent = makeCustomContent()
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setCustomContentView(customContent)
.createContentView()
checkViews(views) {
// first check that the custom view is actually shown
val customTextView = requireViewByIdName<TextView>("text1")
assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
assertThat(customTextView.text).isEqualTo("Example Text")
// check that the icon shows
val iconView = requireViewByIdName<ImageView>("icon")
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
}
}
fun testCustomViewNotification_expandedState_isDecorated() {
val customContent = makeCustomContent()
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setCustomBigContentView(customContent)
.createBigContentView()
checkViews(views) {
// first check that the custom view is actually shown
val customTextView = requireViewByIdName<TextView>("text1")
assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
assertThat(customTextView.text).isEqualTo("Example Text")
// check that the app name text shows
val appNameView = requireViewByIdName<TextView>("app_name_text")
assertThat(appNameView.visibility).isEqualTo(View.VISIBLE)
// check that the icon shows
val iconView = requireViewByIdName<ImageView>("icon")
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
}
}
fun testCustomViewNotification_headsUpState_isDecorated() {
val customContent = makeCustomContent()
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setContentTitle("Title")
.setCustomHeadsUpContentView(customContent)
.createHeadsUpContentView()
checkViews(views) {
// first check that the custom view is actually shown
val customTextView = requireViewByIdName<TextView>("text1")
assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
assertThat(customTextView.text).isEqualTo("Example Text")
// check that the icon shows
val iconView = requireViewByIdName<ImageView>("icon")
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
}
}
@SmallTest
fun testCallStyle_forIncomingCall_validatesArguments() {
val namedPerson = Person.Builder().setName("Named Person").build()
val namelessPerson = Person.Builder().setName("").build()
assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
Notification.CallStyle.forIncomingCall(platformNull(), pendingIntent, pendingIntent)
}
assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
Notification.CallStyle.forIncomingCall(namelessPerson, pendingIntent, pendingIntent)
}
assertFailsWith(NullPointerException::class, "declineIntent is required") {
Notification.CallStyle.forIncomingCall(namedPerson, platformNull(), pendingIntent)
}
assertFailsWith(NullPointerException::class, "answerIntent is required") {
Notification.CallStyle.forIncomingCall(namedPerson, pendingIntent, platformNull())
}
Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setStyle(Notification.CallStyle
.forIncomingCall(namedPerson, pendingIntent, pendingIntent))
.build()
}
fun testCallStyle_forIncomingCall_hasCorrectActions() {
val namedPerson = Person.Builder().setName("Named Person").build()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setStyle(Notification.CallStyle
.forIncomingCall(namedPerson, pendingIntent, pendingIntent))
assertThat(builder.build()).isNotNull()
val answerText = mContext.getString(getAndroidRString("call_notification_answer_action"))
val declineText = mContext.getString(getAndroidRString("call_notification_decline_action"))
val hangUpText = mContext.getString(getAndroidRString("call_notification_hang_up_action"))
val views = builder.createBigContentView()
checkViews(views) {
assertThat(requireViewWithText(answerText).visibility).isEqualTo(View.VISIBLE)
assertThat(requireViewWithText(declineText).visibility).isEqualTo(View.VISIBLE)
assertThat(findViewWithText(hangUpText)).isNull()
}
}
fun testCallStyle_forIncomingCall_isVideo_hasCorrectActions() {
val namedPerson = Person.Builder().setName("Named Person").build()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setStyle(Notification.CallStyle
.forIncomingCall(namedPerson, pendingIntent, pendingIntent)
.setIsVideo(true))
val notification = builder.build()
assertThat(notification).isNotNull()
assertThat(notification.extras.getBoolean(Notification.EXTRA_CALL_IS_VIDEO)).isTrue()
val answerText = mContext.getString(
getAndroidRString("call_notification_answer_video_action"))
val declineText = mContext.getString(getAndroidRString("call_notification_decline_action"))
val hangUpText = mContext.getString(getAndroidRString("call_notification_hang_up_action"))
val views = builder.createBigContentView()
checkViews(views) {
assertThat(requireViewWithText(answerText).visibility).isEqualTo(View.VISIBLE)
assertThat(requireViewWithText(declineText).visibility).isEqualTo(View.VISIBLE)
assertThat(findViewWithText(hangUpText)).isNull()
}
}
@SmallTest
fun testCallStyle_forOngoingCall_validatesArguments() {
val namedPerson = Person.Builder().setName("Named Person").build()
val namelessPerson = Person.Builder().setName("").build()
assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
Notification.CallStyle.forOngoingCall(platformNull(), pendingIntent)
}
assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
Notification.CallStyle.forOngoingCall(namelessPerson, pendingIntent)
}
assertFailsWith(NullPointerException::class, "hangUpIntent is required") {
Notification.CallStyle.forOngoingCall(namedPerson, platformNull())
}
Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setStyle(Notification.CallStyle.forOngoingCall(namedPerson, pendingIntent))
.build()
}
fun testCallStyle_forOngoingCall_hasCorrectActions() {
val namedPerson = Person.Builder().setName("Named Person").build()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setStyle(Notification.CallStyle.forOngoingCall(namedPerson, pendingIntent))
assertThat(builder.build()).isNotNull()
val answerText = mContext.getString(getAndroidRString("call_notification_answer_action"))
val declineText = mContext.getString(getAndroidRString("call_notification_decline_action"))
val hangUpText = mContext.getString(getAndroidRString("call_notification_hang_up_action"))
val views = builder.createBigContentView()
checkViews(views) {
assertThat(findViewWithText(answerText)).isNull()
assertThat(findViewWithText(declineText)).isNull()
assertThat(requireViewWithText(hangUpText).visibility).isEqualTo(View.VISIBLE)
}
}
@SmallTest
fun testCallStyle_forScreeningCall_validatesArguments() {
val namedPerson = Person.Builder().setName("Named Person").build()
val namelessPerson = Person.Builder().setName("").build()
assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
Notification.CallStyle.forScreeningCall(platformNull(), pendingIntent, pendingIntent)
}
assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
Notification.CallStyle.forScreeningCall(namelessPerson, pendingIntent, pendingIntent)
}
assertFailsWith(NullPointerException::class, "hangUpIntent is required") {
Notification.CallStyle.forScreeningCall(namedPerson, platformNull(), pendingIntent)
}
assertFailsWith(NullPointerException::class, "answerIntent is required") {
Notification.CallStyle.forScreeningCall(namedPerson, pendingIntent, platformNull())
}
Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setStyle(Notification.CallStyle
.forScreeningCall(namedPerson, pendingIntent, pendingIntent))
.build()
}
fun testCallStyle_forScreeningCall_hasCorrectActions() {
val namedPerson = Person.Builder().setName("Named Person").build()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setStyle(Notification.CallStyle
.forScreeningCall(namedPerson, pendingIntent, pendingIntent))
assertThat(builder.build()).isNotNull()
val answerText = mContext.getString(getAndroidRString("call_notification_answer_action"))
val declineText = mContext.getString(getAndroidRString("call_notification_decline_action"))
val hangUpText = mContext.getString(getAndroidRString("call_notification_hang_up_action"))
val views = builder.createBigContentView()
checkViews(views) {
assertThat(requireViewWithText(answerText).visibility).isEqualTo(View.VISIBLE)
assertThat(findViewWithText(declineText)).isNull()
assertThat(requireViewWithText(hangUpText).visibility).isEqualTo(View.VISIBLE)
}
}
fun testCallStyle_hidesVerification_whenNotProvided() {
val person = Person.Builder().setName("Person").build()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setStyle(Notification.CallStyle
.forIncomingCall(person, pendingIntent, pendingIntent))
val notification = builder.build()
val extras = notification.extras
assertThat(extras.containsKey(Notification.EXTRA_VERIFICATION_TEXT)).isFalse()
assertThat(extras.containsKey(Notification.EXTRA_VERIFICATION_ICON)).isFalse()
val views = builder.createBigContentView()
checkViews(views) {
val textView = requireViewByIdName<TextView>("verification_text")
assertThat(textView.visibility).isEqualTo(View.GONE)
val iconView = requireViewByIdName<ImageView>("verification_icon")
assertThat(iconView.visibility).isEqualTo(View.GONE)
}
}
fun testCallStyle_showsVerification_whenProvided() {
val person = Person.Builder().setName("Person").build()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setStyle(Notification.CallStyle
.forIncomingCall(person, pendingIntent, pendingIntent)
.setVerificationIcon(Icon.createWithResource(mContext, R.drawable.ic_info))
.setVerificationText("Verified!"))
val notification = builder.build()
val extras = notification.extras
assertThat(extras.getCharSequence(Notification.EXTRA_VERIFICATION_TEXT))
.isEqualTo("Verified!")
assertThat(extras.getParcelable<Icon>(Notification.EXTRA_VERIFICATION_ICON)?.resId)
.isEqualTo(R.drawable.ic_info)
val views = builder.createBigContentView()
checkViews(views) {
val textView = requireViewByIdName<TextView>("verification_text")
assertThat(textView.visibility).isEqualTo(View.VISIBLE)
assertThat(textView.text).isEqualTo("Verified!")
val iconView = requireViewByIdName<ImageView>("verification_icon")
assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
}
}
fun testCallStyle_ignoresCustomColors_whenNotColorized() {
if (!mContext.resources.getBoolean(getAndroidRBool(
"config_callNotificationActionColorsRequireColorized"))) {
Log.i(TAG, "Skipping: testCallStyle_ignoresCustomColors_whenNotColorized" +
" - Test will not run when config disabled.")
return
}
val person = Person.Builder().setName("Person").build()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setColor(Color.WHITE)
.setStyle(Notification.CallStyle
.forIncomingCall(person, pendingIntent, pendingIntent)
.setAnswerButtonColorHint(Color.BLUE)
.setDeclineButtonColorHint(Color.MAGENTA))
val notification = builder.build()
assertThat(notification.extras.getInt(Notification.EXTRA_ANSWER_COLOR, -1))
.isEqualTo(Color.BLUE)
assertThat(notification.extras.getInt(Notification.EXTRA_DECLINE_COLOR, -1))
.isEqualTo(Color.MAGENTA)
val answerText = mContext.getString(getAndroidRString("call_notification_answer_action"))
val declineText = mContext.getString(getAndroidRString("call_notification_decline_action"))
val views = builder.createBigContentView()
checkViews(views) {
assertThat(requireViewWithText(answerText).bgContainsColor(Color.BLUE)).isFalse()
assertThat(requireViewWithText(declineText).bgContainsColor(Color.MAGENTA)).isFalse()
}
}
fun testCallStyle_usesCustomColors_whenColorized() {
val person = Person.Builder().setName("Person").build()
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_media_play)
.setColorized(true)
.setColor(Color.WHITE)
.setStyle(Notification.CallStyle
.forIncomingCall(person, pendingIntent, pendingIntent)
.setAnswerButtonColorHint(Color.BLUE)
.setDeclineButtonColorHint(Color.MAGENTA))
val notification = builder.build()
assertThat(notification.extras.getInt(Notification.EXTRA_ANSWER_COLOR, -1))
.isEqualTo(Color.BLUE)
assertThat(notification.extras.getInt(Notification.EXTRA_DECLINE_COLOR, -1))
.isEqualTo(Color.MAGENTA)
// Setting this flag ensures that createBigContentView allows colorization.
notification.flags = notification.flags or Notification.FLAG_FOREGROUND_SERVICE
val answerText = mContext.getString(getAndroidRString("call_notification_answer_action"))
val declineText = mContext.getString(getAndroidRString("call_notification_decline_action"))
val views = builder.createBigContentView()
checkViews(views) {
// TODO(b/184896890): diagnose/fix flaky bgContainsColor method
assertThat(requireViewWithText(answerText).bgContainsColor(Color.BLUE)) // .isTrue()
assertThat(requireViewWithText(declineText).bgContainsColor(Color.MAGENTA)) // .isTrue()
}
}
private fun View.bgContainsColor(@ColorInt color: Int): Boolean {
val background = background ?: return false
val bitmap = createBitmap(width, height)
val canvas = Canvas(bitmap)
background.draw(canvas)
val maskedColor = color and 0x00ffffff
for (x in 0 until bitmap.width) {
for (y in 0 until bitmap.height) {
if (bitmap.getPixel(x, y) and 0x00ffffff == maskedColor) {
return true
}
}
}
return false
}
private val pendingIntent by lazy {
PendingIntent.getBroadcast(mContext, 0, Intent("test"), PendingIntent.FLAG_IMMUTABLE)
}
/**
* Assume that we're running on the platform that supports styled notifications.
*
* If the current platform does not support notification styles, skip this test without failure.
*/
private fun skipIfPlatformDoesNotSupportNotificationStyles(): Boolean {
return mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) ||
mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
}
companion object {
val TAG = NotificationTemplateTest::class.java.simpleName
const val NOTIFICATION_CHANNEL_ID = "NotificationTemplateTest"
}
}