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

Commit 6219c19b authored by Bryce Lee's avatar Bryce Lee Committed by Android (Google) Code Review
Browse files

Merge "Prevent widget entries with duplicate widget_ids." into main

parents 3898892c 2694993b
Loading
Loading
Loading
Loading
+106 −0
Original line number Diff line number Diff line
{
  "formatVersion": 1,
  "database": {
    "version": 6,
    "identityHash": "912adf66ee487e9e4afba007cc8073c7",
    "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, `span_y` INTEGER NOT NULL DEFAULT 3, `span_y_new` 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"
          },
          {
            "fieldPath": "spanY",
            "columnName": "span_y",
            "affinity": "INTEGER",
            "notNull": true,
            "defaultValue": "3"
          },
          {
            "fieldPath": "spanYNew",
            "columnName": "span_y_new",
            "affinity": "INTEGER",
            "notNull": true,
            "defaultValue": "1"
          }
        ],
        "primaryKey": {
          "autoGenerate": true,
          "columnNames": [
            "uid"
          ]
        },
        "indices": [
          {
            "name": "widget_id_index",
            "unique": true,
            "columnNames": [
              "widget_id"
            ],
            "orders": [],
            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `widget_id_index` ON `${TABLE_NAME}` (`widget_id`)"
          }
        ]
      },
      {
        "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, '912adf66ee487e9e4afba007cc8073c7')"
    ]
  }
}
 No newline at end of file
+24 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ import com.android.systemui.communal.shared.model.SpanValue
import com.android.systemui.communal.shared.model.toResponsive
import com.android.systemui.res.R

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

@@ -66,6 +66,7 @@ abstract class CommunalDatabase : RoomDatabase() {
                                MIGRATION_2_3,
                                MIGRATION_3_4,
                                MIGRATION_4_5,
                                MIGRATION_5_6,
                            )
                            builder.fallbackToDestructiveMigration(dropAllTables = true)
                            callback?.let { callback -> builder.addCallback(callback) }
@@ -155,5 +156,27 @@ abstract class CommunalDatabase : RoomDatabase() {
                    }
                }
            }

        /** Eliminates duplicate entries with same widget id and enforces a uniqueness index */
        @VisibleForTesting
        val MIGRATION_5_6 =
            object : Migration(5, 6) {
                override fun migrate(db: SupportSQLiteDatabase) {
                    Log.i(TAG, "Migrating from version 5 to 6")
                    db.query(
                            "SELECT item_id, widget_id FROM communal_widget_table WHERE widget_id IN (SELECT widget_id FROM communal_widget_table GROUP BY widget_id HAVING COUNT(widget_id) > 1)"
                        )
                        .use { cursor ->
                            while (cursor.moveToNext()) {
                                val id = cursor.getInt(cursor.getColumnIndex("item_id"))
                                db.execSQL("DELETE FROM communal_widget_table WHERE item_id = $id")
                                db.execSQL("DELETE FROM communal_item_rank_table WHERE uid = $id")
                            }
                        }
                    db.execSQL(
                        "CREATE UNIQUE INDEX IF NOT EXISTS `widget_id_index` ON `communal_widget_table` (`widget_id`)"
                    )
                }
            }
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -18,9 +18,13 @@ package com.android.systemui.communal.data.db

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey

