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