Merge "Implement pull logging for Notification Memory" into tm-qpr-dev-plus-aosp
This commit is contained in:
@@ -17,10 +17,14 @@
|
||||
|
||||
package com.android.systemui.statusbar.notification.logging
|
||||
|
||||
import android.app.Notification
|
||||
|
||||
/** Describes usage of a notification. */
|
||||
data class NotificationMemoryUsage(
|
||||
val packageName: String,
|
||||
val uid: Int,
|
||||
val notificationKey: String,
|
||||
val notification: Notification,
|
||||
val objectUsage: NotificationObjectUsage,
|
||||
val viewUsage: List<NotificationViewUsage>
|
||||
)
|
||||
@@ -34,7 +38,8 @@ data class NotificationObjectUsage(
|
||||
val smallIcon: Int,
|
||||
val largeIcon: Int,
|
||||
val extras: Int,
|
||||
val style: String?,
|
||||
/** Style type, integer from [android.stats.sysui.NotificationEnums] */
|
||||
val style: Int,
|
||||
val styleIcon: Int,
|
||||
val bigPicture: Int,
|
||||
val extender: Int,
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2022 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 com.android.systemui.statusbar.notification.logging
|
||||
|
||||
import android.stats.sysui.NotificationEnums
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import java.io.PrintWriter
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Dumps current notification memory use to bug reports for easier debugging. */
|
||||
@SysUISingleton
|
||||
class NotificationMemoryDumper
|
||||
@Inject
|
||||
constructor(val dumpManager: DumpManager, val notificationPipeline: NotifPipeline) : Dumpable {
|
||||
|
||||
fun init() {
|
||||
dumpManager.registerNormalDumpable(javaClass.simpleName, this)
|
||||
}
|
||||
|
||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||
val memoryUse =
|
||||
NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
|
||||
.sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
|
||||
dumpNotificationObjects(pw, memoryUse)
|
||||
dumpNotificationViewUsage(pw, memoryUse)
|
||||
}
|
||||
|
||||
/** Renders a table of notification object usage into passed [PrintWriter]. */
|
||||
private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
|
||||
pw.println("Notification Object Usage")
|
||||
pw.println("-----------")
|
||||
pw.println(
|
||||
"Package".padEnd(35) +
|
||||
"\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
|
||||
)
|
||||
pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
|
||||
pw.println()
|
||||
|
||||
memoryUse.forEach { use ->
|
||||
pw.println(
|
||||
use.packageName.padEnd(35) +
|
||||
"\t\t" +
|
||||
"${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
|
||||
(styleEnumToString(use.objectUsage.style).take(15) ?: "").padEnd(15) +
|
||||
"\t\t${use.objectUsage.styleIcon}\t" +
|
||||
"${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
|
||||
"${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
|
||||
use.notificationKey
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate totals for easily glanceable summary.
|
||||
data class Totals(
|
||||
var smallIcon: Int = 0,
|
||||
var largeIcon: Int = 0,
|
||||
var styleIcon: Int = 0,
|
||||
var bigPicture: Int = 0,
|
||||
var extender: Int = 0,
|
||||
var extras: Int = 0,
|
||||
)
|
||||
|
||||
val totals =
|
||||
memoryUse.fold(Totals()) { t, usage ->
|
||||
t.smallIcon += usage.objectUsage.smallIcon
|
||||
t.largeIcon += usage.objectUsage.largeIcon
|
||||
t.styleIcon += usage.objectUsage.styleIcon
|
||||
t.bigPicture += usage.objectUsage.bigPicture
|
||||
t.extender += usage.objectUsage.extender
|
||||
t.extras += usage.objectUsage.extras
|
||||
t
|
||||
}
|
||||
|
||||
pw.println()
|
||||
pw.println("TOTALS")
|
||||
pw.println(
|
||||
"".padEnd(35) +
|
||||
"\t\t" +
|
||||
"${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
|
||||
"".padEnd(15) +
|
||||
"\t\t${toKb(totals.styleIcon)}\t" +
|
||||
"${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
|
||||
toKb(totals.extras)
|
||||
)
|
||||
pw.println()
|
||||
}
|
||||
|
||||
/** Renders a table of notification view usage into passed [PrintWriter] */
|
||||
private fun dumpNotificationViewUsage(
|
||||
pw: PrintWriter,
|
||||
memoryUse: List<NotificationMemoryUsage>,
|
||||
) {
|
||||
|
||||
data class Totals(
|
||||
var smallIcon: Int = 0,
|
||||
var largeIcon: Int = 0,
|
||||
var style: Int = 0,
|
||||
var customViews: Int = 0,
|
||||
var softwareBitmapsPenalty: Int = 0,
|
||||
)
|
||||
|
||||
val totals = Totals()
|
||||
pw.println("Notification View Usage")
|
||||
pw.println("-----------")
|
||||
pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
|
||||
pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
|
||||
pw.println()
|
||||
memoryUse
|
||||
.filter { it.viewUsage.isNotEmpty() }
|
||||
.forEach { use ->
|
||||
pw.println(use.packageName + " " + use.notificationKey)
|
||||
use.viewUsage.forEach { view ->
|
||||
pw.println(
|
||||
" ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
|
||||
"\t${view.largeIcon}\t${view.style}" +
|
||||
"\t${view.customViews}\t${view.softwareBitmapsPenalty}"
|
||||
)
|
||||
|
||||
if (view.viewType == ViewType.TOTAL) {
|
||||
totals.smallIcon += view.smallIcon
|
||||
totals.largeIcon += view.largeIcon
|
||||
totals.style += view.style
|
||||
totals.customViews += view.customViews
|
||||
totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
|
||||
}
|
||||
}
|
||||
}
|
||||
pw.println()
|
||||
pw.println("TOTALS")
|
||||
pw.println(
|
||||
" ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
|
||||
"\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
|
||||
"\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
|
||||
)
|
||||
pw.println()
|
||||
}
|
||||
|
||||
private fun styleEnumToString(styleEnum: Int): String =
|
||||
when (styleEnum) {
|
||||
NotificationEnums.STYLE_UNSPECIFIED -> "Unspecified"
|
||||
NotificationEnums.STYLE_NONE -> "None"
|
||||
NotificationEnums.STYLE_BIG_PICTURE -> "BigPicture"
|
||||
NotificationEnums.STYLE_BIG_TEXT -> "BigText"
|
||||
NotificationEnums.STYLE_CALL -> "Call"
|
||||
NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW -> "DCustomView"
|
||||
NotificationEnums.STYLE_INBOX -> "Inbox"
|
||||
NotificationEnums.STYLE_MEDIA -> "Media"
|
||||
NotificationEnums.STYLE_MESSAGING -> "Messaging"
|
||||
NotificationEnums.STYLE_RANKER_GROUP -> "RankerGroup"
|
||||
else -> "Unknown"
|
||||
}
|
||||
|
||||
private fun toKb(bytes: Int): String {
|
||||
return (bytes / 1024).toString() + " KB"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2022 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 com.android.systemui.statusbar.notification.logging
|
||||
|
||||
import android.app.StatsManager
|
||||
import android.util.StatsEvent
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.shared.system.SysUiStatsLog
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.util.traceSection
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/** Periodically logs current state of notification memory consumption. */
|
||||
@SysUISingleton
|
||||
class NotificationMemoryLogger
|
||||
@Inject
|
||||
constructor(
|
||||
private val notificationPipeline: NotifPipeline,
|
||||
private val statsManager: StatsManager,
|
||||
@Main private val mainDispatcher: CoroutineDispatcher,
|
||||
@Background private val backgroundExecutor: Executor
|
||||
) : StatsManager.StatsPullAtomCallback {
|
||||
|
||||
/**
|
||||
* This class is used to accumulate and aggregate data - the fields mirror values in statd Atom
|
||||
* with ONE IMPORTANT difference - the values are in bytes, not KB!
|
||||
*/
|
||||
internal data class NotificationMemoryUseAtomBuilder(val uid: Int, val style: Int) {
|
||||
var count: Int = 0
|
||||
var countWithInflatedViews: Int = 0
|
||||
var smallIconObject: Int = 0
|
||||
var smallIconBitmapCount: Int = 0
|
||||
var largeIconObject: Int = 0
|
||||
var largeIconBitmapCount: Int = 0
|
||||
var bigPictureObject: Int = 0
|
||||
var bigPictureBitmapCount: Int = 0
|
||||
var extras: Int = 0
|
||||
var extenders: Int = 0
|
||||
var smallIconViews: Int = 0
|
||||
var largeIconViews: Int = 0
|
||||
var systemIconViews: Int = 0
|
||||
var styleViews: Int = 0
|
||||
var customViews: Int = 0
|
||||
var softwareBitmaps: Int = 0
|
||||
var seenCount = 0
|
||||
}
|
||||
|
||||
fun init() {
|
||||
statsManager.setPullAtomCallback(
|
||||
SysUiStatsLog.NOTIFICATION_MEMORY_USE,
|
||||
null,
|
||||
backgroundExecutor,
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
/** Called by statsd to pull data. */
|
||||
override fun onPullAtom(atomTag: Int, data: MutableList<StatsEvent>): Int =
|
||||
traceSection("NML#onPullAtom") {
|
||||
if (atomTag != SysUiStatsLog.NOTIFICATION_MEMORY_USE) {
|
||||
return StatsManager.PULL_SKIP
|
||||
}
|
||||
|
||||
// Notifications can only be retrieved on the main thread, so switch to that thread.
|
||||
val notifications = getAllNotificationsOnMainThread()
|
||||
val notificationMemoryUse =
|
||||
NotificationMemoryMeter.notificationMemoryUse(notifications)
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ it.packageName },
|
||||
{ it.objectUsage.style },
|
||||
{ it.notificationKey }
|
||||
)
|
||||
)
|
||||
val usageData = aggregateMemoryUsageData(notificationMemoryUse)
|
||||
usageData.forEach { (_, use) ->
|
||||
data.add(
|
||||
SysUiStatsLog.buildStatsEvent(
|
||||
SysUiStatsLog.NOTIFICATION_MEMORY_USE,
|
||||
use.uid,
|
||||
use.style,
|
||||
use.count,
|
||||
use.countWithInflatedViews,
|
||||
toKb(use.smallIconObject),
|
||||
use.smallIconBitmapCount,
|
||||
toKb(use.largeIconObject),
|
||||
use.largeIconBitmapCount,
|
||||
toKb(use.bigPictureObject),
|
||||
use.bigPictureBitmapCount,
|
||||
toKb(use.extras),
|
||||
toKb(use.extenders),
|
||||
toKb(use.smallIconViews),
|
||||
toKb(use.largeIconViews),
|
||||
toKb(use.systemIconViews),
|
||||
toKb(use.styleViews),
|
||||
toKb(use.customViews),
|
||||
toKb(use.softwareBitmaps),
|
||||
use.seenCount
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return StatsManager.PULL_SUCCESS
|
||||
}
|
||||
|
||||
private fun getAllNotificationsOnMainThread() =
|
||||
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++
|
||||
}
|
||||
|
||||
use.smallIconObject += element.objectUsage.smallIcon
|
||||
if (element.objectUsage.smallIcon > 0) {
|
||||
use.smallIconBitmapCount++
|
||||
}
|
||||
|
||||
use.largeIconObject += element.objectUsage.largeIcon
|
||||
if (element.objectUsage.largeIcon > 0) {
|
||||
use.largeIconBitmapCount++
|
||||
}
|
||||
|
||||
use.bigPictureObject += element.objectUsage.bigPicture
|
||||
if (element.objectUsage.bigPicture > 0) {
|
||||
use.bigPictureBitmapCount++
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */
|
||||
private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt()
|
||||
}
|
||||
@@ -1,12 +1,20 @@
|
||||
package com.android.systemui.statusbar.notification.logging
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.Notification.BigPictureStyle
|
||||
import android.app.Notification.BigTextStyle
|
||||
import android.app.Notification.CallStyle
|
||||
import android.app.Notification.DecoratedCustomViewStyle
|
||||
import android.app.Notification.InboxStyle
|
||||
import android.app.Notification.MediaStyle
|
||||
import android.app.Notification.MessagingStyle
|
||||
import android.app.Person
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.stats.sysui.NotificationEnums
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.android.systemui.statusbar.notification.NotificationUtils
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
@@ -19,6 +27,7 @@ internal object NotificationMemoryMeter {
|
||||
private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
|
||||
private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
|
||||
private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
|
||||
private const val AUTOGROUP_KEY = "ranker_group"
|
||||
|
||||
/** Returns a list of memory use entries for currently shown notifications. */
|
||||
@WorkerThread
|
||||
@@ -29,12 +38,15 @@ internal object NotificationMemoryMeter {
|
||||
.asSequence()
|
||||
.map { entry ->
|
||||
val packageName = entry.sbn.packageName
|
||||
val uid = entry.sbn.uid
|
||||
val notificationObjectUsage =
|
||||
notificationMemoryUse(entry.sbn.notification, hashSetOf())
|
||||
val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
|
||||
NotificationMemoryUsage(
|
||||
packageName,
|
||||
uid,
|
||||
NotificationUtils.logKey(entry.sbn.key),
|
||||
entry.sbn.notification,
|
||||
notificationObjectUsage,
|
||||
notificationViewUsage
|
||||
)
|
||||
@@ -49,7 +61,9 @@ internal object NotificationMemoryMeter {
|
||||
): NotificationMemoryUsage {
|
||||
return NotificationMemoryUsage(
|
||||
entry.sbn.packageName,
|
||||
entry.sbn.uid,
|
||||
NotificationUtils.logKey(entry.sbn.key),
|
||||
entry.sbn.notification,
|
||||
notificationMemoryUse(entry.sbn.notification, seenBitmaps),
|
||||
NotificationMemoryViewWalker.getViewUsage(entry.row)
|
||||
)
|
||||
@@ -116,7 +130,13 @@ internal object NotificationMemoryMeter {
|
||||
val wearExtenderBackground =
|
||||
computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
|
||||
|
||||
val style = notification.notificationStyle
|
||||
val style =
|
||||
if (notification.group == AUTOGROUP_KEY) {
|
||||
NotificationEnums.STYLE_RANKER_GROUP
|
||||
} else {
|
||||
styleEnum(notification.notificationStyle)
|
||||
}
|
||||
|
||||
val hasCustomView = notification.contentView != null || notification.bigContentView != null
|
||||
val extrasSize = computeBundleSize(extras)
|
||||
|
||||
@@ -124,7 +144,7 @@ internal object NotificationMemoryMeter {
|
||||
smallIcon = smallIconUse,
|
||||
largeIcon = largeIconUse,
|
||||
extras = extrasSize,
|
||||
style = style?.simpleName,
|
||||
style = style,
|
||||
styleIcon =
|
||||
bigPictureIconUse +
|
||||
peopleUse +
|
||||
@@ -143,6 +163,25 @@ internal object NotificationMemoryMeter {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns logging style enum based on current style class.
|
||||
*
|
||||
* @return style value in [NotificationEnums]
|
||||
*/
|
||||
private fun styleEnum(style: Class<out Notification.Style>?): Int =
|
||||
when (style?.name) {
|
||||
null -> NotificationEnums.STYLE_NONE
|
||||
BigTextStyle::class.java.name -> NotificationEnums.STYLE_BIG_TEXT
|
||||
BigPictureStyle::class.java.name -> NotificationEnums.STYLE_BIG_PICTURE
|
||||
InboxStyle::class.java.name -> NotificationEnums.STYLE_INBOX
|
||||
MediaStyle::class.java.name -> NotificationEnums.STYLE_MEDIA
|
||||
DecoratedCustomViewStyle::class.java.name ->
|
||||
NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW
|
||||
MessagingStyle::class.java.name -> NotificationEnums.STYLE_MESSAGING
|
||||
CallStyle::class.java.name -> NotificationEnums.STYLE_CALL
|
||||
else -> NotificationEnums.STYLE_UNSPECIFIED
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
|
||||
* bitmaps). Can be slow.
|
||||
@@ -176,7 +215,7 @@ internal object NotificationMemoryMeter {
|
||||
*
|
||||
* @return memory usage in bytes or 0 if the icon is Uri/Resource based
|
||||
*/
|
||||
private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
|
||||
private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>): Int =
|
||||
when (icon?.type) {
|
||||
Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
|
||||
Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
|
||||
|
||||
@@ -18,11 +18,10 @@
|
||||
package com.android.systemui.statusbar.notification.logging
|
||||
|
||||
import android.util.Log
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import java.io.PrintWriter
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import dagger.Lazy
|
||||
import javax.inject.Inject
|
||||
|
||||
/** This class monitors and logs current Notification memory use. */
|
||||
@@ -30,9 +29,10 @@ import javax.inject.Inject
|
||||
class NotificationMemoryMonitor
|
||||
@Inject
|
||||
constructor(
|
||||
val notificationPipeline: NotifPipeline,
|
||||
val dumpManager: DumpManager,
|
||||
) : Dumpable {
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val notificationMemoryDumper: NotificationMemoryDumper,
|
||||
private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "NotificationMemory"
|
||||
@@ -40,127 +40,10 @@ constructor(
|
||||
|
||||
fun init() {
|
||||
Log.d(TAG, "NotificationMemoryMonitor initialized.")
|
||||
dumpManager.registerDumpable(javaClass.simpleName, this)
|
||||
}
|
||||
|
||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||
val memoryUse =
|
||||
NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
|
||||
.sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
|
||||
dumpNotificationObjects(pw, memoryUse)
|
||||
dumpNotificationViewUsage(pw, memoryUse)
|
||||
}
|
||||
|
||||
/** Renders a table of notification object usage into passed [PrintWriter]. */
|
||||
private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
|
||||
pw.println("Notification Object Usage")
|
||||
pw.println("-----------")
|
||||
pw.println(
|
||||
"Package".padEnd(35) +
|
||||
"\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
|
||||
)
|
||||
pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
|
||||
pw.println()
|
||||
|
||||
memoryUse.forEach { use ->
|
||||
pw.println(
|
||||
use.packageName.padEnd(35) +
|
||||
"\t\t" +
|
||||
"${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
|
||||
(use.objectUsage.style?.take(15) ?: "").padEnd(15) +
|
||||
"\t\t${use.objectUsage.styleIcon}\t" +
|
||||
"${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
|
||||
"${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
|
||||
use.notificationKey
|
||||
)
|
||||
notificationMemoryDumper.init()
|
||||
if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
|
||||
Log.d(TAG, "Notification memory logging enabled.")
|
||||
notificationMemoryLogger.get().init()
|
||||
}
|
||||
|
||||
// Calculate totals for easily glanceable summary.
|
||||
data class Totals(
|
||||
var smallIcon: Int = 0,
|
||||
var largeIcon: Int = 0,
|
||||
var styleIcon: Int = 0,
|
||||
var bigPicture: Int = 0,
|
||||
var extender: Int = 0,
|
||||
var extras: Int = 0,
|
||||
)
|
||||
|
||||
val totals =
|
||||
memoryUse.fold(Totals()) { t, usage ->
|
||||
t.smallIcon += usage.objectUsage.smallIcon
|
||||
t.largeIcon += usage.objectUsage.largeIcon
|
||||
t.styleIcon += usage.objectUsage.styleIcon
|
||||
t.bigPicture += usage.objectUsage.bigPicture
|
||||
t.extender += usage.objectUsage.extender
|
||||
t.extras += usage.objectUsage.extras
|
||||
t
|
||||
}
|
||||
|
||||
pw.println()
|
||||
pw.println("TOTALS")
|
||||
pw.println(
|
||||
"".padEnd(35) +
|
||||
"\t\t" +
|
||||
"${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
|
||||
"".padEnd(15) +
|
||||
"\t\t${toKb(totals.styleIcon)}\t" +
|
||||
"${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
|
||||
toKb(totals.extras)
|
||||
)
|
||||
pw.println()
|
||||
}
|
||||
|
||||
/** Renders a table of notification view usage into passed [PrintWriter] */
|
||||
private fun dumpNotificationViewUsage(
|
||||
pw: PrintWriter,
|
||||
memoryUse: List<NotificationMemoryUsage>,
|
||||
) {
|
||||
|
||||
data class Totals(
|
||||
var smallIcon: Int = 0,
|
||||
var largeIcon: Int = 0,
|
||||
var style: Int = 0,
|
||||
var customViews: Int = 0,
|
||||
var softwareBitmapsPenalty: Int = 0,
|
||||
)
|
||||
|
||||
val totals = Totals()
|
||||
pw.println("Notification View Usage")
|
||||
pw.println("-----------")
|
||||
pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
|
||||
pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
|
||||
pw.println()
|
||||
memoryUse
|
||||
.filter { it.viewUsage.isNotEmpty() }
|
||||
.forEach { use ->
|
||||
pw.println(use.packageName + " " + use.notificationKey)
|
||||
use.viewUsage.forEach { view ->
|
||||
pw.println(
|
||||
" ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
|
||||
"\t${view.largeIcon}\t${view.style}" +
|
||||
"\t${view.customViews}\t${view.softwareBitmapsPenalty}"
|
||||
)
|
||||
|
||||
if (view.viewType == ViewType.TOTAL) {
|
||||
totals.smallIcon += view.smallIcon
|
||||
totals.largeIcon += view.largeIcon
|
||||
totals.style += view.style
|
||||
totals.customViews += view.customViews
|
||||
totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
|
||||
}
|
||||
}
|
||||
}
|
||||
pw.println()
|
||||
pw.println("TOTALS")
|
||||
pw.println(
|
||||
" ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
|
||||
"\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
|
||||
"\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
|
||||
)
|
||||
pw.println()
|
||||
}
|
||||
|
||||
private fun toKb(bytes: Int): String {
|
||||
return (bytes / 1024).toString() + " KB"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,11 @@ internal object NotificationMemoryViewWalker {
|
||||
|
||||
/**
|
||||
* Returns memory usage of public and private views contained in passed
|
||||
* [ExpandableNotificationRow]
|
||||
* [ExpandableNotificationRow]. Each entry will correspond to one of the [ViewType] values with
|
||||
* [ViewType.TOTAL] totalling all memory use. If a type of view is missing, the corresponding
|
||||
* entry will not appear in resulting list.
|
||||
*
|
||||
* This will return an empty list if the ExpandableNotificationRow has no views inflated.
|
||||
*/
|
||||
fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
|
||||
if (row == null) {
|
||||
@@ -58,42 +62,72 @@ internal object NotificationMemoryViewWalker {
|
||||
}
|
||||
|
||||
// The ordering here is significant since it determines deduplication of seen drawables.
|
||||
return listOf(
|
||||
getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
|
||||
getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild),
|
||||
getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
|
||||
getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout),
|
||||
getTotalUsage(row)
|
||||
)
|
||||
val perViewUsages =
|
||||
listOf(
|
||||
getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
|
||||
getViewUsage(
|
||||
ViewType.PRIVATE_CONTRACTED_VIEW,
|
||||
row.privateLayout?.contractedChild
|
||||
),
|
||||
getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
|
||||
getViewUsage(
|
||||
ViewType.PUBLIC_VIEW,
|
||||
row.publicLayout?.expandedChild,
|
||||
row.publicLayout?.contractedChild,
|
||||
row.publicLayout?.headsUpChild
|
||||
),
|
||||
)
|
||||
.filterNotNull()
|
||||
|
||||
return if (perViewUsages.isNotEmpty()) {
|
||||
// Attach summed totals field only if there was any view actually measured.
|
||||
// This reduces bug report noise and makes checks for collapsed views easier.
|
||||
val totals = getTotalUsage(row)
|
||||
if (totals == null) {
|
||||
perViewUsages
|
||||
} else {
|
||||
perViewUsages + totals
|
||||
}
|
||||
} else {
|
||||
listOf()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total usage of all views - we need to do a separate traversal to make sure we don't
|
||||
* double count fields.
|
||||
*/
|
||||
private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage {
|
||||
val totalUsage = UsageBuilder()
|
||||
private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage? {
|
||||
val seenObjects = hashSetOf<Int>()
|
||||
|
||||
row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) }
|
||||
row.privateLayout?.let { child ->
|
||||
for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) {
|
||||
(view as? ViewGroup)?.let { v ->
|
||||
computeViewHierarchyUse(v, totalUsage, seenObjects)
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalUsage.build(ViewType.TOTAL)
|
||||
return getViewUsage(
|
||||
ViewType.TOTAL,
|
||||
row.privateLayout?.expandedChild,
|
||||
row.privateLayout?.contractedChild,
|
||||
row.privateLayout?.headsUpChild,
|
||||
row.publicLayout?.expandedChild,
|
||||
row.publicLayout?.contractedChild,
|
||||
row.publicLayout?.headsUpChild,
|
||||
seenObjects = seenObjects
|
||||
)
|
||||
}
|
||||
|
||||
private fun getViewUsage(
|
||||
type: ViewType,
|
||||
rootView: View?,
|
||||
vararg rootViews: View?,
|
||||
seenObjects: HashSet<Int> = hashSetOf()
|
||||
): NotificationViewUsage {
|
||||
val usageBuilder = UsageBuilder()
|
||||
(rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) }
|
||||
return usageBuilder.build(type)
|
||||
): NotificationViewUsage? {
|
||||
val usageBuilder = lazy { UsageBuilder() }
|
||||
rootViews.forEach { rootView ->
|
||||
(rootView as? ViewGroup)?.let { rootViewGroup ->
|
||||
computeViewHierarchyUse(rootViewGroup, usageBuilder.value, seenObjects)
|
||||
}
|
||||
}
|
||||
|
||||
return if (usageBuilder.isInitialized()) {
|
||||
usageBuilder.value.build(type)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeViewHierarchyUse(
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.systemui.statusbar.notification.logging
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.StatsManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Icon
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.util.StatsEvent
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.shared.system.SysUiStatsLog
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
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.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class NotificationMemoryLoggerTest : SysuiTestCase() {
|
||||
|
||||
private val bgExecutor = FakeExecutor(FakeSystemClock())
|
||||
private val immediate = Dispatchers.Main.immediate
|
||||
|
||||
@Mock private lateinit var statsManager: StatsManager
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onInit_registersCallback() {
|
||||
val logger = createLoggerWithNotifications(listOf())
|
||||
logger.init()
|
||||
verify(statsManager)
|
||||
.setPullAtomCallback(SysUiStatsLog.NOTIFICATION_MEMORY_USE, null, bgExecutor, logger)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPullAtom_wrongAtomId_returnsSkip() {
|
||||
val logger = createLoggerWithNotifications(listOf())
|
||||
val data: MutableList<StatsEvent> = mutableListOf()
|
||||
assertThat(logger.onPullAtom(111, data)).isEqualTo(StatsManager.PULL_SKIP)
|
||||
assertThat(data).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPullAtom_emptyNotifications_returnsZeros() {
|
||||
val logger = createLoggerWithNotifications(listOf())
|
||||
val data: MutableList<StatsEvent> = mutableListOf()
|
||||
assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
|
||||
.isEqualTo(StatsManager.PULL_SUCCESS)
|
||||
assertThat(data).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPullAtom_notificationPassed_populatesData() {
|
||||
val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
|
||||
val notification =
|
||||
Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build()
|
||||
val logger = createLoggerWithNotifications(listOf(notification))
|
||||
val data: MutableList<StatsEvent> = mutableListOf()
|
||||
|
||||
assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
|
||||
.isEqualTo(StatsManager.PULL_SUCCESS)
|
||||
assertThat(data).hasSize(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPullAtom_multipleNotificationsPassed_populatesData() {
|
||||
val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
|
||||
val notification =
|
||||
Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build()
|
||||
val iconTwo = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
|
||||
|
||||
val notificationTwo =
|
||||
Notification.Builder(context)
|
||||
.setStyle(Notification.BigTextStyle().bigText("text"))
|
||||
.setSmallIcon(iconTwo)
|
||||
.setContentTitle("titleTwo")
|
||||
.build()
|
||||
val logger = createLoggerWithNotifications(listOf(notification, notificationTwo))
|
||||
val data: MutableList<StatsEvent> = mutableListOf()
|
||||
|
||||
assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
|
||||
.isEqualTo(StatsManager.PULL_SUCCESS)
|
||||
assertThat(data).hasSize(2)
|
||||
}
|
||||
|
||||
private fun createLoggerWithNotifications(
|
||||
notifications: List<Notification>
|
||||
): NotificationMemoryLogger {
|
||||
val pipeline: NotifPipeline = mock()
|
||||
val notifications =
|
||||
notifications.map { notification ->
|
||||
NotificationEntryBuilder().setTag("test").setNotification(notification).build()
|
||||
}
|
||||
whenever(pipeline.allNotifs).thenReturn(notifications)
|
||||
return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import android.app.Person
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Icon
|
||||
import android.stats.sysui.NotificationEnums
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.widget.RemoteViews
|
||||
import androidx.test.filters.SmallTest
|
||||
@@ -50,7 +51,27 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 3316,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = null,
|
||||
style = NotificationEnums.STYLE_NONE,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_rankerGroupNotification() {
|
||||
val notification = createBasicNotification().build()
|
||||
val memoryUse =
|
||||
NotificationMemoryMeter.notificationMemoryUse(
|
||||
createNotificationEntry(createBasicNotification().setGroup("ranker_group").build())
|
||||
)
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
|
||||
extras = 3316,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = NotificationEnums.STYLE_RANKER_GROUP,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
@@ -69,7 +90,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 3316,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = null,
|
||||
style = NotificationEnums.STYLE_NONE,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
@@ -92,7 +113,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 3384,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = null,
|
||||
style = NotificationEnums.STYLE_NONE,
|
||||
styleIcon = 0,
|
||||
hasCustomView = true,
|
||||
)
|
||||
@@ -112,7 +133,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 3212,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = null,
|
||||
style = NotificationEnums.STYLE_NONE,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
@@ -141,7 +162,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 4092,
|
||||
bigPicture = bigPicture.bitmap.allocationByteCount,
|
||||
extender = 0,
|
||||
style = "BigPictureStyle",
|
||||
style = NotificationEnums.STYLE_BIG_PICTURE,
|
||||
styleIcon = bigPictureIcon.bitmap.allocationByteCount,
|
||||
hasCustomView = false,
|
||||
)
|
||||
@@ -167,7 +188,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 4084,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = "CallStyle",
|
||||
style = NotificationEnums.STYLE_CALL,
|
||||
styleIcon = personIcon.bitmap.allocationByteCount,
|
||||
hasCustomView = false,
|
||||
)
|
||||
@@ -203,7 +224,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 5024,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = "MessagingStyle",
|
||||
style = NotificationEnums.STYLE_MESSAGING,
|
||||
styleIcon =
|
||||
personIcon.bitmap.allocationByteCount +
|
||||
historicPersonIcon.bitmap.allocationByteCount,
|
||||
@@ -225,7 +246,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 3612,
|
||||
bigPicture = 0,
|
||||
extender = 556656,
|
||||
style = null,
|
||||
style = NotificationEnums.STYLE_NONE,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
@@ -246,7 +267,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras = 3820,
|
||||
bigPicture = 0,
|
||||
extender = 388 + wearBackground.allocationByteCount,
|
||||
style = null,
|
||||
style = NotificationEnums.STYLE_NONE,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
@@ -272,7 +293,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
extras: Int,
|
||||
bigPicture: Int,
|
||||
extender: Int,
|
||||
style: String?,
|
||||
style: Int,
|
||||
styleIcon: Int,
|
||||
hasCustomView: Boolean,
|
||||
) {
|
||||
@@ -282,11 +303,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
||||
assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
|
||||
assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
|
||||
assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
|
||||
if (style == null) {
|
||||
assertThat(memoryUse.objectUsage.style).isNull()
|
||||
} else {
|
||||
assertThat(memoryUse.objectUsage.style).isEqualTo(style)
|
||||
}
|
||||
assertThat(memoryUse.objectUsage.style).isEqualTo(style)
|
||||
assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
|
||||
assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.testing.TestableLooper
|
||||
import android.widget.RemoteViews
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder
|
||||
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
|
||||
import com.android.systemui.tests.R
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -39,16 +40,84 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
||||
fun testViewWalker_plainNotification() {
|
||||
val row = testHelper.createRow()
|
||||
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
||||
assertThat(result).hasSize(5)
|
||||
assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
|
||||
assertThat(result)
|
||||
.contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
|
||||
assertThat(result).hasSize(3)
|
||||
assertThat(result)
|
||||
.contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
|
||||
assertThat(result)
|
||||
.contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0))
|
||||
assertThat(result).contains(NotificationViewUsage(ViewType.TOTAL, 0, 0, 0, 0, 0, 0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testViewWalker_plainNotification_withPublicView() {
|
||||
val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
|
||||
val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888))
|
||||
testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL)
|
||||
val row =
|
||||
testHelper.createRow(
|
||||
Notification.Builder(mContext)
|
||||
.setContentText("Test")
|
||||
.setContentTitle("title")
|
||||
.setSmallIcon(icon)
|
||||
.setPublicVersion(
|
||||
Notification.Builder(mContext)
|
||||
.setContentText("Public Test")
|
||||
.setContentTitle("title")
|
||||
.setSmallIcon(publicIcon)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
||||
assertThat(result).hasSize(4)
|
||||
assertThat(result)
|
||||
.contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
|
||||
.contains(
|
||||
NotificationViewUsage(
|
||||
ViewType.PRIVATE_EXPANDED_VIEW,
|
||||
icon.bitmap.allocationByteCount,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
icon.bitmap.allocationByteCount
|
||||
)
|
||||
)
|
||||
assertThat(result)
|
||||
.contains(
|
||||
NotificationViewUsage(
|
||||
ViewType.PRIVATE_CONTRACTED_VIEW,
|
||||
icon.bitmap.allocationByteCount,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
icon.bitmap.allocationByteCount
|
||||
)
|
||||
)
|
||||
assertThat(result)
|
||||
.contains(
|
||||
NotificationViewUsage(
|
||||
ViewType.PUBLIC_VIEW,
|
||||
publicIcon.bitmap.allocationByteCount,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
publicIcon.bitmap.allocationByteCount
|
||||
)
|
||||
)
|
||||
assertThat(result)
|
||||
.contains(
|
||||
NotificationViewUsage(
|
||||
ViewType.TOTAL,
|
||||
icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -67,7 +136,7 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
||||
.build()
|
||||
)
|
||||
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
||||
assertThat(result).hasSize(5)
|
||||
assertThat(result).hasSize(3)
|
||||
assertThat(result)
|
||||
.contains(
|
||||
NotificationViewUsage(
|
||||
@@ -95,8 +164,20 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
||||
icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
|
||||
)
|
||||
)
|
||||
// Due to deduplication, this should all be 0.
|
||||
assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
|
||||
assertThat(result)
|
||||
.contains(
|
||||
NotificationViewUsage(
|
||||
ViewType.TOTAL,
|
||||
icon.bitmap.allocationByteCount,
|
||||
largeIcon.bitmap.allocationByteCount,
|
||||
0,
|
||||
bigPicture.allocationByteCount,
|
||||
0,
|
||||
bigPicture.allocationByteCount +
|
||||
icon.bitmap.allocationByteCount +
|
||||
largeIcon.bitmap.allocationByteCount
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,7 +198,7 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
||||
.build()
|
||||
)
|
||||
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
||||
assertThat(result).hasSize(5)
|
||||
assertThat(result).hasSize(3)
|
||||
assertThat(result)
|
||||
.contains(
|
||||
NotificationViewUsage(
|
||||
@@ -142,7 +223,17 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
||||
bitmap.allocationByteCount + icon.bitmap.allocationByteCount
|
||||
)
|
||||
)
|
||||
// Due to deduplication, this should all be 0.
|
||||
assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
|
||||
assertThat(result)
|
||||
.contains(
|
||||
NotificationViewUsage(
|
||||
ViewType.TOTAL,
|
||||
icon.bitmap.allocationByteCount,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
bitmap.allocationByteCount,
|
||||
bitmap.allocationByteCount + icon.bitmap.allocationByteCount
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user