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 INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
|
||||||
|
|
||||||
|
public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
|
||||||
|
false);
|
||||||
|
|
||||||
// next id: 112
|
// next id: 112
|
||||||
|
|
||||||
/***************************************/
|
/***************************************/
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.init
|
|||||||
import android.service.notification.StatusBarNotification
|
import android.service.notification.StatusBarNotification
|
||||||
import com.android.systemui.ForegroundServiceNotificationListener
|
import com.android.systemui.ForegroundServiceNotificationListener
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
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.people.widget.PeopleSpaceWidgetManager
|
||||||
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
|
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
|
||||||
import com.android.systemui.statusbar.NotificationListener
|
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.notifcollection.NotifCollectionListener
|
||||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
|
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
|
||||||
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
|
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.row.NotifBindPipelineInitializer
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
|
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
|
||||||
import com.android.systemui.statusbar.phone.CentralSurfaces
|
import com.android.systemui.statusbar.phone.CentralSurfaces
|
||||||
@@ -68,6 +71,8 @@ class NotificationsControllerImpl @Inject constructor(
|
|||||||
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
|
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
|
||||||
private val bubblesOptional: Optional<Bubbles>,
|
private val bubblesOptional: Optional<Bubbles>,
|
||||||
private val fgsNotifListener: ForegroundServiceNotificationListener,
|
private val fgsNotifListener: ForegroundServiceNotificationListener,
|
||||||
|
private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
|
||||||
|
private val featureFlags: FeatureFlags
|
||||||
) : NotificationsController {
|
) : NotificationsController {
|
||||||
|
|
||||||
override fun initialize(
|
override fun initialize(
|
||||||
@@ -107,6 +112,9 @@ class NotificationsControllerImpl @Inject constructor(
|
|||||||
|
|
||||||
peopleSpaceWidgetManager.attach(notificationListener)
|
peopleSpaceWidgetManager.attach(notificationListener)
|
||||||
fgsNotifListener.init()
|
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
|
// 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