Merge "Implement pull logging for Notification Memory" into tm-qpr-dev
This commit is contained in:
@@ -17,10 +17,14 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.notification.logging
|
package com.android.systemui.statusbar.notification.logging
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
|
||||||
/** Describes usage of a notification. */
|
/** Describes usage of a notification. */
|
||||||
data class NotificationMemoryUsage(
|
data class NotificationMemoryUsage(
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
|
val uid: Int,
|
||||||
val notificationKey: String,
|
val notificationKey: String,
|
||||||
|
val notification: Notification,
|
||||||
val objectUsage: NotificationObjectUsage,
|
val objectUsage: NotificationObjectUsage,
|
||||||
val viewUsage: List<NotificationViewUsage>
|
val viewUsage: List<NotificationViewUsage>
|
||||||
)
|
)
|
||||||
@@ -34,7 +38,8 @@ data class NotificationObjectUsage(
|
|||||||
val smallIcon: Int,
|
val smallIcon: Int,
|
||||||
val largeIcon: Int,
|
val largeIcon: Int,
|
||||||
val extras: Int,
|
val extras: Int,
|
||||||
val style: String?,
|
/** Style type, integer from [android.stats.sysui.NotificationEnums] */
|
||||||
|
val style: Int,
|
||||||
val styleIcon: Int,
|
val styleIcon: Int,
|
||||||
val bigPicture: Int,
|
val bigPicture: Int,
|
||||||
val extender: 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
|
package com.android.systemui.statusbar.notification.logging
|
||||||
|
|
||||||
import android.app.Notification
|
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.app.Person
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import android.stats.sysui.NotificationEnums
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.android.systemui.statusbar.notification.NotificationUtils
|
import com.android.systemui.statusbar.notification.NotificationUtils
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
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 TV_EXTENSIONS = "android.tv.EXTENSIONS"
|
||||||
private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
|
private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
|
||||||
private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
|
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. */
|
/** Returns a list of memory use entries for currently shown notifications. */
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
@@ -29,12 +38,15 @@ internal object NotificationMemoryMeter {
|
|||||||
.asSequence()
|
.asSequence()
|
||||||
.map { entry ->
|
.map { entry ->
|
||||||
val packageName = entry.sbn.packageName
|
val packageName = entry.sbn.packageName
|
||||||
|
val uid = entry.sbn.uid
|
||||||
val notificationObjectUsage =
|
val notificationObjectUsage =
|
||||||
notificationMemoryUse(entry.sbn.notification, hashSetOf())
|
notificationMemoryUse(entry.sbn.notification, hashSetOf())
|
||||||
val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
|
val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
|
||||||
NotificationMemoryUsage(
|
NotificationMemoryUsage(
|
||||||
packageName,
|
packageName,
|
||||||
|
uid,
|
||||||
NotificationUtils.logKey(entry.sbn.key),
|
NotificationUtils.logKey(entry.sbn.key),
|
||||||
|
entry.sbn.notification,
|
||||||
notificationObjectUsage,
|
notificationObjectUsage,
|
||||||
notificationViewUsage
|
notificationViewUsage
|
||||||
)
|
)
|
||||||
@@ -49,7 +61,9 @@ internal object NotificationMemoryMeter {
|
|||||||
): NotificationMemoryUsage {
|
): NotificationMemoryUsage {
|
||||||
return NotificationMemoryUsage(
|
return NotificationMemoryUsage(
|
||||||
entry.sbn.packageName,
|
entry.sbn.packageName,
|
||||||
|
entry.sbn.uid,
|
||||||
NotificationUtils.logKey(entry.sbn.key),
|
NotificationUtils.logKey(entry.sbn.key),
|
||||||
|
entry.sbn.notification,
|
||||||
notificationMemoryUse(entry.sbn.notification, seenBitmaps),
|
notificationMemoryUse(entry.sbn.notification, seenBitmaps),
|
||||||
NotificationMemoryViewWalker.getViewUsage(entry.row)
|
NotificationMemoryViewWalker.getViewUsage(entry.row)
|
||||||
)
|
)
|
||||||
@@ -116,7 +130,13 @@ internal object NotificationMemoryMeter {
|
|||||||
val wearExtenderBackground =
|
val wearExtenderBackground =
|
||||||
computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
|
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 hasCustomView = notification.contentView != null || notification.bigContentView != null
|
||||||
val extrasSize = computeBundleSize(extras)
|
val extrasSize = computeBundleSize(extras)
|
||||||
|
|
||||||
@@ -124,7 +144,7 @@ internal object NotificationMemoryMeter {
|
|||||||
smallIcon = smallIconUse,
|
smallIcon = smallIconUse,
|
||||||
largeIcon = largeIconUse,
|
largeIcon = largeIconUse,
|
||||||
extras = extrasSize,
|
extras = extrasSize,
|
||||||
style = style?.simpleName,
|
style = style,
|
||||||
styleIcon =
|
styleIcon =
|
||||||
bigPictureIconUse +
|
bigPictureIconUse +
|
||||||
peopleUse +
|
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
|
* Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
|
||||||
* bitmaps). Can be slow.
|
* 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
|
* @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) {
|
when (icon?.type) {
|
||||||
Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
|
Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
|
||||||
Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
|
Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
|
||||||
|
|||||||
@@ -18,11 +18,10 @@
|
|||||||
package com.android.systemui.statusbar.notification.logging
|
package com.android.systemui.statusbar.notification.logging
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.systemui.Dumpable
|
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.flags.FeatureFlags
|
||||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
import com.android.systemui.flags.Flags
|
||||||
import java.io.PrintWriter
|
import dagger.Lazy
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/** This class monitors and logs current Notification memory use. */
|
/** This class monitors and logs current Notification memory use. */
|
||||||
@@ -30,9 +29,10 @@ import javax.inject.Inject
|
|||||||
class NotificationMemoryMonitor
|
class NotificationMemoryMonitor
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
val notificationPipeline: NotifPipeline,
|
private val featureFlags: FeatureFlags,
|
||||||
val dumpManager: DumpManager,
|
private val notificationMemoryDumper: NotificationMemoryDumper,
|
||||||
) : Dumpable {
|
private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
|
||||||
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "NotificationMemory"
|
private const val TAG = "NotificationMemory"
|
||||||
@@ -40,127 +40,10 @@ constructor(
|
|||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
Log.d(TAG, "NotificationMemoryMonitor initialized.")
|
Log.d(TAG, "NotificationMemoryMonitor initialized.")
|
||||||
dumpManager.registerDumpable(javaClass.simpleName, this)
|
notificationMemoryDumper.init()
|
||||||
}
|
if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
|
||||||
|
Log.d(TAG, "Notification memory logging enabled.")
|
||||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
notificationMemoryLogger.get().init()
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
* 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> {
|
fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
@@ -58,42 +62,72 @@ internal object NotificationMemoryViewWalker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The ordering here is significant since it determines deduplication of seen drawables.
|
// The ordering here is significant since it determines deduplication of seen drawables.
|
||||||
return listOf(
|
val perViewUsages =
|
||||||
getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
|
listOf(
|
||||||
getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild),
|
getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
|
||||||
getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
|
getViewUsage(
|
||||||
getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout),
|
ViewType.PRIVATE_CONTRACTED_VIEW,
|
||||||
getTotalUsage(row)
|
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
|
* Calculate total usage of all views - we need to do a separate traversal to make sure we don't
|
||||||
* double count fields.
|
* double count fields.
|
||||||
*/
|
*/
|
||||||
private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage {
|
private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage? {
|
||||||
val totalUsage = UsageBuilder()
|
|
||||||
val seenObjects = hashSetOf<Int>()
|
val seenObjects = hashSetOf<Int>()
|
||||||
|
return getViewUsage(
|
||||||
row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) }
|
ViewType.TOTAL,
|
||||||
row.privateLayout?.let { child ->
|
row.privateLayout?.expandedChild,
|
||||||
for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) {
|
row.privateLayout?.contractedChild,
|
||||||
(view as? ViewGroup)?.let { v ->
|
row.privateLayout?.headsUpChild,
|
||||||
computeViewHierarchyUse(v, totalUsage, seenObjects)
|
row.publicLayout?.expandedChild,
|
||||||
}
|
row.publicLayout?.contractedChild,
|
||||||
}
|
row.publicLayout?.headsUpChild,
|
||||||
}
|
seenObjects = seenObjects
|
||||||
return totalUsage.build(ViewType.TOTAL)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getViewUsage(
|
private fun getViewUsage(
|
||||||
type: ViewType,
|
type: ViewType,
|
||||||
rootView: View?,
|
vararg rootViews: View?,
|
||||||
seenObjects: HashSet<Int> = hashSetOf()
|
seenObjects: HashSet<Int> = hashSetOf()
|
||||||
): NotificationViewUsage {
|
): NotificationViewUsage? {
|
||||||
val usageBuilder = UsageBuilder()
|
val usageBuilder = lazy { UsageBuilder() }
|
||||||
(rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) }
|
rootViews.forEach { rootView ->
|
||||||
return usageBuilder.build(type)
|
(rootView as? ViewGroup)?.let { rootViewGroup ->
|
||||||
|
computeViewHierarchyUse(rootViewGroup, usageBuilder.value, seenObjects)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (usageBuilder.isInitialized()) {
|
||||||
|
usageBuilder.value.build(type)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeViewHierarchyUse(
|
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.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
|
import android.stats.sysui.NotificationEnums
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
@@ -50,7 +51,27 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 3316,
|
extras = 3316,
|
||||||
bigPicture = 0,
|
bigPicture = 0,
|
||||||
extender = 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,
|
styleIcon = 0,
|
||||||
hasCustomView = false,
|
hasCustomView = false,
|
||||||
)
|
)
|
||||||
@@ -69,7 +90,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 3316,
|
extras = 3316,
|
||||||
bigPicture = 0,
|
bigPicture = 0,
|
||||||
extender = 0,
|
extender = 0,
|
||||||
style = null,
|
style = NotificationEnums.STYLE_NONE,
|
||||||
styleIcon = 0,
|
styleIcon = 0,
|
||||||
hasCustomView = false,
|
hasCustomView = false,
|
||||||
)
|
)
|
||||||
@@ -92,7 +113,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 3384,
|
extras = 3384,
|
||||||
bigPicture = 0,
|
bigPicture = 0,
|
||||||
extender = 0,
|
extender = 0,
|
||||||
style = null,
|
style = NotificationEnums.STYLE_NONE,
|
||||||
styleIcon = 0,
|
styleIcon = 0,
|
||||||
hasCustomView = true,
|
hasCustomView = true,
|
||||||
)
|
)
|
||||||
@@ -112,7 +133,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 3212,
|
extras = 3212,
|
||||||
bigPicture = 0,
|
bigPicture = 0,
|
||||||
extender = 0,
|
extender = 0,
|
||||||
style = null,
|
style = NotificationEnums.STYLE_NONE,
|
||||||
styleIcon = 0,
|
styleIcon = 0,
|
||||||
hasCustomView = false,
|
hasCustomView = false,
|
||||||
)
|
)
|
||||||
@@ -141,7 +162,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 4092,
|
extras = 4092,
|
||||||
bigPicture = bigPicture.bitmap.allocationByteCount,
|
bigPicture = bigPicture.bitmap.allocationByteCount,
|
||||||
extender = 0,
|
extender = 0,
|
||||||
style = "BigPictureStyle",
|
style = NotificationEnums.STYLE_BIG_PICTURE,
|
||||||
styleIcon = bigPictureIcon.bitmap.allocationByteCount,
|
styleIcon = bigPictureIcon.bitmap.allocationByteCount,
|
||||||
hasCustomView = false,
|
hasCustomView = false,
|
||||||
)
|
)
|
||||||
@@ -167,7 +188,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 4084,
|
extras = 4084,
|
||||||
bigPicture = 0,
|
bigPicture = 0,
|
||||||
extender = 0,
|
extender = 0,
|
||||||
style = "CallStyle",
|
style = NotificationEnums.STYLE_CALL,
|
||||||
styleIcon = personIcon.bitmap.allocationByteCount,
|
styleIcon = personIcon.bitmap.allocationByteCount,
|
||||||
hasCustomView = false,
|
hasCustomView = false,
|
||||||
)
|
)
|
||||||
@@ -203,7 +224,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 5024,
|
extras = 5024,
|
||||||
bigPicture = 0,
|
bigPicture = 0,
|
||||||
extender = 0,
|
extender = 0,
|
||||||
style = "MessagingStyle",
|
style = NotificationEnums.STYLE_MESSAGING,
|
||||||
styleIcon =
|
styleIcon =
|
||||||
personIcon.bitmap.allocationByteCount +
|
personIcon.bitmap.allocationByteCount +
|
||||||
historicPersonIcon.bitmap.allocationByteCount,
|
historicPersonIcon.bitmap.allocationByteCount,
|
||||||
@@ -225,7 +246,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 3612,
|
extras = 3612,
|
||||||
bigPicture = 0,
|
bigPicture = 0,
|
||||||
extender = 556656,
|
extender = 556656,
|
||||||
style = null,
|
style = NotificationEnums.STYLE_NONE,
|
||||||
styleIcon = 0,
|
styleIcon = 0,
|
||||||
hasCustomView = false,
|
hasCustomView = false,
|
||||||
)
|
)
|
||||||
@@ -246,7 +267,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras = 3820,
|
extras = 3820,
|
||||||
bigPicture = 0,
|
bigPicture = 0,
|
||||||
extender = 388 + wearBackground.allocationByteCount,
|
extender = 388 + wearBackground.allocationByteCount,
|
||||||
style = null,
|
style = NotificationEnums.STYLE_NONE,
|
||||||
styleIcon = 0,
|
styleIcon = 0,
|
||||||
hasCustomView = false,
|
hasCustomView = false,
|
||||||
)
|
)
|
||||||
@@ -272,7 +293,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
extras: Int,
|
extras: Int,
|
||||||
bigPicture: Int,
|
bigPicture: Int,
|
||||||
extender: Int,
|
extender: Int,
|
||||||
style: String?,
|
style: Int,
|
||||||
styleIcon: Int,
|
styleIcon: Int,
|
||||||
hasCustomView: Boolean,
|
hasCustomView: Boolean,
|
||||||
) {
|
) {
|
||||||
@@ -282,11 +303,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
|
|||||||
assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
|
assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
|
||||||
assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
|
assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
|
||||||
assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
|
assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
|
||||||
if (style == null) {
|
assertThat(memoryUse.objectUsage.style).isEqualTo(style)
|
||||||
assertThat(memoryUse.objectUsage.style).isNull()
|
|
||||||
} else {
|
|
||||||
assertThat(memoryUse.objectUsage.style).isEqualTo(style)
|
|
||||||
}
|
|
||||||
assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
|
assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
|
||||||
assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
|
assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.testing.TestableLooper
|
|||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
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.statusbar.notification.row.NotificationTestHelper
|
||||||
import com.android.systemui.tests.R
|
import com.android.systemui.tests.R
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
@@ -39,16 +40,84 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
|||||||
fun testViewWalker_plainNotification() {
|
fun testViewWalker_plainNotification() {
|
||||||
val row = testHelper.createRow()
|
val row = testHelper.createRow()
|
||||||
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
||||||
assertThat(result).hasSize(5)
|
assertThat(result).hasSize(3)
|
||||||
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)
|
assertThat(result)
|
||||||
.contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
|
.contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
|
||||||
assertThat(result)
|
assertThat(result)
|
||||||
.contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0))
|
.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)
|
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
|
@Test
|
||||||
@@ -67,7 +136,7 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
||||||
assertThat(result).hasSize(5)
|
assertThat(result).hasSize(3)
|
||||||
assertThat(result)
|
assertThat(result)
|
||||||
.contains(
|
.contains(
|
||||||
NotificationViewUsage(
|
NotificationViewUsage(
|
||||||
@@ -95,8 +164,20 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
|||||||
icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
|
icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// Due to deduplication, this should all be 0.
|
assertThat(result)
|
||||||
assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
|
.contains(
|
||||||
|
NotificationViewUsage(
|
||||||
|
ViewType.TOTAL,
|
||||||
|
icon.bitmap.allocationByteCount,
|
||||||
|
largeIcon.bitmap.allocationByteCount,
|
||||||
|
0,
|
||||||
|
bigPicture.allocationByteCount,
|
||||||
|
0,
|
||||||
|
bigPicture.allocationByteCount +
|
||||||
|
icon.bitmap.allocationByteCount +
|
||||||
|
largeIcon.bitmap.allocationByteCount
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -117,7 +198,7 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
val result = NotificationMemoryViewWalker.getViewUsage(row)
|
||||||
assertThat(result).hasSize(5)
|
assertThat(result).hasSize(3)
|
||||||
assertThat(result)
|
assertThat(result)
|
||||||
.contains(
|
.contains(
|
||||||
NotificationViewUsage(
|
NotificationViewUsage(
|
||||||
@@ -142,7 +223,17 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
|
|||||||
bitmap.allocationByteCount + icon.bitmap.allocationByteCount
|
bitmap.allocationByteCount + icon.bitmap.allocationByteCount
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// Due to deduplication, this should all be 0.
|
assertThat(result)
|
||||||
assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
|
.contains(
|
||||||
|
NotificationViewUsage(
|
||||||
|
ViewType.TOTAL,
|
||||||
|
icon.bitmap.allocationByteCount,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
bitmap.allocationByteCount,
|
||||||
|
bitmap.allocationByteCount + icon.bitmap.allocationByteCount
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user