| /* |
| * 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.ActivityManager |
| 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.math.min |
| 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 (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testWideIcon_inBigPicture_cappedTo16By9" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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 (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testWideIcon_inBigPicture_canShowExact4By3" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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 (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testWideIcon_inBigPicture_neverNarrowerThanSquare" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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 (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testBigPictureStyle_populatesExtrasCompatibly" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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 (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testBigPictureStyle_bigPictureUriIcon" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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) |
| |
| var expectedWidth = min(400, bigPictureWidth()) |
| var expectedHeight = min(300, bigPictureWidth() * 3 / 4) |
| // It's possible that big picture width is configured smaller than we expect here. |
| // In that situation, we need to flip the expected size. |
| if (bigPictureHeight() < expectedHeight) { |
| expectedHeight = bigPictureHeight() |
| expectedWidth = bigPictureHeight() * 4 / 3 |
| } |
| |
| assertThat(pictureView.drawable.intrinsicWidth).isEqualTo(expectedWidth) |
| assertThat(pictureView.drawable.intrinsicHeight).isEqualTo(expectedHeight) |
| } |
| } |
| |
| @CddTest(requirement = "3.8.3.1/C-2-1") |
| fun testPromoteBigPicture_withBigPictureUriIcon() { |
| if (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testPromoteBigPicture_withBigPictureUriIcon" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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.drawable.intrinsicWidth).isEqualTo(rightIconSize()) |
| assertThat(iconView.drawable.intrinsicHeight).isEqualTo(rightIconSize() * 3 / 4) |
| } |
| } |
| |
| fun testPromoteBigPicture_withoutLargeIcon() { |
| if (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testPromoteBigPicture_withoutLargeIcon" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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.drawable.intrinsicWidth).isEqualTo(40) |
| assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30) |
| } |
| checkIconView(builder.createBigContentView()) { iconView -> |
| assertThat(iconView.visibility).isEqualTo(View.GONE) |
| } |
| } |
| |
| fun testPromoteBigPicture_withLargeIcon() { |
| if (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testPromoteBigPicture_withLargeIcon" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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) |
| ) |
| |
| // At really high densities the size of rendered icon can dip below the |
| // tested size - we allow rendering of smaller icon with the same |
| // aspect ratio then. |
| val expectedIconWidth = minOf(rightIconSize(), 80) |
| val expectedIconHeight = minOf(rightIconSize() * 65 / 80, 65) |
| |
| checkIconView(builder.createContentView()) { iconView -> |
| assertThat(iconView.visibility).isEqualTo(View.VISIBLE) |
| assertThat(iconView.width.toFloat()) |
| .isWithin(1f) |
| .of((iconView.height * 4 / 3).toFloat()) |
| assertThat(iconView.drawable.intrinsicWidth).isEqualTo(40) |
| assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30) |
| } |
| checkIconView(builder.createBigContentView()) { iconView -> |
| assertThat(iconView.visibility).isEqualTo(View.VISIBLE) |
| assertThat(iconView.width.toFloat()) |
| .isWithin(1f) |
| .of((iconView.height * 80 / 65).toFloat()) |
| assertThat(iconView.drawable.intrinsicWidth).isEqualTo(expectedIconWidth) |
| assertThat(iconView.drawable.intrinsicHeight).isEqualTo(expectedIconHeight) |
| } |
| } |
| |
| @CddTest(requirement = "3.8.3.1/C-2-1") |
| fun testPromoteBigPicture_withBigLargeIcon() { |
| if (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testPromoteBigPicture_withBigLargeIcon" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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) |
| ) |
| |
| val expectedIconWidth = minOf(rightIconSize(), inputWidth) |
| val expectedIconHeight = minOf(rightIconSize() * inputHeight / inputWidth, inputHeight) |
| |
| checkIconView(builder.createContentView()) { iconView -> |
| assertThat(iconView.visibility).isEqualTo(View.VISIBLE) |
| assertThat(iconView.width.toFloat()) |
| .isWithin(1f) |
| .of((iconView.height * 4 / 3).toFloat()) |
| assertThat(iconView.drawable.intrinsicWidth).isEqualTo(40) |
| assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30) |
| } |
| checkIconView(builder.createBigContentView()) { iconView -> |
| assertThat(iconView.visibility).isEqualTo(View.VISIBLE) |
| assertThat(iconView.width.toFloat()) |
| .isWithin(1f) |
| .of((iconView.height * 4 / 3).toFloat()) |
| assertThat(iconView.drawable.intrinsicWidth).isEqualTo(expectedIconWidth) |
| assertThat(iconView.drawable.intrinsicHeight).isEqualTo(expectedIconHeight) |
| } |
| 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 (isPlatformAutomotive()) { |
| Log.i(TAG, "Skipping: testBigPicture_withBigLargeIcon_withContentUri" + |
| " - BigPictureStyle is not supported in automotive.") |
| 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.drawable.intrinsicWidth).isEqualTo(rightIconSize()) |
| assertThat(iconView.drawable.intrinsicHeight).isEqualTo(rightIconSize() * 3 / 4) |
| } |
| } |
| |
| @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) |
| } |
| |
| private fun rightIconSize(): Int { |
| return mContext.resources.getDimensionPixelSize(getAndroidRDimen( |
| if (isLowRamDevice()) { |
| "notification_right_icon_size_low_ram" |
| } else { |
| "notification_right_icon_size" |
| })) |
| } |
| |
| private fun bigPictureWidth(): Int { |
| return mContext.resources.getDimensionPixelSize(getAndroidRDimen( |
| if (isLowRamDevice()) { |
| "notification_big_picture_max_width_low_ram" |
| } else { |
| "notification_big_picture_max_width" |
| })) |
| } |
| |
| private fun bigPictureHeight(): Int { |
| return mContext.resources.getDimensionPixelSize(getAndroidRDimen( |
| if (isLowRamDevice()) { |
| "notification_big_picture_max_width_low_ram" |
| } else { |
| "notification_big_picture_max_width" |
| })) |
| } |
| |
| private fun isLowRamDevice(): Boolean { |
| return mContext.getSystemService(ActivityManager::class.java).isLowRamDevice() |
| } |
| |
| private fun isPlatformAutomotive(): Boolean { |
| return mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) |
| } |
| |
| companion object { |
| val TAG = NotificationTemplateTest::class.java.simpleName |
| const val NOTIFICATION_CHANNEL_ID = "NotificationTemplateTest" |
| } |
| } |