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