1 /*
<lambda>null2  * 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 
17 package com.android.launcher3.model.gridmigration
18 
19 import android.content.Context
20 import android.database.sqlite.SQLiteDatabase
21 import android.graphics.Point
22 import android.os.Process
23 import android.util.Log
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.SmallTest
26 import androidx.test.platform.app.InstrumentationRegistry
27 import com.android.launcher3.InvariantDeviceProfile
28 import com.android.launcher3.LauncherSettings.Favorites
29 import com.android.launcher3.celllayout.testgenerator.ValidGridMigrationTestCaseGenerator
30 import com.android.launcher3.celllayout.testgenerator.generateItemsForTest
31 import com.android.launcher3.model.DatabaseHelper
32 import com.android.launcher3.model.DeviceGridState
33 import com.android.launcher3.model.GridSizeMigrationUtil
34 import com.android.launcher3.pm.UserCache
35 import com.android.launcher3.provider.LauncherDbUtils
36 import com.android.launcher3.util.rule.TestStabilityRule
37 import com.android.launcher3.util.rule.TestStabilityRule.Stability
38 import java.util.Random
39 import org.junit.Before
40 import org.junit.Test
41 import org.junit.runner.RunWith
42 
43 private data class Grid(val tableName: String, val size: Point, val items: List<WorkspaceItem>) {
44     fun toGridState(): DeviceGridState =
45         DeviceGridState(size.x, size.y, size.x, InvariantDeviceProfile.TYPE_PHONE, tableName)
46 }
47 
48 @SmallTest
49 @RunWith(AndroidJUnit4::class)
50 class ValidGridMigrationUnitTest {
51 
52     companion object {
53         const val SEED = 1044542
54         val REPEAT_AFTER = Point(0, 10)
55         val REPEAT_AFTER_DST = Point(6, 15)
56         const val TAG = "ValidGridMigrationUnitTest"
57         const val SMALL_TEST_SIZE = 60
58         const val LARGE_TEST_SIZE = 1000
59     }
60 
61     private lateinit var context: Context
62 
63     @Before
setUpnull64     fun setUp() {
65         context = InstrumentationRegistry.getInstrumentation().targetContext
66     }
67 
validatenull68     private fun validate(srcGrid: Grid, dstGrid: Grid, resultItems: List<WorkspaceItem>) {
69         // This returns a map with the number of repeated elements
70         // ex { calculatorIcon : 6, weatherWidget : 2 }
71         val itemsToMap = { it: List<WorkspaceItem> ->
72             it.filter { it.container != Favorites.CONTAINER_HOTSEAT }
73                 .groupingBy {
74                     when (it.type) {
75                         Favorites.ITEM_TYPE_FOLDER,
76                         Favorites.ITEM_TYPE_APP_PAIR -> throw Exception("Not implemented")
77                         Favorites.ITEM_TYPE_APPWIDGET -> it.appWidgetProvider
78                         Favorites.ITEM_TYPE_APPLICATION -> it.intent
79                         else -> it.title
80                     }
81                 }
82                 .eachCount()
83         }
84         resultItems.forEach {
85             assert((it.x in 0..dstGrid.size.x) && (it.y in 0..dstGrid.size.y)) {
86                 "Item outside of the board size. Size = ${dstGrid.size} Item = $it"
87             }
88             assert(
89                 (it.x + it.spanX in 0..dstGrid.size.x) && (it.y + it.spanY in 0..dstGrid.size.y)
90             ) {
91                 "Item doesn't fit in the grid. Size = ${dstGrid.size} Item = $it"
92             }
93         }
94 
95         val srcCountMap = itemsToMap(srcGrid.items)
96         val resultCountMap = itemsToMap(resultItems)
97         val diff = resultCountMap - srcCountMap
98 
99         diff.forEach { (k, count) ->
100             assert(count >= 0) { "Source item $k not present on the result" }
101         }
102     }
103 
addItemsToDbnull104     private fun addItemsToDb(db: SQLiteDatabase, grid: Grid) {
105         LauncherDbUtils.SQLiteTransaction(db).use { transaction ->
106             grid.items.forEach { insertIntoDb(grid.tableName, it, transaction.db) }
107             transaction.commit()
108         }
109     }
110 
migratenull111     private fun migrate(
112         srcGrid: Grid,
113         dstGrid: Grid,
114     ): List<WorkspaceItem> {
115         val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
116         val dbHelper =
117             DatabaseHelper(
118                 context,
119                 null,
120                 { UserCache.INSTANCE.get(context).getSerialNumberForUser(it) },
121                 {}
122             )
123 
124         Favorites.addTableToDb(dbHelper.writableDatabase, userSerial, false, srcGrid.tableName)
125 
126         addItemsToDb(dbHelper.writableDatabase, srcGrid)
127         addItemsToDb(dbHelper.writableDatabase, dstGrid)
128 
129         LauncherDbUtils.SQLiteTransaction(dbHelper.writableDatabase).use {
130             GridSizeMigrationUtil.migrate(
131                 dbHelper,
132                 GridSizeMigrationUtil.DbReader(it.db, srcGrid.tableName, context, MockSet(1)),
133                 GridSizeMigrationUtil.DbReader(it.db, dstGrid.tableName, context, MockSet(1)),
134                 dstGrid.size.x,
135                 dstGrid.size,
136                 srcGrid.toGridState(),
137                 dstGrid.toGridState()
138             )
139             it.commit()
140         }
141         return readDb(dstGrid.tableName, dbHelper.readableDatabase)
142     }
143 
144     @Test
runTestCasenull145     fun runTestCase() {
146         val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong()))
147         for (i in 0..SMALL_TEST_SIZE) {
148             val testCase = caseGenerator.generateTestCase(isDestEmpty = true)
149             Log.d(TAG, "Test case = $testCase")
150             val srcGrid =
151                 Grid(
152                     tableName = Favorites.TMP_TABLE,
153                     size = testCase.srcSize,
154                     items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
155                 )
156             val dstGrid =
157                 Grid(tableName = Favorites.TABLE_NAME, size = testCase.targetSize, items = listOf())
158             validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid))
159         }
160     }
161 
162     @Test
mergeBoardsnull163     fun mergeBoards() {
164         val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong()))
165         for (i in 0..SMALL_TEST_SIZE) {
166             val testCase = caseGenerator.generateTestCase(isDestEmpty = false)
167             Log.d(TAG, "Test case = $testCase")
168             val srcGrid =
169                 Grid(
170                     tableName = Favorites.TMP_TABLE,
171                     size = testCase.srcSize,
172                     items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
173                 )
174             val dstGrid =
175                 Grid(
176                     tableName = Favorites.TABLE_NAME,
177                     size = testCase.targetSize,
178                     items = generateItemsForTest(testCase.destBoards, REPEAT_AFTER_DST)
179                 )
180             validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid))
181         }
182     }
183 
184     // This test takes about 4 minutes, there is no need to run it in presubmit.
185     @Stability(flavors = TestStabilityRule.LOCAL or TestStabilityRule.PLATFORM_POSTSUBMIT)
186     @Test
runExtensiveTestCasesnull187     fun runExtensiveTestCases() {
188         val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong()))
189         for (i in 0..LARGE_TEST_SIZE) {
190             val testCase = caseGenerator.generateTestCase(isDestEmpty = true)
191             Log.d(TAG, "Test case = $testCase")
192             val srcGrid =
193                 Grid(
194                     tableName = Favorites.TMP_TABLE,
195                     size = testCase.srcSize,
196                     items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
197                 )
198             val dstGrid =
199                 Grid(tableName = Favorites.TABLE_NAME, size = testCase.targetSize, items = listOf())
200             validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid))
201         }
202     }
203 }
204