1 /*
2  * Copyright (C) 2023 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 package com.android.launcher3.model
17 
18 import androidx.test.ext.junit.runners.AndroidJUnit4
19 import androidx.test.filters.SmallTest
20 import com.android.launcher3.LauncherAppState
21 import com.android.launcher3.model.data.WorkspaceItemInfo
22 import com.android.launcher3.util.Executors
23 import com.android.launcher3.util.LauncherLayoutBuilder
24 import com.android.launcher3.util.LauncherModelHelper
25 import com.android.launcher3.util.LauncherModelHelper.*
26 import com.android.launcher3.util.TestUtil
27 import com.google.common.truth.Truth.assertThat
28 import com.google.common.truth.Truth.assertWithMessage
29 import java.util.concurrent.CountDownLatch
30 import org.junit.After
31 import org.junit.Before
32 import org.junit.Test
33 import org.junit.runner.RunWith
34 
35 /** Tests to verify that folder icons are loaded with appropriate resolution */
36 @SmallTest
37 @RunWith(AndroidJUnit4::class)
38 class FolderIconLoadTest {
39     private lateinit var modelHelper: LauncherModelHelper
40 
41     private val uniqueActivities =
42         listOf(
43             TEST_ACTIVITY,
44             TEST_ACTIVITY2,
45             TEST_ACTIVITY3,
46             TEST_ACTIVITY4,
47             TEST_ACTIVITY5,
48             TEST_ACTIVITY6,
49             TEST_ACTIVITY7,
50             TEST_ACTIVITY8,
51             TEST_ACTIVITY9,
52             TEST_ACTIVITY10,
53             TEST_ACTIVITY11,
54             TEST_ACTIVITY12,
55             TEST_ACTIVITY13,
56             TEST_ACTIVITY14
57         )
58 
59     @Before
setUpnull60     fun setUp() {
61         modelHelper = LauncherModelHelper()
62     }
63 
64     @After
65     @Throws(Exception::class)
tearDownnull66     fun tearDown() {
67         modelHelper.destroy()
68         TestUtil.uninstallDummyApp()
69     }
70 
71     @Test
72     @Throws(Exception::class)
folderLoadedWithHighRes_2x2null73     fun folderLoadedWithHighRes_2x2() {
74         val items = setupAndLoadFolder(4)
75         assertThat(items.size).isEqualTo(4)
76         verifyHighRes(items, 0, 1, 2, 3)
77     }
78 
79     @Test
80     @Throws(Exception::class)
folderLoadedWithHighRes_3x2null81     fun folderLoadedWithHighRes_3x2() {
82         val items = setupAndLoadFolder(6)
83         assertThat(items.size).isEqualTo(6)
84         verifyHighRes(items, 0, 1, 3, 4)
85         verifyLowRes(items, 2, 5)
86     }
87 
88     @Test
89     @Throws(Exception::class)
folderLoadedWithHighRes_max_3x3null90     fun folderLoadedWithHighRes_max_3x3() {
91         val idp = LauncherAppState.getIDP(modelHelper.sandboxContext)
92         idp.numFolderColumns = intArrayOf(3, 3, 3, 3)
93         idp.numFolderRows = intArrayOf(3, 3, 3, 3)
94         recreateSupportedDeviceProfiles()
95 
96         val items = setupAndLoadFolder(14)
97         verifyHighRes(items, 0, 1, 3, 4)
98         verifyLowRes(items, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13)
99     }
100 
101     @Test
102     @Throws(Exception::class)
folderLoadedWithHighRes_max_4x4null103     fun folderLoadedWithHighRes_max_4x4() {
104         val idp = LauncherAppState.getIDP(modelHelper.sandboxContext)
105         idp.numFolderColumns = intArrayOf(4, 4, 4, 4)
106         idp.numFolderRows = intArrayOf(4, 4, 4, 4)
107         recreateSupportedDeviceProfiles()
108 
109         val items = setupAndLoadFolder(14)
110         verifyHighRes(items, 0, 1, 4, 5)
111         verifyLowRes(items, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13)
112     }
113 
114     @Test
115     @Throws(Exception::class)
folderLoadedWithHighRes_differentFolderConfigurationsnull116     fun folderLoadedWithHighRes_differentFolderConfigurations() {
117         val idp = LauncherAppState.getIDP(modelHelper.sandboxContext)
118         idp.numFolderColumns = intArrayOf(4, 3, 4, 4)
119         idp.numFolderRows = intArrayOf(4, 3, 4, 4)
120         recreateSupportedDeviceProfiles()
121 
122         val items = setupAndLoadFolder(14)
123         verifyHighRes(items, 0, 1, 3, 4, 5)
124         verifyLowRes(items, 2, 6, 7, 8, 9, 10, 11, 12, 13)
125     }
126 
127     @Throws(Exception::class)
setupAndLoadFoldernull128     private fun setupAndLoadFolder(itemCount: Int): ArrayList<WorkspaceItemInfo> {
129         val builder =
130             LauncherLayoutBuilder()
131                 .atWorkspace(0, 0, 1)
132                 .putFolder("Sample")
133                 .apply {
134                     for (i in 0..itemCount - 1) this.addApp(TEST_PACKAGE, uniqueActivities[i])
135                 }
136                 .build()
137 
138         modelHelper.setupDefaultLayoutProvider(builder)
139         modelHelper.loadModelSync()
140 
141         // The first load initializes the DB, load again so that icons are now used from the DB
142         // Wait for the icon cache to be updated and then reload
143         val app = LauncherAppState.getInstance(modelHelper.sandboxContext)
144         val cache = app.iconCache
145         while (cache.isIconUpdateInProgress) {
146             val wait = CountDownLatch(1)
147             Executors.MODEL_EXECUTOR.handler.postDelayed({ wait.countDown() }, 10)
148             wait.await()
149         }
150         TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { cache.clearMemoryCache() }
151         // Reload again with correct icon state
152         app.model.forceReload()
153         modelHelper.loadModelSync()
154         val collections = modelHelper.getBgDataModel().collections
155 
156         assertThat(collections.size()).isEqualTo(1)
157         assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount)
158         return collections.valueAt(0).getAppContents()
159     }
160 
verifyHighResnull161     private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
162         for (index in indices) {
163             assertWithMessage("Index $index was not highRes")
164                 .that(items[index].bitmap.isNullOrLowRes)
165                 .isFalse()
166         }
167     }
168 
verifyLowResnull169     private fun verifyLowRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
170         for (index in indices) {
171             assertWithMessage("Index $index was not lowRes")
172                 .that(items[index].bitmap.isNullOrLowRes)
173                 .isTrue()
174         }
175     }
176 
177     /** Recreate DeviceProfiles after changing InvariantDeviceProfile */
recreateSupportedDeviceProfilesnull178     private fun recreateSupportedDeviceProfiles() {
179         LauncherAppState.getIDP(modelHelper.sandboxContext).supportedProfiles =
180             LauncherAppState.getIDP(modelHelper.sandboxContext).supportedProfiles.map {
181                 it.copy(modelHelper.sandboxContext)
182             }
183     }
184 }
185