1 /* <lambda>null2 * Copyright (C) 2024 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 18 19 import android.platform.test.flag.junit.SetFlagsRule 20 import androidx.test.ext.junit.runners.AndroidJUnit4 21 import androidx.test.filters.SmallTest 22 import androidx.test.platform.app.InstrumentationRegistry 23 import com.android.launcher3.Flags 24 import com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE 25 import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME 26 import com.android.launcher3.celllayout.board.CellLayoutBoard 27 import com.android.launcher3.pm.UserCache 28 import com.android.launcher3.util.rule.TestToPhoneFileCopier 29 import com.android.launcher3.util.rule.setFlags 30 import org.junit.Before 31 import org.junit.Rule 32 import org.junit.Test 33 import org.junit.runner.RunWith 34 35 private val phoneContext = InstrumentationRegistry.getInstrumentation().targetContext 36 37 data class EntryData(val x: Int, val y: Int, val spanX: Int, val spanY: Int, val rank: Int) 38 39 /** 40 * Holds the data needed to run a test in GridMigrationTest, usually we would have a src 41 * GridMigrationData and a dst GridMigrationData meaning the data after a migration has occurred. 42 * This class holds a gridState, which is the size of the grid like 5x5 (among other things). a 43 * dbHelper which contains the readable database and writable database used to migrate the 44 * databases. 45 * 46 * You can also get all the entries defined in the dbHelper database. 47 */ 48 class GridMigrationData(dbFileName: String?, val gridState: DeviceGridState) { 49 50 val dbHelper: DatabaseHelper = 51 DatabaseHelper( 52 phoneContext, 53 dbFileName, 54 { UserCache.INSTANCE.get(phoneContext).getSerialNumberForUser(it) }, 55 {} 56 ) 57 58 fun readEntries(): List<GridSizeMigrationUtil.DbEntry> = 59 GridSizeMigrationUtil.readAllEntries(dbHelper.readableDatabase, TABLE_NAME, phoneContext) 60 } 61 62 /** 63 * Test the migration of a database from one size to another. It reads a database from the test 64 * assets, uploads it into the phone and migrates the database to a database in memory which is 65 * later compared against a database in the test assets to make sure they are identical. 66 */ 67 @SmallTest 68 @RunWith(AndroidJUnit4::class) 69 class GridMigrationTest { 70 private val DB_FILE = "test_launcher.db" 71 72 @JvmField 73 @Rule 74 val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) 75 76 // Copying the src db for all tests. 77 @JvmField 78 @Rule 79 val fileCopier = 80 TestToPhoneFileCopier( 81 src = "databases/GridMigrationTest/$DB_FILE", 82 dest = "databases/$DB_FILE", 83 removeOnFinish = true 84 ) 85 86 @Before setupnull87 fun setup() { 88 setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX) 89 } 90 migratenull91 private fun migrate(src: GridMigrationData, dst: GridMigrationData) { 92 GridSizeMigrationUtil.migrateGridIfNeeded( 93 phoneContext, 94 src.gridState, 95 dst.gridState, 96 dst.dbHelper, 97 src.dbHelper.readableDatabase 98 ) 99 } 100 101 /** 102 * Makes sure that none of the items overlaps on the result, i.e. no widget or icons share the 103 * same space in the db. 104 */ validateDbnull105 private fun validateDb(data: GridMigrationData) { 106 // The array size is just a big enough number to fit all the number of workspaces 107 val boards = Array(100) { CellLayoutBoard(data.gridState.columns, data.gridState.rows) } 108 data.readEntries().forEach { 109 val cellLayoutBoard = boards[it.screenId] 110 assert(cellLayoutBoard.isEmpty(it.cellX, it.cellY, it.spanX, it.spanY)) { 111 "Db has overlapping items" 112 } 113 cellLayoutBoard.addWidget(it.cellX, it.cellY, it.spanX, it.spanY) 114 } 115 } 116 comparenull117 private fun compare(dst: GridMigrationData, target: GridMigrationData) { 118 val sort = compareBy<GridSizeMigrationUtil.DbEntry>({ it.cellX }, { it.cellY }) 119 val mapF = { it: GridSizeMigrationUtil.DbEntry -> 120 EntryData(it.cellX, it.cellY, it.spanX, it.spanY, it.rank) 121 } 122 val entriesDst = dst.readEntries().sortedWith(sort).map(mapF) 123 val entriesTarget = target.readEntries().sortedWith(sort).map(mapF) 124 125 assert(entriesDst == entriesTarget) { 126 "The elements on the dst database is not the same as in the target" 127 } 128 } 129 130 /** 131 * Migrate src into dst and compare to target. This method validates 3 things: 132 * 1. dst has the same number of items as src after the migration, meaning, none of the items 133 * were removed during the migration. 134 * 2. dst is valid, meaning that none of the items overlap with each other. 135 * 3. dst is equal to target to ensure we don't unintentionally change the migration logic. 136 */ runTestnull137 private fun runTest(src: GridMigrationData, dst: GridMigrationData, target: GridMigrationData) { 138 migrate(src, dst) 139 assert(src.readEntries().size == dst.readEntries().size) { 140 "Source db and destination db do not contain the same number of elements" 141 } 142 validateDb(dst) 143 compare(dst, target) 144 } 145 146 @JvmField 147 @Rule 148 val result5x5to3x3 = 149 TestToPhoneFileCopier( 150 src = "databases/GridMigrationTest/result5x5to3x3.db", 151 dest = "databases/result5x5to3x3.db", 152 removeOnFinish = true 153 ) 154 155 @Test 5x5 to 3x3null156 fun `5x5 to 3x3`() = 157 runTest( 158 src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)), 159 dst = 160 GridMigrationData( 161 null, // in memory db, to download a new db change null for the filename of the 162 // db name to store it. Do not use existing names. 163 DeviceGridState(3, 3, 3, TYPE_PHONE, "") 164 ), 165 target = 166 GridMigrationData("result5x5to3x3.db", DeviceGridState(3, 3, 3, TYPE_PHONE, "")) 167 ) 168 169 @JvmField 170 @Rule 171 val result5x5to4x7 = 172 TestToPhoneFileCopier( 173 src = "databases/GridMigrationTest/result5x5to4x7.db", 174 dest = "databases/result5x5to4x7.db", 175 removeOnFinish = true 176 ) 177 178 @Test 179 fun `5x5 to 4x7`() = 180 runTest( 181 src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)), 182 dst = 183 GridMigrationData( 184 null, // in memory db, to download a new db change null for the filename of the 185 // db name to store it. Do not use existing names. 186 DeviceGridState(4, 7, 4, TYPE_PHONE, "") 187 ), 188 target = 189 GridMigrationData("result5x5to4x7.db", DeviceGridState(4, 7, 4, TYPE_PHONE, "")) 190 ) 191 192 @JvmField 193 @Rule 194 val result5x5to5x8 = 195 TestToPhoneFileCopier( 196 src = "databases/GridMigrationTest/result5x5to5x8.db", 197 dest = "databases/result5x5to5x8.db", 198 removeOnFinish = true 199 ) 200 201 @Test 202 fun `5x5 to 5x8`() = 203 runTest( 204 src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)), 205 dst = 206 GridMigrationData( 207 null, // in memory db, to download a new db change null for the filename of the 208 // db name to store it. Do not use existing names. 209 DeviceGridState(5, 8, 5, TYPE_PHONE, "") 210 ), 211 target = 212 GridMigrationData("result5x5to5x8.db", DeviceGridState(5, 8, 5, TYPE_PHONE, "")) 213 ) 214 215 @JvmField 216 @Rule 217 val flaggedResult5x5to5x8 = 218 TestToPhoneFileCopier( 219 src = "databases/GridMigrationTest/flagged_result5x5to5x8.db", 220 dest = "databases/flagged_result5x5to5x8.db", 221 removeOnFinish = true 222 ) 223 224 @Test 225 fun `flagged 5x5 to 5x8`() { 226 setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX) 227 runTest( 228 src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)), 229 dst = 230 GridMigrationData( 231 null, // in memory db, to download a new db change null for the filename of the 232 // db name to store it. Do not use existing names. 233 DeviceGridState(5, 8, 5, TYPE_PHONE, "") 234 ), 235 target = 236 GridMigrationData( 237 "flagged_result5x5to5x8.db", 238 DeviceGridState(5, 8, 5, TYPE_PHONE, "") 239 ) 240 ) 241 } 242 } 243