1 /*
2  *
3  * Copyright (C) 2022 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.systemui.statusbar.notification.logging
19 
20 import android.app.Notification
21 import android.app.PendingIntent
22 import android.app.Person
23 import android.content.Intent
24 import android.graphics.Bitmap
25 import android.graphics.drawable.Icon
26 import android.stats.sysui.NotificationEnums
27 import android.widget.RemoteViews
28 import androidx.test.ext.junit.runners.AndroidJUnit4
29 import androidx.test.filters.SmallTest
30 import com.android.systemui.SysuiTestCase
31 import com.android.systemui.statusbar.notification.NotificationUtils
32 import com.android.systemui.statusbar.notification.collection.NotificationEntry
33 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
34 import com.google.common.truth.Truth.assertThat
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 
38 @SmallTest
39 @RunWith(AndroidJUnit4::class)
40 class NotificationMemoryMeterTest : SysuiTestCase() {
41 
42     @Test
currentNotificationMemoryUse_plainNotificationnull43     fun currentNotificationMemoryUse_plainNotification() {
44         val notification = createBasicNotification().build()
45         val memoryUse =
46             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
47         assertNotificationObjectSizes(
48             memoryUse,
49             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
50             largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
51             extras = 3316,
52             bigPicture = 0,
53             extender = 0,
54             style = NotificationEnums.STYLE_NONE,
55             styleIcon = 0,
56             hasCustomView = false,
57         )
58     }
59 
60     @Test
currentNotificationMemoryUse_rankerGroupNotificationnull61     fun currentNotificationMemoryUse_rankerGroupNotification() {
62         val notification = createBasicNotification().build()
63         val memoryUse =
64             NotificationMemoryMeter.notificationMemoryUse(
65                 createNotificationEntry(createBasicNotification().setGroup("ranker_group").build())
66             )
67         assertNotificationObjectSizes(
68             memoryUse,
69             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
70             largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
71             extras = 3316,
72             bigPicture = 0,
73             extender = 0,
74             style = NotificationEnums.STYLE_RANKER_GROUP,
75             styleIcon = 0,
76             hasCustomView = false,
77         )
78     }
79 
80     @Test
currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmapnull81     fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
82         val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
83         val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
84         val memoryUse =
85             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
86         assertNotificationObjectSizes(
87             memoryUse = memoryUse,
88             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
89             largeIcon = 0,
90             extras = 3316,
91             bigPicture = 0,
92             extender = 0,
93             style = NotificationEnums.STYLE_NONE,
94             styleIcon = 0,
95             hasCustomView = false,
96         )
97     }
98 
99     @Test
currentNotificationMemoryUse_customViewNotification_marksTruenull100     fun currentNotificationMemoryUse_customViewNotification_marksTrue() {
101         val notification =
102             createBasicNotification()
103                 .setCustomContentView(
104                     RemoteViews(context.packageName, android.R.layout.list_content)
105                 )
106                 .build()
107         val memoryUse =
108             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
109         assertNotificationObjectSizes(
110             memoryUse = memoryUse,
111             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
112             largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
113             extras = 3384,
114             bigPicture = 0,
115             extender = 0,
116             style = NotificationEnums.STYLE_NONE,
117             styleIcon = 0,
118             hasCustomView = true,
119         )
120     }
121 
122     @Test
currentNotificationMemoryUse_notificationWithDataIcon_calculatesCorrectlynull123     fun currentNotificationMemoryUse_notificationWithDataIcon_calculatesCorrectly() {
124         val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
125         val notification =
126             createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
127         val memoryUse =
128             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
129         assertNotificationObjectSizes(
130             memoryUse = memoryUse,
131             smallIcon = 444444,
132             largeIcon = 0,
133             extras = 3212,
134             bigPicture = 0,
135             extender = 0,
136             style = NotificationEnums.STYLE_NONE,
137             styleIcon = 0,
138             hasCustomView = false,
139         )
140     }
141 
142     @Test
currentNotificationMemoryUse_bigPictureStylenull143     fun currentNotificationMemoryUse_bigPictureStyle() {
144         val bigPicture =
145             Icon.createWithBitmap(Bitmap.createBitmap(600, 400, Bitmap.Config.ARGB_8888))
146         val bigPictureIcon =
147             Icon.createWithAdaptiveBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
148         val notification =
149             createBasicNotification()
150                 .setStyle(
151                     Notification.BigPictureStyle()
152                         .bigPicture(bigPicture)
153                         .bigLargeIcon(bigPictureIcon)
154                 )
155                 .build()
156         val memoryUse =
157             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
158         assertNotificationObjectSizes(
159             memoryUse = memoryUse,
160             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
161             largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
162             extras = 4092,
163             bigPicture = bigPicture.bitmap.allocationByteCount,
164             extender = 0,
165             style = NotificationEnums.STYLE_BIG_PICTURE,
166             styleIcon = bigPictureIcon.bitmap.allocationByteCount,
167             hasCustomView = false,
168         )
169     }
170 
171     @Test
currentNotificationMemoryUse_callingStylenull172     fun currentNotificationMemoryUse_callingStyle() {
173         val personIcon =
174             Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
175         val person = Person.Builder().setIcon(personIcon).setName("Person").build()
176         val fakeIntent =
177             PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
178         val notification =
179             createBasicNotification()
180                 .setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
181                 .build()
182         val memoryUse =
183             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
184         assertNotificationObjectSizes(
185             memoryUse = memoryUse,
186             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
187             largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
188             extras = 4084,
189             bigPicture = 0,
190             extender = 0,
191             style = NotificationEnums.STYLE_CALL,
192             styleIcon = personIcon.bitmap.allocationByteCount,
193             hasCustomView = false,
194         )
195     }
196 
197     @Test
currentNotificationMemoryUse_messagingStylenull198     fun currentNotificationMemoryUse_messagingStyle() {
199         val personIcon =
200             Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
201         val person = Person.Builder().setIcon(personIcon).setName("Person").build()
202         val message = Notification.MessagingStyle.Message("Message!", 4323, person)
203         val historicPersonIcon =
204             Icon.createWithBitmap(Bitmap.createBitmap(348, 382, Bitmap.Config.ARGB_8888))
205         val historicPerson =
206             Person.Builder().setIcon(historicPersonIcon).setName("Historic person").build()
207         val historicMessage =
208             Notification.MessagingStyle.Message("Historic message!", 5848, historicPerson)
209 
210         val notification =
211             createBasicNotification()
212                 .setStyle(
213                     Notification.MessagingStyle(person)
214                         .addMessage(message)
215                         .addHistoricMessage(historicMessage)
216                 )
217                 .build()
218         val memoryUse =
219             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
220         assertNotificationObjectSizes(
221             memoryUse = memoryUse,
222             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
223             largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
224             extras = 5024,
225             bigPicture = 0,
226             extender = 0,
227             style = NotificationEnums.STYLE_MESSAGING,
228             styleIcon =
229                 personIcon.bitmap.allocationByteCount +
230                     historicPersonIcon.bitmap.allocationByteCount,
231             hasCustomView = false,
232         )
233     }
234 
235     @Test
currentNotificationMemoryUse_carExtendernull236     fun currentNotificationMemoryUse_carExtender() {
237         val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
238         val extender = Notification.CarExtender().setLargeIcon(carIcon)
239         val notification = createBasicNotification().extend(extender).build()
240         val memoryUse =
241             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
242         assertNotificationObjectSizes(
243             memoryUse = memoryUse,
244             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
245             largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
246             extras = 3612,
247             bigPicture = 0,
248             extender = 556656,
249             style = NotificationEnums.STYLE_NONE,
250             styleIcon = 0,
251             hasCustomView = false,
252         )
253     }
254 
255     @Test
currentNotificationMemoryUse_tvWearExtendernull256     fun currentNotificationMemoryUse_tvWearExtender() {
257         val tvExtender = Notification.TvExtender().setChannel("channel2")
258         val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
259         val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
260         val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
261         val memoryUse =
262             NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
263         assertNotificationObjectSizes(
264             memoryUse = memoryUse,
265             smallIcon = notification.smallIcon.bitmap.allocationByteCount,
266             largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
267             extras = 3820,
268             bigPicture = 0,
269             extender = 388 + wearBackground.allocationByteCount,
270             style = NotificationEnums.STYLE_NONE,
271             styleIcon = 0,
272             hasCustomView = false,
273         )
274     }
275 
createBasicNotificationnull276     private fun createBasicNotification(): Notification.Builder {
277         val smallIcon =
278             Icon.createWithBitmap(Bitmap.createBitmap(250, 250, Bitmap.Config.ARGB_8888))
279         val largeIcon =
280             Icon.createWithBitmap(Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888))
281         return Notification.Builder(context)
282             .setSmallIcon(smallIcon)
283             .setLargeIcon(largeIcon)
284             .setContentTitle("This is a title")
285             .setContentText("This is content text.")
286     }
287 
288     /** This will generate a nicer error message than comparing objects */
assertNotificationObjectSizesnull289     private fun assertNotificationObjectSizes(
290         memoryUse: NotificationMemoryUsage,
291         smallIcon: Int,
292         largeIcon: Int,
293         extras: Int,
294         bigPicture: Int,
295         extender: Int,
296         style: Int,
297         styleIcon: Int,
298         hasCustomView: Boolean,
299     ) {
300         assertThat(memoryUse.packageName).isEqualTo("test_pkg")
301         assertThat(memoryUse.notificationKey)
302             .isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
303         assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
304         assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
305         assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
306         assertThat(memoryUse.objectUsage.style).isEqualTo(style)
307         assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
308         assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
309     }
310 
getUseObjectnull311     private fun getUseObject(
312         singleItemUseList: List<NotificationMemoryUsage>,
313     ): NotificationMemoryUsage {
314         assertThat(singleItemUseList).hasSize(1)
315         return singleItemUseList[0]
316     }
317 
createNotificationEntrynull318     private fun createNotificationEntry(
319         notification: Notification,
320     ): NotificationEntry =
321         NotificationEntryBuilder().setTag("test").setNotification(notification).build()
322 }
323