@Entity(tableName = "communal_widget_table")
@Entity(
    tableName = "communal_widget_table",
    indices = [Index(value = ["widget_id"], unique = true, name = "widget_id_index")],
)
data class CommunalWidgetItem(
    @PrimaryKey(autoGenerate = true) val uid: Long,
    /** Id of an app widget */
+15 −0
Original line number Diff line number Diff line
@@ -171,6 +171,9 @@ interface CommunalWidgetDao {
        spanYNew: Int,
    ): Long

    @Query("SELECT COUNT(widget_id) FROM communal_widget_table WHERE widget_id = :widgetId")
    fun getWidgetCount(widgetId: Int): Long

    @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
    fun insertItemRank(rank: Int): Long

@@ -229,6 +232,10 @@ interface CommunalWidgetDao {
        userSerialNumber: Int,
        spanY: SpanValue,
    ): Long {
        // Remove any existing entry with the same widget id  to prevent issue where multiple
        // entries are linked to the same widget. This should not normally happen if all widgets
        // are requested from the same provider.
        removeAllWidgetsById(widgetId)
        val widgets = getWidgetsNow()

        // If rank is not specified (null or less than 0), rank it last by finding the current
@@ -253,6 +260,14 @@ interface CommunalWidgetDao {
        )
    }

    @Transaction
    fun removeAllWidgetsById(widgetId: Int) {
        var continueRemoving = false
        do {
            continueRemoving = deleteWidgetById(widgetId)
        } while (continueRemoving)
    }

    @Transaction
    fun deleteWidgetById(widgetId: Int): Boolean {
        val widget =
+122 −0
Original line number Diff line number Diff line
@@ -218,6 +218,65 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
        databaseV5.verifyWidgetsV5(fakeWidgetsV4.map { it.getV5() })
    }

    @Test
    fun migrate5To6_removesDuplicates() {
        val databaseV5 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 5)

        val uniqueWidget =
            FakeCommunalWidgetItemV5(
                widgetId = 3,
                componentName = "test_widget_3",
                itemId = 13,
                userSerialNumber = 0,
                spanY = 0,
                spanYNew = 0,
            )

        val fakeWidgetsV5 =
            listOf(
                FakeCommunalWidgetItemV5(
                    widgetId = 1,
                    componentName = "test_widget_1",
                    itemId = 10,
                    userSerialNumber = 0,
                    spanY = 3,
                    spanYNew = 3,
                ),
                FakeCommunalWidgetItemV5(
                    widgetId = 1,
                    componentName = "test_widget_2",
                    itemId = 11,
                    userSerialNumber = 10,
                    spanY = 6,
                    spanYNew = 6,
                ),
                FakeCommunalWidgetItemV5(
                    widgetId = 1,
                    componentName = "test_widget_2",
                    itemId = 12,
                    userSerialNumber = 10,
                    spanY = 6,
                    spanYNew = 6,
                ),
                uniqueWidget,
            )

        val correctedWidgetsV6 = listOf(uniqueWidget.getV6())

        databaseV5.insertWidgetsV5(fakeWidgetsV5)
        databaseV5.verifyWidgetsV5(fakeWidgetsV5)

        val databaseV6 =
            migrationTestHelper.runMigrationsAndValidate(
                name = DATABASE_NAME,
                version = 6,
                validateDroppedTables = false,
                CommunalDatabase.MIGRATION_5_6,
            )

        databaseV6.verifyWidgetsV6(correctedWidgetsV6)
    }

    private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
        widgets.forEach { widget ->
            execSQL(
@@ -261,6 +320,26 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
        }
    }

    private fun SupportSQLiteDatabase.insertWidgetsV5(widgets: List<FakeCommunalWidgetItemV5>) {
        widgets.forEach { widget ->
            execSQL(
                "INSERT INTO communal_widget_table(" +
                    "widget_id, " +
                    "component_name, " +
                    "item_id, " +
                    "user_serial_number, " +
                    "span_y, " +
                    "span_y_new) " +
                    "VALUES(${widget.widgetId}, " +
                    "'${widget.componentName}', " +
                    "${widget.itemId}, " +
                    "${widget.userSerialNumber}," +
                    "${widget.spanY}," +
                    "${widget.spanYNew})"
            )
        }
    }

    private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
        val cursor = query("SELECT * FROM communal_widget_table")
        assertThat(cursor.moveToFirst()).isTrue()
@@ -354,6 +433,29 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
        assertThat(cursor.isAfterLast).isTrue()
    }

    private fun SupportSQLiteDatabase.verifyWidgetsV6(widgets: List<FakeCommunalWidgetItemV6>) {
        val cursor = query("SELECT * FROM communal_widget_table")

        assertThat(cursor.count).isEqualTo(widgets.size)
        assertThat(cursor.moveToFirst()).isTrue()

        widgets.forEach { widget ->
            assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
            assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
                .isEqualTo(widget.componentName)
            assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
            assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
                .isEqualTo(widget.userSerialNumber)
            assertThat(cursor.getInt(cursor.getColumnIndex("span_y"))).isEqualTo(widget.spanY)
            assertThat(cursor.getInt(cursor.getColumnIndex("span_y_new")))
                .isEqualTo(widget.spanYNew)

            cursor.moveToNext()
        }

        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})")
@@ -439,6 +541,26 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
        val spanYNew: Int,
    )

    private fun FakeCommunalWidgetItemV5.getV6(): FakeCommunalWidgetItemV6 {
        return FakeCommunalWidgetItemV6(
            widgetId = widgetId,
            componentName = componentName,
            itemId = itemId,
            userSerialNumber = userSerialNumber,
            spanY = spanY,
            spanYNew = spanYNew,
        )
    }

    private data class FakeCommunalWidgetItemV6(
        val widgetId: Int,
        val componentName: String,
        val itemId: Int,
        val userSerialNumber: Int,
        val spanY: Int,
        val spanYNew: Int,
    )

    private data class FakeCommunalItemRank(val rank: Int)

    companion object {
Loading