1 /* <lambda>null2 * Copyright (C) 2022 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 17 package com.android.systemui.statusbar.notification.logging 18 19 import android.app.Notification 20 import android.app.StatsManager 21 import android.graphics.Bitmap 22 import android.graphics.drawable.Icon 23 import android.stats.sysui.NotificationEnums 24 import android.util.StatsEvent 25 import androidx.test.ext.junit.runners.AndroidJUnit4 26 import androidx.test.filters.SmallTest 27 import com.android.systemui.SysuiTestCase 28 import com.android.systemui.log.assertLogsWtf 29 import com.android.systemui.shared.system.SysUiStatsLog 30 import com.android.systemui.statusbar.notification.collection.NotifPipeline 31 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder 32 import com.android.systemui.util.concurrency.FakeExecutor 33 import com.android.systemui.util.mockito.mock 34 import com.android.systemui.util.mockito.whenever 35 import com.android.systemui.util.time.FakeSystemClock 36 import com.google.common.truth.Expect 37 import com.google.common.truth.Truth.assertThat 38 import java.lang.RuntimeException 39 import kotlinx.coroutines.Dispatchers 40 import org.junit.Before 41 import org.junit.Rule 42 import org.junit.Test 43 import org.junit.runner.RunWith 44 import org.mockito.Mock 45 import org.mockito.Mockito.verify 46 import org.mockito.MockitoAnnotations 47 48 @SmallTest 49 @RunWith(AndroidJUnit4::class) 50 class NotificationMemoryLoggerTest : SysuiTestCase() { 51 52 @Rule @JvmField val expect = Expect.create() 53 54 private val bgExecutor = FakeExecutor(FakeSystemClock()) 55 private val immediate = Dispatchers.Main.immediate 56 57 @Mock private lateinit var statsManager: StatsManager 58 59 @Before 60 fun setUp() { 61 MockitoAnnotations.initMocks(this) 62 } 63 64 @Test 65 fun onInit_registersCallback() { 66 val logger = createLoggerWithNotifications(listOf()) 67 logger.init() 68 verify(statsManager) 69 .setPullAtomCallback(SysUiStatsLog.NOTIFICATION_MEMORY_USE, null, bgExecutor, logger) 70 } 71 72 @Test 73 fun onPullAtom_wrongAtomId_returnsSkip() { 74 val logger = createLoggerWithNotifications(listOf()) 75 val data: MutableList<StatsEvent> = mutableListOf() 76 assertThat(logger.onPullAtom(111, data)).isEqualTo(StatsManager.PULL_SKIP) 77 assertThat(data).isEmpty() 78 } 79 80 @Test 81 fun onPullAtom_emptyNotifications_returnsZeros() { 82 val logger = createLoggerWithNotifications(listOf()) 83 val data: MutableList<StatsEvent> = mutableListOf() 84 assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data)) 85 .isEqualTo(StatsManager.PULL_SUCCESS) 86 assertThat(data).isEmpty() 87 } 88 89 @Test 90 fun onPullAtom_notificationPassed_populatesData() { 91 val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)) 92 val notification = 93 Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build() 94 val logger = createLoggerWithNotifications(listOf(notification)) 95 val data: MutableList<StatsEvent> = mutableListOf() 96 97 assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data)) 98 .isEqualTo(StatsManager.PULL_SUCCESS) 99 assertThat(data).hasSize(1) 100 } 101 102 @Test 103 fun onPullAtom_multipleNotificationsPassed_populatesData() { 104 val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)) 105 val notification = 106 Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build() 107 val iconTwo = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)) 108 109 val notificationTwo = 110 Notification.Builder(context) 111 .setStyle(Notification.BigTextStyle().bigText("text")) 112 .setSmallIcon(iconTwo) 113 .setContentTitle("titleTwo") 114 .build() 115 val logger = createLoggerWithNotifications(listOf(notification, notificationTwo)) 116 val data: MutableList<StatsEvent> = mutableListOf() 117 118 assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data)) 119 .isEqualTo(StatsManager.PULL_SUCCESS) 120 assertThat(data).hasSize(2) 121 } 122 123 @Test 124 fun onPullAtom_throwsInterruptedException_failsGracefully() { 125 val pipeline: NotifPipeline = mock() 126 whenever(pipeline.allNotifs).thenAnswer { throw InterruptedException("Timeout") } 127 val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor) 128 assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf())) 129 .isEqualTo(StatsManager.PULL_SKIP) 130 } 131 132 @Test 133 fun onPullAtom_throwsRuntimeException_failsGracefully() { 134 val pipeline: NotifPipeline = mock() 135 whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!")) 136 val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor) 137 assertLogsWtf { 138 assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf())) 139 .isEqualTo(StatsManager.PULL_SKIP) 140 } 141 } 142 143 @Test 144 fun aggregateMemoryUsageData_returnsCorrectlyAggregatedSamePackageData() { 145 val usage = getPresetMemoryUsages() 146 val aggregateUsage = aggregateMemoryUsageData(usage) 147 148 assertThat(aggregateUsage).hasSize(3) 149 assertThat(aggregateUsage) 150 .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)) 151 152 // Aggregated fields 153 val aggregatedData = 154 aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)]!! 155 val presetUsage1 = usage[0] 156 val presetUsage2 = usage[1] 157 assertAggregatedData( 158 aggregatedData, 159 2, 160 2, 161 smallIconObject = 162 presetUsage1.objectUsage.smallIcon + presetUsage2.objectUsage.smallIcon, 163 smallIconBitmapCount = 2, 164 largeIconObject = 165 presetUsage1.objectUsage.largeIcon + presetUsage2.objectUsage.largeIcon, 166 largeIconBitmapCount = 2, 167 bigPictureObject = 168 presetUsage1.objectUsage.bigPicture + presetUsage2.objectUsage.bigPicture, 169 bigPictureBitmapCount = 2, 170 extras = presetUsage1.objectUsage.extras + presetUsage2.objectUsage.extras, 171 extenders = presetUsage1.objectUsage.extender + presetUsage2.objectUsage.extender, 172 // Only totals need to be summarized. 173 smallIconViews = 174 presetUsage1.viewUsage[0].smallIcon + presetUsage2.viewUsage[0].smallIcon, 175 largeIconViews = 176 presetUsage1.viewUsage[0].largeIcon + presetUsage2.viewUsage[0].largeIcon, 177 systemIconViews = 178 presetUsage1.viewUsage[0].systemIcons + presetUsage2.viewUsage[0].systemIcons, 179 styleViews = presetUsage1.viewUsage[0].style + presetUsage2.viewUsage[0].style, 180 customViews = 181 presetUsage1.viewUsage[0].customViews + presetUsage2.viewUsage[0].customViews, 182 softwareBitmaps = 183 presetUsage1.viewUsage[0].softwareBitmapsPenalty + 184 presetUsage2.viewUsage[0].softwareBitmapsPenalty, 185 seenCount = 0 186 ) 187 } 188 189 @Test 190 fun aggregateMemoryUsageData_correctlySeparatesDifferentStyles() { 191 val usage = getPresetMemoryUsages() 192 val aggregateUsage = aggregateMemoryUsageData(usage) 193 194 assertThat(aggregateUsage).hasSize(3) 195 assertThat(aggregateUsage) 196 .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)) 197 assertThat(aggregateUsage).containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)) 198 199 // Different style should be separate 200 val separateStyleData = 201 aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)]!! 202 val presetUsage = usage[2] 203 assertAggregatedData( 204 separateStyleData, 205 1, 206 1, 207 presetUsage.objectUsage.smallIcon, 208 1, 209 presetUsage.objectUsage.largeIcon, 210 1, 211 presetUsage.objectUsage.bigPicture, 212 1, 213 presetUsage.objectUsage.extras, 214 presetUsage.objectUsage.extender, 215 presetUsage.viewUsage[0].smallIcon, 216 presetUsage.viewUsage[0].largeIcon, 217 presetUsage.viewUsage[0].systemIcons, 218 presetUsage.viewUsage[0].style, 219 presetUsage.viewUsage[0].customViews, 220 presetUsage.viewUsage[0].softwareBitmapsPenalty, 221 0 222 ) 223 } 224 225 @Test 226 fun aggregateMemoryUsageData_correctlySeparatesDifferentProcess() { 227 val usage = getPresetMemoryUsages() 228 val aggregateUsage = aggregateMemoryUsageData(usage) 229 230 assertThat(aggregateUsage).hasSize(3) 231 assertThat(aggregateUsage) 232 .containsKey(Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)) 233 234 // Different UID/package should also be separate 235 val separatePackageData = 236 aggregateUsage[Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)]!! 237 val presetUsage = usage[3] 238 assertAggregatedData( 239 separatePackageData, 240 1, 241 1, 242 presetUsage.objectUsage.smallIcon, 243 1, 244 presetUsage.objectUsage.largeIcon, 245 1, 246 presetUsage.objectUsage.bigPicture, 247 1, 248 presetUsage.objectUsage.extras, 249 presetUsage.objectUsage.extender, 250 presetUsage.viewUsage[0].smallIcon, 251 presetUsage.viewUsage[0].largeIcon, 252 presetUsage.viewUsage[0].systemIcons, 253 presetUsage.viewUsage[0].style, 254 presetUsage.viewUsage[0].customViews, 255 presetUsage.viewUsage[0].softwareBitmapsPenalty, 256 0 257 ) 258 } 259 260 private fun createLoggerWithNotifications( 261 notifications: List<Notification> 262 ): NotificationMemoryLogger { 263 val pipeline: NotifPipeline = mock() 264 val notifications = 265 notifications.map { notification -> 266 NotificationEntryBuilder().setTag("test").setNotification(notification).build() 267 } 268 whenever(pipeline.allNotifs).thenReturn(notifications) 269 return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor) 270 } 271 272 /** 273 * Short hand for making sure the passed NotificationMemoryUseAtomBuilder object contains 274 * expected values. 275 */ 276 private fun assertAggregatedData( 277 value: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder, 278 count: Int, 279 countWithInflatedViews: Int, 280 smallIconObject: Int, 281 smallIconBitmapCount: Int, 282 largeIconObject: Int, 283 largeIconBitmapCount: Int, 284 bigPictureObject: Int, 285 bigPictureBitmapCount: Int, 286 extras: Int, 287 extenders: Int, 288 smallIconViews: Int, 289 largeIconViews: Int, 290 systemIconViews: Int, 291 styleViews: Int, 292 customViews: Int, 293 softwareBitmaps: Int, 294 seenCount: Int 295 ) { 296 expect.withMessage("count").that(value.count).isEqualTo(count) 297 expect 298 .withMessage("countWithInflatedViews") 299 .that(value.countWithInflatedViews) 300 .isEqualTo(countWithInflatedViews) 301 expect.withMessage("smallIconObject").that(value.smallIconObject).isEqualTo(smallIconObject) 302 expect 303 .withMessage("smallIconBitmapCount") 304 .that(value.smallIconBitmapCount) 305 .isEqualTo(smallIconBitmapCount) 306 expect.withMessage("largeIconObject").that(value.largeIconObject).isEqualTo(largeIconObject) 307 expect 308 .withMessage("largeIconBitmapCount") 309 .that(value.largeIconBitmapCount) 310 .isEqualTo(largeIconBitmapCount) 311 expect 312 .withMessage("bigPictureObject") 313 .that(value.bigPictureObject) 314 .isEqualTo(bigPictureObject) 315 expect 316 .withMessage("bigPictureBitmapCount") 317 .that(value.bigPictureBitmapCount) 318 .isEqualTo(bigPictureBitmapCount) 319 expect.withMessage("extras").that(value.extras).isEqualTo(extras) 320 expect.withMessage("extenders").that(value.extenders).isEqualTo(extenders) 321 expect.withMessage("smallIconViews").that(value.smallIconViews).isEqualTo(smallIconViews) 322 expect.withMessage("largeIconViews").that(value.largeIconViews).isEqualTo(largeIconViews) 323 expect.withMessage("systemIconViews").that(value.systemIconViews).isEqualTo(systemIconViews) 324 expect.withMessage("styleViews").that(value.styleViews).isEqualTo(styleViews) 325 expect.withMessage("customViews").that(value.customViews).isEqualTo(customViews) 326 expect.withMessage("softwareBitmaps").that(value.softwareBitmaps).isEqualTo(softwareBitmaps) 327 expect.withMessage("seenCount").that(value.seenCount).isEqualTo(seenCount) 328 } 329 330 /** Generates a static set of [NotificationMemoryUsage] objects. */ 331 private fun getPresetMemoryUsages() = 332 listOf( 333 // A pair of notifications that have to be aggregated, same UID and style 334 NotificationMemoryUsage( 335 "package 1", 336 384, 337 "key1", 338 Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), 339 NotificationObjectUsage( 340 23, 341 45, 342 67, 343 NotificationEnums.STYLE_BIG_PICTURE, 344 12, 345 483, 346 4382, 347 true 348 ), 349 listOf( 350 NotificationViewUsage(ViewType.TOTAL, 493, 584, 4833, 584, 4888, 5843), 351 NotificationViewUsage( 352 ViewType.PRIVATE_CONTRACTED_VIEW, 353 100, 354 250, 355 300, 356 594, 357 6000, 358 5843 359 ) 360 ) 361 ), 362 NotificationMemoryUsage( 363 "package 1", 364 384, 365 "key2", 366 Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), 367 NotificationObjectUsage( 368 77, 369 54, 370 34, 371 NotificationEnums.STYLE_BIG_PICTURE, 372 77, 373 432, 374 2342, 375 true 376 ), 377 listOf( 378 NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655), 379 NotificationViewUsage( 380 ViewType.PRIVATE_CONTRACTED_VIEW, 381 160, 382 350, 383 300, 384 5544, 385 66500, 386 5433 387 ) 388 ) 389 ), 390 // Different style is different aggregation 391 NotificationMemoryUsage( 392 "package 1", 393 384, 394 "key2", 395 Notification.Builder(context).setStyle(Notification.BigTextStyle()).build(), 396 NotificationObjectUsage( 397 77, 398 54, 399 34, 400 NotificationEnums.STYLE_BIG_TEXT, 401 77, 402 432, 403 2342, 404 true 405 ), 406 listOf( 407 NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655), 408 NotificationViewUsage( 409 ViewType.PRIVATE_CONTRACTED_VIEW, 410 160, 411 350, 412 300, 413 5544, 414 66500, 415 5433 416 ) 417 ) 418 ), 419 // Different package is also different aggregation 420 NotificationMemoryUsage( 421 "package 2", 422 684, 423 "key2", 424 Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), 425 NotificationObjectUsage( 426 32, 427 654, 428 234, 429 NotificationEnums.STYLE_BIG_PICTURE, 430 211, 431 776, 432 435, 433 true 434 ), 435 listOf( 436 NotificationViewUsage(ViewType.TOTAL, 4355, 6543, 4322, 5435, 6546, 65485), 437 NotificationViewUsage( 438 ViewType.PRIVATE_CONTRACTED_VIEW, 439 6546, 440 7657, 441 4353, 442 6546, 443 76575, 444 54654 445 ) 446 ) 447 ) 448 ) 449 } 450