Merge "Implement class to evaluate current use of Notification object memory" into tm-qpr-dev
This commit is contained in:
@@ -68,6 +68,9 @@ public class Flags {
|
||||
|
||||
public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
|
||||
|
||||
public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
|
||||
false);
|
||||
|
||||
// next id: 112
|
||||
|
||||
/***************************************/
|
||||
|
||||
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.init
|
||||
import android.service.notification.StatusBarNotification
|
||||
import com.android.systemui.ForegroundServiceNotificationListener
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
|
||||
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
|
||||
import com.android.systemui.statusbar.NotificationListener
|
||||
@@ -36,6 +38,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
|
||||
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
|
||||
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
|
||||
import com.android.systemui.statusbar.phone.CentralSurfaces
|
||||
@@ -68,6 +71,8 @@ class NotificationsControllerImpl @Inject constructor(
|
||||
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
|
||||
private val bubblesOptional: Optional<Bubbles>,
|
||||
private val fgsNotifListener: ForegroundServiceNotificationListener,
|
||||
private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
|
||||
private val featureFlags: FeatureFlags
|
||||
) : NotificationsController {
|
||||
|
||||
override fun initialize(
|
||||
@@ -107,6 +112,9 @@ class NotificationsControllerImpl @Inject constructor(
|
||||
|
||||
peopleSpaceWidgetManager.attach(notificationListener)
|
||||
fgsNotifListener.init()
|
||||
if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
|
||||
memoryMonitor.get().init()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Convert all functions below this line into listeners instead of public methods
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
*
|
||||
* 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
|
||||
|
||||
/** Describes usage of a notification. */
|
||||
data class NotificationMemoryUsage(
|
||||
val packageName: String,
|
||||
val notificationId: String,
|
||||
val objectUsage: NotificationObjectUsage,
|
||||
)
|
||||
|
||||
/**
|
||||
* Describes current memory usage of a [android.app.Notification] object.
|
||||
*
|
||||
* The values are in bytes.
|
||||
*/
|
||||
data class NotificationObjectUsage(
|
||||
val smallIcon: Int,
|
||||
val largeIcon: Int,
|
||||
val extras: Int,
|
||||
val style: String?,
|
||||
val styleIcon: Int,
|
||||
val bigPicture: Int,
|
||||
val extender: Int,
|
||||
val hasCustomView: Boolean,
|
||||
)
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
*
|
||||
* 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.Person
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.util.contains
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.statusbar.notification.NotificationUtils
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import java.io.PrintWriter
|
||||
import javax.inject.Inject
|
||||
|
||||
/** This class monitors and logs current Notification memory use. */
|
||||
@SysUISingleton
|
||||
class NotificationMemoryMonitor
|
||||
@Inject
|
||||
constructor(
|
||||
val notificationPipeline: NotifPipeline,
|
||||
val dumpManager: DumpManager,
|
||||
) : Dumpable {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "NotificationMemMonitor"
|
||||
private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
|
||||
private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
|
||||
private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
|
||||
private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
|
||||
private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
|
||||
}
|
||||
|
||||
fun init() {
|
||||
Log.d(TAG, "NotificationMemoryMonitor initialized.")
|
||||
dumpManager.registerDumpable(javaClass.simpleName, this)
|
||||
}
|
||||
|
||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||
currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
|
||||
return notificationMemoryUse(notificationPipeline.allNotifs)
|
||||
}
|
||||
|
||||
/** Returns a list of memory use entries for currently shown notifications. */
|
||||
@WorkerThread
|
||||
fun notificationMemoryUse(
|
||||
notifications: Collection<NotificationEntry>
|
||||
): List<NotificationMemoryUsage> {
|
||||
return notifications
|
||||
.asSequence()
|
||||
.map { entry ->
|
||||
val packageName = entry.sbn.packageName
|
||||
val notificationObjectUsage =
|
||||
computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
|
||||
NotificationMemoryUsage(
|
||||
packageName,
|
||||
NotificationUtils.logKey(entry.sbn.key),
|
||||
notificationObjectUsage
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the estimated memory usage of a given [Notification] object. It'll attempt to
|
||||
* inspect Bitmaps in the object and provide summary of memory usage.
|
||||
*/
|
||||
private fun computeNotificationObjectUse(
|
||||
notification: Notification,
|
||||
seenBitmaps: HashSet<Int>
|
||||
): NotificationObjectUsage {
|
||||
val extras = notification.extras
|
||||
val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
|
||||
val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
|
||||
|
||||
// Collect memory usage of extra styles
|
||||
|
||||
// Big Picture
|
||||
val bigPictureIconUse =
|
||||
computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
|
||||
computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
|
||||
val bigPictureUse =
|
||||
computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
|
||||
computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
|
||||
|
||||
// People
|
||||
val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
|
||||
val peopleUse =
|
||||
peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
|
||||
|
||||
// Calling
|
||||
val callingPersonUse =
|
||||
computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
|
||||
val verificationIconUse =
|
||||
computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
|
||||
|
||||
// Messages
|
||||
val messages =
|
||||
Notification.MessagingStyle.Message.getMessagesFromBundleArray(
|
||||
extras.getParcelableArray(Notification.EXTRA_MESSAGES)
|
||||
)
|
||||
val messagesUse =
|
||||
messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
|
||||
val historicMessages =
|
||||
Notification.MessagingStyle.Message.getMessagesFromBundleArray(
|
||||
extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
|
||||
)
|
||||
val historyicMessagesUse =
|
||||
historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
|
||||
|
||||
// Extenders
|
||||
val carExtender = extras.getBundle(CAR_EXTENSIONS)
|
||||
val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
|
||||
val carExtenderIcon =
|
||||
computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
|
||||
|
||||
val tvExtender = extras.getBundle(TV_EXTENSIONS)
|
||||
val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
|
||||
|
||||
val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
|
||||
val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
|
||||
val wearExtenderBackground =
|
||||
computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
|
||||
|
||||
val style = notification.notificationStyle
|
||||
val hasCustomView = notification.contentView != null || notification.bigContentView != null
|
||||
val extrasSize = computeBundleSize(extras)
|
||||
|
||||
return NotificationObjectUsage(
|
||||
smallIconUse,
|
||||
largeIconUse,
|
||||
extrasSize,
|
||||
style?.simpleName,
|
||||
bigPictureIconUse +
|
||||
peopleUse +
|
||||
callingPersonUse +
|
||||
verificationIconUse +
|
||||
messagesUse +
|
||||
historyicMessagesUse,
|
||||
bigPictureUse,
|
||||
carExtenderSize +
|
||||
carExtenderIcon +
|
||||
tvExtenderSize +
|
||||
wearExtenderSize +
|
||||
wearExtenderBackground,
|
||||
hasCustomView
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
|
||||
* bitmaps). Can be slow.
|
||||
*/
|
||||
private fun computeBundleSize(extras: Bundle): Int {
|
||||
val parcel = Parcel.obtain()
|
||||
try {
|
||||
extras.writeToParcel(parcel, 0)
|
||||
return parcel.dataSize()
|
||||
} finally {
|
||||
parcel.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
|
||||
* if the key does not exist in extras.
|
||||
*/
|
||||
private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
|
||||
return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
|
||||
is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
|
||||
is Icon -> computeIconUse(parcelable, seenBitmaps)
|
||||
is Person -> computeIconUse(parcelable.icon, seenBitmaps)
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
|
||||
* defined via Uri or a resource.
|
||||
*
|
||||
* @return memory usage in bytes or 0 if the icon is Uri/Resource based
|
||||
*/
|
||||
private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
|
||||
when (icon?.type) {
|
||||
Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
|
||||
Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
|
||||
Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
|
||||
else -> 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
|
||||
* seenBitmaps set, this method returns 0 to avoid double counting.
|
||||
*
|
||||
* @return memory usage of the bitmap in bytes
|
||||
*/
|
||||
private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
|
||||
val refId = System.identityHashCode(bitmap)
|
||||
if (seenBitmaps?.contains(refId) == true) {
|
||||
return 0
|
||||
}
|
||||
|
||||
seenBitmaps?.add(refId)
|
||||
return bitmap.allocationByteCount
|
||||
}
|
||||
|
||||
private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
|
||||
val refId = System.identityHashCode(icon.dataBytes)
|
||||
if (seenBitmaps.contains(refId)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
seenBitmaps.add(refId)
|
||||
return icon.dataLength
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
*
|
||||
* 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.PendingIntent
|
||||
import android.app.Person
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Icon
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.widget.RemoteViews
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.notification.NotificationUtils
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class NotificationMemoryMonitorTest : SysuiTestCase() {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_plainNotification() {
|
||||
val notification = createBasicNotification().build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
|
||||
extras = 3316,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = null,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
|
||||
val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
|
||||
val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse = memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = 0,
|
||||
extras = 3316,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = null,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_customViewNotification_marksTrue() {
|
||||
val notification =
|
||||
createBasicNotification()
|
||||
.setCustomContentView(
|
||||
RemoteViews(context.packageName, android.R.layout.list_content)
|
||||
)
|
||||
.build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse = memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
|
||||
extras = 3384,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = null,
|
||||
styleIcon = 0,
|
||||
hasCustomView = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_notificationWithDataIcon_calculatesCorrectly() {
|
||||
val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
|
||||
val notification =
|
||||
createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse = memoryUse,
|
||||
smallIcon = 444444,
|
||||
largeIcon = 0,
|
||||
extras = 3212,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = null,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_bigPictureStyle() {
|
||||
val bigPicture =
|
||||
Icon.createWithBitmap(Bitmap.createBitmap(600, 400, Bitmap.Config.ARGB_8888))
|
||||
val bigPictureIcon =
|
||||
Icon.createWithAdaptiveBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
|
||||
val notification =
|
||||
createBasicNotification()
|
||||
.setStyle(
|
||||
Notification.BigPictureStyle()
|
||||
.bigPicture(bigPicture)
|
||||
.bigLargeIcon(bigPictureIcon)
|
||||
)
|
||||
.build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse = memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
|
||||
extras = 4092,
|
||||
bigPicture = bigPicture.bitmap.allocationByteCount,
|
||||
extender = 0,
|
||||
style = "BigPictureStyle",
|
||||
styleIcon = bigPictureIcon.bitmap.allocationByteCount,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_callingStyle() {
|
||||
val personIcon =
|
||||
Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
|
||||
val person = Person.Builder().setIcon(personIcon).setName("Person").build()
|
||||
val fakeIntent =
|
||||
PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
|
||||
val notification =
|
||||
createBasicNotification()
|
||||
.setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
|
||||
.build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse = memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
|
||||
extras = 4084,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = "CallStyle",
|
||||
styleIcon = personIcon.bitmap.allocationByteCount,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_messagingStyle() {
|
||||
val personIcon =
|
||||
Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
|
||||
val person = Person.Builder().setIcon(personIcon).setName("Person").build()
|
||||
val message = Notification.MessagingStyle.Message("Message!", 4323, person)
|
||||
val historicPersonIcon =
|
||||
Icon.createWithBitmap(Bitmap.createBitmap(348, 382, Bitmap.Config.ARGB_8888))
|
||||
val historicPerson =
|
||||
Person.Builder().setIcon(historicPersonIcon).setName("Historic person").build()
|
||||
val historicMessage =
|
||||
Notification.MessagingStyle.Message("Historic message!", 5848, historicPerson)
|
||||
|
||||
val notification =
|
||||
createBasicNotification()
|
||||
.setStyle(
|
||||
Notification.MessagingStyle(person)
|
||||
.addMessage(message)
|
||||
.addHistoricMessage(historicMessage)
|
||||
)
|
||||
.build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse = memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
|
||||
extras = 5024,
|
||||
bigPicture = 0,
|
||||
extender = 0,
|
||||
style = "MessagingStyle",
|
||||
styleIcon =
|
||||
personIcon.bitmap.allocationByteCount +
|
||||
historicPersonIcon.bitmap.allocationByteCount,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_carExtender() {
|
||||
val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
|
||||
val extender = Notification.CarExtender().setLargeIcon(carIcon)
|
||||
val notification = createBasicNotification().extend(extender).build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse = memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
|
||||
extras = 3612,
|
||||
bigPicture = 0,
|
||||
extender = 556656,
|
||||
style = null,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentNotificationMemoryUse_tvWearExtender() {
|
||||
val tvExtender = Notification.TvExtender().setChannel("channel2")
|
||||
val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
|
||||
val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
|
||||
val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
|
||||
val nmm = createNMMWithNotifications(listOf(notification))
|
||||
val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
|
||||
assertNotificationObjectSizes(
|
||||
memoryUse = memoryUse,
|
||||
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
|
||||
largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
|
||||
extras = 3820,
|
||||
bigPicture = 0,
|
||||
extender = 388 + wearBackground.allocationByteCount,
|
||||
style = null,
|
||||
styleIcon = 0,
|
||||
hasCustomView = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createBasicNotification(): Notification.Builder {
|
||||
val smallIcon =
|
||||
Icon.createWithBitmap(Bitmap.createBitmap(250, 250, Bitmap.Config.ARGB_8888))
|
||||
val largeIcon =
|
||||
Icon.createWithBitmap(Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888))
|
||||
return Notification.Builder(context)
|
||||
.setSmallIcon(smallIcon)
|
||||
.setLargeIcon(largeIcon)
|
||||
.setContentTitle("This is a title")
|
||||
.setContentText("This is content text.")
|
||||
}
|
||||
|
||||
/** This will generate a nicer error message than comparing objects */
|
||||
private fun assertNotificationObjectSizes(
|
||||
memoryUse: NotificationMemoryUsage,
|
||||
smallIcon: Int,
|
||||
largeIcon: Int,
|
||||
extras: Int,
|
||||
bigPicture: Int,
|
||||
extender: Int,
|
||||
style: String?,
|
||||
styleIcon: Int,
|
||||
hasCustomView: Boolean
|
||||
) {
|
||||
assertThat(memoryUse.packageName).isEqualTo("test_pkg")
|
||||
assertThat(memoryUse.notificationId)
|
||||
.isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
|
||||
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.styleIcon).isEqualTo(styleIcon)
|
||||
assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
|
||||
}
|
||||
|
||||
private fun getUseObject(
|
||||
singleItemUseList: List<NotificationMemoryUsage>
|
||||
): NotificationMemoryUsage {
|
||||
assertThat(singleItemUseList).hasSize(1)
|
||||
return singleItemUseList[0]
|
||||
}
|
||||
|
||||
private fun createNMMWithNotifications(
|
||||
notifications: List<Notification>
|
||||
): NotificationMemoryMonitor {
|
||||
val notifPipeline: NotifPipeline = mock()
|
||||
val notificationEntries =
|
||||
notifications.map { n ->
|
||||
NotificationEntryBuilder().setTag("test").setNotification(n).build()
|
||||
}
|
||||
whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
|
||||
return NotificationMemoryMonitor(notifPipeline, mock())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user