diff options
2 files changed, 359 insertions, 55 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt index cc1103de8ec0..abe067039cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt @@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.logging import android.app.StatsManager import android.util.Log import android.util.StatsEvent +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -143,67 +144,70 @@ constructor( runBlocking(mainDispatcher) { traceSection("NML#getNotifications") { notificationPipeline.allNotifs } } +} - /** Aggregates memory usage data by package and style, returning sums. */ - private fun aggregateMemoryUsageData( - notificationMemoryUse: List<NotificationMemoryUsage> - ): Map<Pair<String, Int>, NotificationMemoryUseAtomBuilder> { - return notificationMemoryUse - .groupingBy { Pair(it.packageName, it.objectUsage.style) } - .aggregate { - _, - accumulator: NotificationMemoryUseAtomBuilder?, - element: NotificationMemoryUsage, - first -> - val use = - if (first) { - NotificationMemoryUseAtomBuilder(element.uid, element.objectUsage.style) - } else { - accumulator!! - } - - use.count++ - // If the views of the notification weren't inflated, the list of memory usage - // parameters will be empty. - if (element.viewUsage.isNotEmpty()) { - use.countWithInflatedViews++ +/** Aggregates memory usage data by package and style, returning sums. */ +@VisibleForTesting +internal fun aggregateMemoryUsageData( + notificationMemoryUse: List<NotificationMemoryUsage> +): Map<Pair<String, Int>, NotificationMemoryLogger.NotificationMemoryUseAtomBuilder> { + return notificationMemoryUse + .groupingBy { Pair(it.packageName, it.objectUsage.style) } + .aggregate { + _, + accumulator: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder?, + element: NotificationMemoryUsage, + first -> + val use = + if (first) { + NotificationMemoryLogger.NotificationMemoryUseAtomBuilder( + element.uid, + element.objectUsage.style + ) + } else { + accumulator!! } - use.smallIconObject += element.objectUsage.smallIcon - if (element.objectUsage.smallIcon > 0) { - use.smallIconBitmapCount++ - } + use.count++ + // If the views of the notification weren't inflated, the list of memory usage + // parameters will be empty. + if (element.viewUsage.isNotEmpty()) { + use.countWithInflatedViews++ + } - use.largeIconObject += element.objectUsage.largeIcon - if (element.objectUsage.largeIcon > 0) { - use.largeIconBitmapCount++ - } + use.smallIconObject += element.objectUsage.smallIcon + if (element.objectUsage.smallIcon > 0) { + use.smallIconBitmapCount++ + } - use.bigPictureObject += element.objectUsage.bigPicture - if (element.objectUsage.bigPicture > 0) { - use.bigPictureBitmapCount++ - } + use.largeIconObject += element.objectUsage.largeIcon + if (element.objectUsage.largeIcon > 0) { + use.largeIconBitmapCount++ + } - use.extras += element.objectUsage.extras - use.extenders += element.objectUsage.extender - - // Use totals count which are more accurate when aggregated - // in this manner. - element.viewUsage - .firstOrNull { vu -> vu.viewType == ViewType.TOTAL } - ?.let { - use.smallIconViews += it.smallIcon - use.largeIconViews += it.largeIcon - use.systemIconViews += it.systemIcons - use.styleViews += it.style - use.customViews += it.style - use.softwareBitmaps += it.softwareBitmapsPenalty - } - - return@aggregate use + use.bigPictureObject += element.objectUsage.bigPicture + if (element.objectUsage.bigPicture > 0) { + use.bigPictureBitmapCount++ } - } - /** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */ - private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt() + use.extras += element.objectUsage.extras + use.extenders += element.objectUsage.extender + + // Use totals count which are more accurate when aggregated + // in this manner. + element.viewUsage + .firstOrNull { vu -> vu.viewType == ViewType.TOTAL } + ?.let { + use.smallIconViews += it.smallIcon + use.largeIconViews += it.largeIcon + use.systemIconViews += it.systemIcons + use.styleViews += it.style + use.customViews += it.customViews + use.softwareBitmaps += it.softwareBitmapsPenalty + } + + return@aggregate use + } } +/** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */ +private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt index bd039031cecc..33a838ed5183 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt @@ -20,6 +20,7 @@ import android.app.Notification import android.app.StatsManager import android.graphics.Bitmap import android.graphics.drawable.Icon +import android.stats.sysui.NotificationEnums import android.testing.AndroidTestingRunner import android.util.StatsEvent import androidx.test.filters.SmallTest @@ -31,10 +32,12 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat import java.lang.RuntimeException import kotlinx.coroutines.Dispatchers import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -45,6 +48,8 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) class NotificationMemoryLoggerTest : SysuiTestCase() { + @Rule @JvmField val expect = Expect.create() + private val bgExecutor = FakeExecutor(FakeSystemClock()) private val immediate = Dispatchers.Main.immediate @@ -132,6 +137,123 @@ class NotificationMemoryLoggerTest : SysuiTestCase() { .isEqualTo(StatsManager.PULL_SKIP) } + @Test + fun aggregateMemoryUsageData_returnsCorrectlyAggregatedSamePackageData() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)) + + // Aggregated fields + val aggregatedData = + aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)]!! + val presetUsage1 = usage[0] + val presetUsage2 = usage[1] + assertAggregatedData( + aggregatedData, + 2, + 2, + smallIconObject = + presetUsage1.objectUsage.smallIcon + presetUsage2.objectUsage.smallIcon, + smallIconBitmapCount = 2, + largeIconObject = + presetUsage1.objectUsage.largeIcon + presetUsage2.objectUsage.largeIcon, + largeIconBitmapCount = 2, + bigPictureObject = + presetUsage1.objectUsage.bigPicture + presetUsage2.objectUsage.bigPicture, + bigPictureBitmapCount = 2, + extras = presetUsage1.objectUsage.extras + presetUsage2.objectUsage.extras, + extenders = presetUsage1.objectUsage.extender + presetUsage2.objectUsage.extender, + // Only totals need to be summarized. + smallIconViews = + presetUsage1.viewUsage[0].smallIcon + presetUsage2.viewUsage[0].smallIcon, + largeIconViews = + presetUsage1.viewUsage[0].largeIcon + presetUsage2.viewUsage[0].largeIcon, + systemIconViews = + presetUsage1.viewUsage[0].systemIcons + presetUsage2.viewUsage[0].systemIcons, + styleViews = presetUsage1.viewUsage[0].style + presetUsage2.viewUsage[0].style, + customViews = + presetUsage1.viewUsage[0].customViews + presetUsage2.viewUsage[0].customViews, + softwareBitmaps = + presetUsage1.viewUsage[0].softwareBitmapsPenalty + + presetUsage2.viewUsage[0].softwareBitmapsPenalty, + seenCount = 0 + ) + } + + @Test + fun aggregateMemoryUsageData_correctlySeparatesDifferentStyles() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)) + assertThat(aggregateUsage).containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)) + + // Different style should be separate + val separateStyleData = + aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)]!! + val presetUsage = usage[2] + assertAggregatedData( + separateStyleData, + 1, + 1, + presetUsage.objectUsage.smallIcon, + 1, + presetUsage.objectUsage.largeIcon, + 1, + presetUsage.objectUsage.bigPicture, + 1, + presetUsage.objectUsage.extras, + presetUsage.objectUsage.extender, + presetUsage.viewUsage[0].smallIcon, + presetUsage.viewUsage[0].largeIcon, + presetUsage.viewUsage[0].systemIcons, + presetUsage.viewUsage[0].style, + presetUsage.viewUsage[0].customViews, + presetUsage.viewUsage[0].softwareBitmapsPenalty, + 0 + ) + } + + @Test + fun aggregateMemoryUsageData_correctlySeparatesDifferentProcess() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)) + + // Different UID/package should also be separate + val separatePackageData = + aggregateUsage[Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)]!! + val presetUsage = usage[3] + assertAggregatedData( + separatePackageData, + 1, + 1, + presetUsage.objectUsage.smallIcon, + 1, + presetUsage.objectUsage.largeIcon, + 1, + presetUsage.objectUsage.bigPicture, + 1, + presetUsage.objectUsage.extras, + presetUsage.objectUsage.extender, + presetUsage.viewUsage[0].smallIcon, + presetUsage.viewUsage[0].largeIcon, + presetUsage.viewUsage[0].systemIcons, + presetUsage.viewUsage[0].style, + presetUsage.viewUsage[0].customViews, + presetUsage.viewUsage[0].softwareBitmapsPenalty, + 0 + ) + } + private fun createLoggerWithNotifications( notifications: List<Notification> ): NotificationMemoryLogger { @@ -143,4 +265,182 @@ class NotificationMemoryLoggerTest : SysuiTestCase() { whenever(pipeline.allNotifs).thenReturn(notifications) return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor) } + + /** + * Short hand for making sure the passed NotificationMemoryUseAtomBuilder object contains + * expected values. + */ + private fun assertAggregatedData( + value: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder, + count: Int, + countWithInflatedViews: Int, + smallIconObject: Int, + smallIconBitmapCount: Int, + largeIconObject: Int, + largeIconBitmapCount: Int, + bigPictureObject: Int, + bigPictureBitmapCount: Int, + extras: Int, + extenders: Int, + smallIconViews: Int, + largeIconViews: Int, + systemIconViews: Int, + styleViews: Int, + customViews: Int, + softwareBitmaps: Int, + seenCount: Int + ) { + expect.withMessage("count").that(value.count).isEqualTo(count) + expect + .withMessage("countWithInflatedViews") + .that(value.countWithInflatedViews) + .isEqualTo(countWithInflatedViews) + expect.withMessage("smallIconObject").that(value.smallIconObject).isEqualTo(smallIconObject) + expect + .withMessage("smallIconBitmapCount") + .that(value.smallIconBitmapCount) + .isEqualTo(smallIconBitmapCount) + expect.withMessage("largeIconObject").that(value.largeIconObject).isEqualTo(largeIconObject) + expect + .withMessage("largeIconBitmapCount") + .that(value.largeIconBitmapCount) + .isEqualTo(largeIconBitmapCount) + expect + .withMessage("bigPictureObject") + .that(value.bigPictureObject) + .isEqualTo(bigPictureObject) + expect + .withMessage("bigPictureBitmapCount") + .that(value.bigPictureBitmapCount) + .isEqualTo(bigPictureBitmapCount) + expect.withMessage("extras").that(value.extras).isEqualTo(extras) + expect.withMessage("extenders").that(value.extenders).isEqualTo(extenders) + expect.withMessage("smallIconViews").that(value.smallIconViews).isEqualTo(smallIconViews) + expect.withMessage("largeIconViews").that(value.largeIconViews).isEqualTo(largeIconViews) + expect.withMessage("systemIconViews").that(value.systemIconViews).isEqualTo(systemIconViews) + expect.withMessage("styleViews").that(value.styleViews).isEqualTo(styleViews) + expect.withMessage("customViews").that(value.customViews).isEqualTo(customViews) + expect.withMessage("softwareBitmaps").that(value.softwareBitmaps).isEqualTo(softwareBitmaps) + expect.withMessage("seenCount").that(value.seenCount).isEqualTo(seenCount) + } + + /** Generates a static set of [NotificationMemoryUsage] objects. */ + private fun getPresetMemoryUsages() = + listOf( + // A pair of notifications that have to be aggregated, same UID and style + NotificationMemoryUsage( + "package 1", + 384, + "key1", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 23, + 45, + 67, + NotificationEnums.STYLE_BIG_PICTURE, + 12, + 483, + 4382, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 493, 584, 4833, 584, 4888, 5843), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 100, + 250, + 300, + 594, + 6000, + 5843 + ) + ) + ), + NotificationMemoryUsage( + "package 1", + 384, + "key2", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 77, + 54, + 34, + NotificationEnums.STYLE_BIG_PICTURE, + 77, + 432, + 2342, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 160, + 350, + 300, + 5544, + 66500, + 5433 + ) + ) + ), + // Different style is different aggregation + NotificationMemoryUsage( + "package 1", + 384, + "key2", + Notification.Builder(context).setStyle(Notification.BigTextStyle()).build(), + NotificationObjectUsage( + 77, + 54, + 34, + NotificationEnums.STYLE_BIG_TEXT, + 77, + 432, + 2342, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 160, + 350, + 300, + 5544, + 66500, + 5433 + ) + ) + ), + // Different package is also different aggregation + NotificationMemoryUsage( + "package 2", + 684, + "key2", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 32, + 654, + 234, + NotificationEnums.STYLE_BIG_PICTURE, + 211, + 776, + 435, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 4355, 6543, 4322, 5435, 6546, 65485), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 6546, + 7657, + 4353, + 6546, + 76575, + 54654 + ) + ) + ) + ) } |