• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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