Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4f78f423 authored by Darrell Shi's avatar Darrell Shi
Browse files

Database migration to reverse ranks

This change bumps the database version and adds a migration which
reverses the ranks.

Test: atest CommunalDatabaseMigrationsTest
Test: verified on device migration is successful
Bug: 348041107
Flag: com.android.systemui.communal_hub
Change-Id: Iea88a24e2b559857e3f83b1e3c1f40ab647554eb
parent 1ecf6e2d
Loading
Loading
Loading
Loading
+81 −0
Original line number Diff line number Diff line
{
  "formatVersion": 1,
  "database": {
    "version": 3,
    "identityHash": "02e2da2d36e6955200edd5fb49e63c72",
    "entities": [
      {
        "tableName": "communal_widget_table",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1)",
        "fields": [
          {
            "fieldPath": "uid",
            "columnName": "uid",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "widgetId",
            "columnName": "widget_id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "componentName",
            "columnName": "component_name",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "itemId",
            "columnName": "item_id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "userSerialNumber",
            "columnName": "user_serial_number",
            "affinity": "INTEGER",
            "notNull": true,
            "defaultValue": "-1"
          }
        ],
        "primaryKey": {
          "autoGenerate": true,
          "columnNames": [
            "uid"
          ]
        }
      },
      {
        "tableName": "communal_item_rank_table",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
        "fields": [
          {
            "fieldPath": "uid",
            "columnName": "uid",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "rank",
            "columnName": "rank",
            "affinity": "INTEGER",
            "notNull": true,
            "defaultValue": "0"
          }
        ],
        "primaryKey": {
          "autoGenerate": true,
          "columnNames": [
            "uid"
          ]
        }
      }
    ],
    "setupQueries": [
      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02e2da2d36e6955200edd5fb49e63c72')"
    ]
  }
}
 No newline at end of file
+18 −2
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.res.R

@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 2)
@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 3)
abstract class CommunalDatabase : RoomDatabase() {
    abstract fun communalWidgetDao(): CommunalWidgetDao

@@ -55,7 +55,7 @@ abstract class CommunalDatabase : RoomDatabase() {
                            context.resources.getString(R.string.config_communalDatabase)
                        )
                        .also { builder ->
                            builder.addMigrations(MIGRATION_1_2)
                            builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                            builder.fallbackToDestructiveMigration(dropAllTables = true)
                            callback?.let { callback -> builder.addCallback(callback) }
                        }
@@ -87,5 +87,21 @@ abstract class CommunalDatabase : RoomDatabase() {
                    )
                }
            }

        /**
         * This migration reverses the ranks. For example, if the ranks are 2, 1, 0, then after the
         * migration they will be 0, 1, 2.
         */
        @VisibleForTesting
        val MIGRATION_2_3 =
            object : Migration(2, 3) {
                override fun migrate(db: SupportSQLiteDatabase) {
                    Log.i(TAG, "Migrating from version 2 to 3")
                    db.execSQL(
                        "UPDATE communal_item_rank_table " +
                            "SET rank = (SELECT MAX(rank) FROM communal_item_rank_table) - rank"
                    )
                }
            }
    }
}
+99 −0
Original line number Diff line number Diff line
@@ -72,6 +72,82 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
        databaseV2.verifyWidgetsV2(fakeWidgetsV1.map { it.getV2() })
    }

    @Test
    fun migrate2To3_noGapBetweenRanks_ranksReversed() {
        // Create a communal database in version 2
        val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)

        // Populate some fake data
        val fakeRanks =
            listOf(
                FakeCommunalItemRank(3),
                FakeCommunalItemRank(2),
                FakeCommunalItemRank(1),
                FakeCommunalItemRank(0),
            )
        databaseV2.insertRanks(fakeRanks)

        // Verify fake ranks populated
        databaseV2.verifyRanksInOrder(fakeRanks)

        // Run migration and get database V3
        val databaseV3 =
            migrationTestHelper.runMigrationsAndValidate(
                name = DATABASE_NAME,
                version = 3,
                validateDroppedTables = false,
                CommunalDatabase.MIGRATION_2_3,
            )

        // Verify ranks are reversed
        databaseV3.verifyRanksInOrder(
            listOf(
                FakeCommunalItemRank(0),
                FakeCommunalItemRank(1),
                FakeCommunalItemRank(2),
                FakeCommunalItemRank(3),
            )
        )
    }

    @Test
    fun migrate2To3_withGapBetweenRanks_ranksReversed() {
        // Create a communal database in version 2
        val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)

        // Populate some fake data with gaps between ranks
        val fakeRanks =
            listOf(
                FakeCommunalItemRank(9),
                FakeCommunalItemRank(7),
                FakeCommunalItemRank(2),
                FakeCommunalItemRank(0),
            )
        databaseV2.insertRanks(fakeRanks)

        // Verify fake ranks populated
        databaseV2.verifyRanksInOrder(fakeRanks)

        // Run migration and get database V3
        val databaseV3 =
            migrationTestHelper.runMigrationsAndValidate(
                name = DATABASE_NAME,
                version = 3,
                validateDroppedTables = false,
                CommunalDatabase.MIGRATION_2_3,
            )

        // Verify ranks are reversed
        databaseV3.verifyRanksInOrder(
            listOf(
                FakeCommunalItemRank(0),
                FakeCommunalItemRank(2),
                FakeCommunalItemRank(7),
                FakeCommunalItemRank(9),
            )
        )
    }

    private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
        widgets.forEach { widget ->
            execSQL(
@@ -117,6 +193,25 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
        assertThat(cursor.isAfterLast).isTrue()
    }

    private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
        ranks.forEach { rank ->
            execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
        }
    }

    private fun SupportSQLiteDatabase.verifyRanksInOrder(ranks: List<FakeCommunalItemRank>) {
        val cursor = query("SELECT * FROM communal_item_rank_table ORDER BY uid")
        assertThat(cursor.moveToFirst()).isTrue()

        ranks.forEach { rank ->
            assertThat(cursor.getInt(cursor.getColumnIndex("rank"))).isEqualTo(rank.rank)
            cursor.moveToNext()
        }

        // Verify there is no more columns
        assertThat(cursor.isAfterLast).isTrue()
    }

    /**
     * Returns the expected data after migration from V1 to V2, which is simply that the new user
     * serial number field is now set to [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED].
@@ -143,6 +238,10 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
        val userSerialNumber: Int,
    )

    private data class FakeCommunalItemRank(
        val rank: Int,
    )

    companion object {
        private const val DATABASE_NAME = "communal_db"
    }