1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:OptIn(ExperimentalCoroutinesApi::class)
18 
19 package com.android.systemui.statusbar.notification.icon
20 
21 import android.app.ActivityManager
22 import android.app.Notification
23 import android.app.NotificationChannel
24 import android.app.NotificationManager.IMPORTANCE_DEFAULT
25 import android.app.Person
26 import android.content.pm.LauncherApps
27 import android.content.pm.ShortcutInfo
28 import android.graphics.drawable.Drawable
29 import android.graphics.drawable.Icon
30 import android.os.Bundle
31 import android.os.SystemClock
32 import android.os.UserHandle
33 import android.platform.test.annotations.DisableFlags
34 import android.platform.test.annotations.EnableFlags
35 import androidx.test.InstrumentationRegistry
36 import androidx.test.ext.junit.runners.AndroidJUnit4
37 import androidx.test.filters.SmallTest
38 import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
39 import com.android.systemui.SysuiTestCase
40 import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any
41 import com.android.systemui.statusbar.notification.collection.NotificationEntry
42 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
43 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
44 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
45 import com.google.common.truth.Truth.assertThat
46 import kotlinx.coroutines.ExperimentalCoroutinesApi
47 import kotlinx.coroutines.test.StandardTestDispatcher
48 import kotlinx.coroutines.test.TestScope
49 import kotlinx.coroutines.test.runCurrent
50 import org.junit.Before
51 import org.junit.Test
52 import org.junit.runner.RunWith
53 import org.mockito.Mock
54 import org.mockito.Mockito.anyInt
55 import org.mockito.Mockito.`when`
56 import org.mockito.MockitoAnnotations
57 
58 @SmallTest
59 @RunWith(AndroidJUnit4::class)
60 class IconManagerTest : SysuiTestCase() {
61     companion object {
62         private const val TEST_PACKAGE_NAME = "test"
63 
64         private const val TEST_UID = 0
65     }
66 
67     private var id = 0
68     private val context = InstrumentationRegistry.getTargetContext()
69 
70     @Mock private lateinit var shortcut: ShortcutInfo
71     @Mock private lateinit var shortcutIc: Icon
72     @Mock private lateinit var messageIc: Icon
73     @Mock private lateinit var largeIc: Icon
74     @Mock private lateinit var smallIc: Icon
75     @Mock private lateinit var drawable: Drawable
76     @Mock private lateinit var row: ExpandableNotificationRow
77 
78     @Mock private lateinit var notifCollection: CommonNotifCollection
79     @Mock private lateinit var launcherApps: LauncherApps
80 
81     private val testDispatcher = StandardTestDispatcher()
82     private val testScope = TestScope(testDispatcher)
83     private val mainContext = testScope.coroutineContext
84     private val bgContext = testScope.backgroundScope.coroutineContext
85 
86     private val iconBuilder = IconBuilder(context)
87 
88     private lateinit var iconManager: IconManager
89 
90     @Before
setUpnull91     fun setUp() {
92         MockitoAnnotations.initMocks(this)
93 
94         `when`(shortcutIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable)
95         `when`(messageIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable)
96         `when`(largeIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable)
97         `when`(smallIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable)
98 
99         `when`(shortcut.icon).thenReturn(shortcutIc)
100         `when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc)
101 
102         iconManager =
103             IconManager(
104                 notifCollection,
105                 launcherApps,
106                 iconBuilder,
107                 testScope,
108                 bgContext,
109                 mainContext,
110             )
111     }
112 
113     @Test
114     @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
testCreateIcons_chipNotifIconFlagDisabled_statusBarChipIconIsNullnull115     fun testCreateIcons_chipNotifIconFlagDisabled_statusBarChipIconIsNull() {
116         val entry =
117             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
118         entry?.let { iconManager.createIcons(it) }
119         testScope.runCurrent()
120 
121         assertThat(entry?.icons?.statusBarChipIcon).isNull()
122     }
123 
124     @Test
125     @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
testCreateIcons_chipNotifIconFlagEnabled_statusBarChipIconIsNullnull126     fun testCreateIcons_chipNotifIconFlagEnabled_statusBarChipIconIsNull() {
127         val entry =
128             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
129         entry?.let { iconManager.createIcons(it) }
130         testScope.runCurrent()
131 
132         assertThat(entry?.icons?.statusBarChipIcon).isNotNull()
133     }
134 
135     @Test
testCreateIcons_importantConversation_shortcutIconnull136     fun testCreateIcons_importantConversation_shortcutIcon() {
137         val entry =
138             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
139         entry?.channel?.isImportantConversation = true
140         entry?.let { iconManager.createIcons(it) }
141         testScope.runCurrent()
142         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
143     }
144 
145     @Test
testCreateIcons_importantConversation_messageIconnull146     fun testCreateIcons_importantConversation_messageIcon() {
147         val entry =
148             notificationEntry(hasShortcut = false, hasMessageSenderIcon = true, hasLargeIcon = true)
149         entry?.channel?.isImportantConversation = true
150         entry?.let { iconManager.createIcons(it) }
151         testScope.runCurrent()
152         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(messageIc)
153     }
154 
155     @Test
testCreateIcons_importantConversation_largeIconnull156     fun testCreateIcons_importantConversation_largeIcon() {
157         val entry =
158             notificationEntry(
159                 hasShortcut = false,
160                 hasMessageSenderIcon = false,
161                 hasLargeIcon = true
162             )
163         entry?.channel?.isImportantConversation = true
164         entry?.let { iconManager.createIcons(it) }
165         testScope.runCurrent()
166         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(largeIc)
167     }
168 
169     @Test
testCreateIcons_importantConversation_smallIconnull170     fun testCreateIcons_importantConversation_smallIcon() {
171         val entry =
172             notificationEntry(
173                 hasShortcut = false,
174                 hasMessageSenderIcon = false,
175                 hasLargeIcon = false
176             )
177         entry?.channel?.isImportantConversation = true
178         entry?.let { iconManager.createIcons(it) }
179         testScope.runCurrent()
180         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
181     }
182 
183     @Test
testCreateIcons_importantConversationWithoutMessagingStylenull184     fun testCreateIcons_importantConversationWithoutMessagingStyle() {
185         val entry =
186             notificationEntry(
187                 hasShortcut = true,
188                 hasMessageSenderIcon = true,
189                 useMessagingStyle = false,
190                 hasLargeIcon = true
191             )
192         entry?.channel?.isImportantConversation = true
193         entry?.let { iconManager.createIcons(it) }
194         testScope.runCurrent()
195         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
196     }
197 
198     @Test
testCreateIcons_notImportantConversationnull199     fun testCreateIcons_notImportantConversation() {
200         val entry =
201             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
202         entry?.let { iconManager.createIcons(it) }
203         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
204     }
205 
206     @Test
207     @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
testCreateIcons_sensitiveImportantConversationnull208     fun testCreateIcons_sensitiveImportantConversation() {
209         val entry =
210             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
211         entry?.setSensitive(true, true)
212         entry?.channel?.isImportantConversation = true
213         entry?.let { iconManager.createIcons(it) }
214         testScope.runCurrent()
215         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
216         assertThat(entry?.icons?.statusBarChipIcon?.sourceIcon).isEqualTo(shortcutIc)
217         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
218         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
219     }
220 
221     @Test
222     @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
testUpdateIcons_sensitiveImportantConversationnull223     fun testUpdateIcons_sensitiveImportantConversation() {
224         val entry =
225             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
226         entry?.setSensitive(true, true)
227         entry?.channel?.isImportantConversation = true
228         entry?.let { iconManager.createIcons(it) }
229         // Updating the icons after creation shouldn't break anything
230         entry?.let { iconManager.updateIcons(it) }
231         testScope.runCurrent()
232         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
233         assertThat(entry?.icons?.statusBarChipIcon?.sourceIcon).isEqualTo(shortcutIc)
234         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
235         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
236     }
237 
238     @Test
testUpdateIcons_sensitivityChangenull239     fun testUpdateIcons_sensitivityChange() {
240         val entry =
241             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
242         entry?.channel?.isImportantConversation = true
243         entry?.setSensitive(true, true)
244         entry?.let { iconManager.createIcons(it) }
245         testScope.runCurrent()
246         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
247         entry?.setSensitive(false, false)
248         entry?.let { iconManager.updateIcons(it) }
249         testScope.runCurrent()
250         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(shortcutIc)
251     }
252 
notificationEntrynull253     private fun notificationEntry(
254         hasShortcut: Boolean,
255         hasMessageSenderIcon: Boolean,
256         useMessagingStyle: Boolean = true,
257         hasLargeIcon: Boolean
258     ): NotificationEntry? {
259         val n =
260             Notification.Builder(mContext, "id")
261                 .setSmallIcon(smallIc)
262                 .setContentTitle("Title")
263                 .setContentText("Text")
264 
265         val messagingStyle =
266             Notification.MessagingStyle("")
267                 .addMessage(
268                     Notification.MessagingStyle.Message(
269                         "",
270                         SystemClock.currentThreadTimeMillis(),
271                         Person.Builder()
272                             .setIcon(if (hasMessageSenderIcon) messageIc else null)
273                             .build()
274                     )
275                 )
276         if (useMessagingStyle) {
277             n.style = messagingStyle
278         } else {
279             val bundle = Bundle()
280             messagingStyle.addExtras(bundle, false, 0) // Set extras but not EXTRA_TEMPLATE
281             n.addExtras(bundle)
282         }
283 
284         if (hasLargeIcon) {
285             n.setLargeIcon(largeIc)
286         }
287 
288         val builder =
289             NotificationEntryBuilder()
290                 .setPkg(TEST_PACKAGE_NAME)
291                 .setOpPkg(TEST_PACKAGE_NAME)
292                 .setUid(TEST_UID)
293                 .setId(id++)
294                 .setNotification(n.build())
295                 .setChannel(NotificationChannel("id", "", IMPORTANCE_DEFAULT))
296                 .setUser(UserHandle(ActivityManager.getCurrentUser()))
297 
298         if (hasShortcut) {
299             builder.setShortcutInfo(shortcut)
300         }
301 
302         val entry = builder.build()
303         entry.row = row
304         entry.setSensitive(false, true)
305         return entry
306     }
307 }
308