1 /* 2 * Copyright (C) 2025 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 package com.android.launcher3.folder 18 19 import android.R 20 import android.os.Process 21 import androidx.test.ext.junit.runners.AndroidJUnit4 22 import androidx.test.filters.SmallTest 23 import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn 24 import com.android.launcher3.LauncherAppState 25 import com.android.launcher3.LauncherPrefs 26 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER 27 import com.android.launcher3.dagger.LauncherAppComponent 28 import com.android.launcher3.dagger.LauncherAppSingleton 29 import com.android.launcher3.graphics.PreloadIconDrawable 30 import com.android.launcher3.graphics.ThemeManager 31 import com.android.launcher3.icons.BitmapInfo 32 import com.android.launcher3.icons.FastBitmapDrawable 33 import com.android.launcher3.icons.IconCache 34 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver 35 import com.android.launcher3.icons.PlaceHolderIconDrawable 36 import com.android.launcher3.icons.UserBadgeDrawable 37 import com.android.launcher3.model.data.FolderInfo 38 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED 39 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE 40 import com.android.launcher3.model.data.WorkspaceItemInfo 41 import com.android.launcher3.util.ActivityContextWrapper 42 import com.android.launcher3.util.AllModulesForTest 43 import com.android.launcher3.util.Executors 44 import com.android.launcher3.util.FakePrefsModule 45 import com.android.launcher3.util.FlagOp 46 import com.android.launcher3.util.LauncherLayoutBuilder 47 import com.android.launcher3.util.LauncherModelHelper 48 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext 49 import com.android.launcher3.util.TestUtil 50 import com.android.launcher3.util.UserIconInfo 51 import com.google.common.truth.Truth.assertThat 52 import dagger.Component 53 import kotlin.annotation.AnnotationRetention.RUNTIME 54 import kotlin.annotation.AnnotationTarget.FUNCTION 55 import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER 56 import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER 57 import org.junit.After 58 import org.junit.Before 59 import org.junit.Rule 60 import org.junit.Test 61 import org.junit.rules.TestRule 62 import org.junit.runner.Description 63 import org.junit.runner.RunWith 64 import org.junit.runners.model.Statement 65 import org.mockito.MockitoAnnotations 66 import org.mockito.kotlin.any 67 import org.mockito.kotlin.argumentCaptor 68 import org.mockito.kotlin.doReturn 69 import org.mockito.kotlin.eq 70 import org.mockito.kotlin.verify 71 import org.mockito.kotlin.whenever 72 73 /** Tests for [PreviewItemManager] */ 74 @SmallTest 75 @RunWith(AndroidJUnit4::class) 76 class PreviewItemManagerTest { 77 78 @get:Rule val theseStateRule = ThemeStateRule() 79 80 private lateinit var previewItemManager: PreviewItemManager 81 private lateinit var context: SandboxModelContext 82 private lateinit var folderItems: ArrayList<WorkspaceItemInfo> 83 private lateinit var modelHelper: LauncherModelHelper 84 private lateinit var folderIcon: FolderIcon 85 private lateinit var iconCache: IconCache 86 87 @Before setupnull88 fun setup() { 89 MockitoAnnotations.initMocks(this) 90 modelHelper = LauncherModelHelper() 91 context = modelHelper.sandboxContext 92 context.initDaggerComponent(DaggerPreviewItemManagerTestComponent.builder()) 93 theseStateRule.themeState?.let { 94 LauncherPrefs.get(context).putSync(ThemeManager.THEMED_ICONS.to(it)) 95 } 96 folderIcon = FolderIcon(ActivityContextWrapper(context)) 97 98 iconCache = LauncherAppState.INSTANCE[context].iconCache 99 spyOn(iconCache) 100 doReturn(null).whenever(iconCache).updateIconInBackground(any(), any()) 101 102 previewItemManager = PreviewItemManager(folderIcon) 103 modelHelper 104 .setupDefaultLayoutProvider( 105 LauncherLayoutBuilder() 106 .atWorkspace(0, 0, 1) 107 .putFolder(R.string.copy) 108 .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY) 109 .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY2) 110 .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY3) 111 .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY4) 112 .build() 113 ) 114 .loadModelSync() 115 116 folderIcon.mInfo = 117 modelHelper.bgDataModel.itemsIdMap.find { it.itemType == ITEM_TYPE_FOLDER } 118 as FolderInfo 119 // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps 120 folderItems = folderIcon.mInfo.getAppContents() 121 122 // Set second icon to be non-themed. 123 folderItems[1].bitmap.themedBitmap = null 124 125 // Set third icon to be themed with badge. 126 folderItems[2].bitmap = 127 folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) 128 129 // Set fourth icon to be non-themed with badge. 130 folderItems[3].bitmap = 131 folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) 132 folderItems[3].bitmap.themedBitmap = null 133 } 134 135 @After 136 @Throws(Exception::class) tearDownnull137 fun tearDown() { 138 modelHelper.destroy() 139 } 140 141 @Test 142 @MonoThemeEnabled(true) checkThemedIconWithThemingOn_iconShouldBeThemednull143 fun checkThemedIconWithThemingOn_iconShouldBeThemed() { 144 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 145 146 previewItemManager.setDrawable(drawingParams, folderItems[0]) 147 148 assert((drawingParams.drawable as FastBitmapDrawable).isThemed) 149 } 150 151 @Test 152 @MonoThemeEnabled(false) checkThemedIconWithThemingOff_iconShouldNotBeThemednull153 fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() { 154 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 155 156 previewItemManager.setDrawable(drawingParams, folderItems[0]) 157 158 assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed) 159 } 160 161 @Test 162 @MonoThemeEnabled(true) checkUnthemedIconWithThemingOn_iconShouldNotBeThemednull163 fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() { 164 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 165 166 previewItemManager.setDrawable(drawingParams, folderItems[1]) 167 168 assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed) 169 } 170 171 @Test 172 @MonoThemeEnabled(false) checkUnthemedIconWithThemingOff_iconShouldNotBeThemednull173 fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() { 174 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 175 176 previewItemManager.setDrawable(drawingParams, folderItems[1]) 177 178 assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed) 179 } 180 181 @Test 182 @MonoThemeEnabled(true) checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemednull183 fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() { 184 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 185 186 previewItemManager.setDrawable(drawingParams, folderItems[2]) 187 188 assert((drawingParams.drawable as FastBitmapDrawable).isThemed) 189 assert( 190 ((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed 191 ) 192 } 193 194 @Test 195 @MonoThemeEnabled(true) checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemednull196 fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() { 197 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 198 199 previewItemManager.setDrawable(drawingParams, folderItems[3]) 200 201 assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed) 202 assert( 203 ((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed 204 ) 205 } 206 207 @Test 208 @MonoThemeEnabled(false) checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemednull209 fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() { 210 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 211 212 previewItemManager.setDrawable(drawingParams, folderItems[3]) 213 214 assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed) 215 assert( 216 !((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed 217 ) 218 } 219 220 @Test Inactive archived app previews are not drawn as preload iconnull221 fun `Inactive archived app previews are not drawn as preload icon`() { 222 // Given 223 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 224 val archivedApp = 225 WorkspaceItemInfo().apply { 226 runtimeStatusFlags = runtimeStatusFlags or FLAG_ARCHIVED 227 runtimeStatusFlags = runtimeStatusFlags and FLAG_INSTALL_SESSION_ACTIVE.inv() 228 } 229 // When 230 previewItemManager.setDrawable(drawingParams, archivedApp) 231 // Then 232 assertThat(drawingParams.drawable).isNotInstanceOf(PreloadIconDrawable::class.java) 233 } 234 235 @Test Actively installing archived app previews are drawn as preload iconnull236 fun `Actively installing archived app previews are drawn as preload icon`() { 237 // Given 238 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 239 val archivedApp = 240 WorkspaceItemInfo().apply { 241 runtimeStatusFlags = runtimeStatusFlags or FLAG_ARCHIVED 242 runtimeStatusFlags = runtimeStatusFlags or FLAG_INSTALL_SESSION_ACTIVE 243 } 244 // When 245 TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { 246 // Run on main thread because preload drawable triggers animator 247 previewItemManager.setDrawable(drawingParams, archivedApp) 248 } 249 // Then 250 assertThat(drawingParams.drawable).isInstanceOf(PreloadIconDrawable::class.java) 251 } 252 253 @Test Preview item loads and apply high res iconnull254 fun `Preview item loads and apply high res icon`() { 255 val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) 256 val originalBitmap = folderItems[3].bitmap 257 folderItems[3].bitmap = BitmapInfo.LOW_RES_INFO 258 259 previewItemManager.setDrawable(drawingParams, folderItems[3]) 260 assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java) 261 262 val callbackCaptor = argumentCaptor<ItemInfoUpdateReceiver>() 263 verify(iconCache).updateIconInBackground(callbackCaptor.capture(), eq(folderItems[3])) 264 265 // Restore high-res icon 266 folderItems[3].bitmap = originalBitmap 267 268 // Calling with a different item info will ignore the update 269 callbackCaptor.firstValue.reapplyItemInfo(folderItems[2]) 270 assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java) 271 272 // Calling with correct value will update the drawable to high-res 273 callbackCaptor.firstValue.reapplyItemInfo(folderItems[3]) 274 assertThat(drawingParams.drawable).isNotInstanceOf(PlaceHolderIconDrawable::class.java) 275 assertThat(drawingParams.drawable).isInstanceOf(FastBitmapDrawable::class.java) 276 } 277 profileFlagOpnull278 private fun profileFlagOp(type: Int) = 279 UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP) 280 } 281 282 class ThemeStateRule : TestRule { 283 284 var themeState: Boolean? = null 285 286 override fun apply(base: Statement, description: Description): Statement { 287 themeState = description.getAnnotation(MonoThemeEnabled::class.java)?.value 288 return base 289 } 290 } 291 292 // Annotation for tests that need to be run with quickstep enabled and disabled. 293 @Retention(RUNTIME) 294 @Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER) 295 annotation class MonoThemeEnabled(val value: Boolean = false) 296 297 @LauncherAppSingleton 298 @Component(modules = [AllModulesForTest::class, FakePrefsModule::class]) 299 interface PreviewItemManagerTestComponent : LauncherAppComponent { 300 301 @Component.Builder 302 interface Builder : LauncherAppComponent.Builder { buildnull303 override fun build(): PreviewItemManagerTestComponent 304 } 305 } 306