microg/GmsCore · error · IllegalStateException

Upgrades not supported

Error message

Upgrades not supported

What it means

SafetyNetDatabase.onUpgrade() deliberately throws IllegalStateException("Upgrades not supported") because the database schema (DB_VERSION = 1) has never had a migration path. If SQLite detects an existing snet.db file with an old version number (or a corrupt version), the framework calls onUpgrade, which immediately fails instead of migrating.

Source

Thrown at play-services-safetynet/core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetDatabase.kt:128

        val sqLiteStatement = db.compileStatement("DELETE FROM $TABLE_RECENTS WHERE $FIELD_TIMESTAMP + ? < ?")
        sqLiteStatement.bindLong(1, timeout.toLong())
        sqLiteStatement.bindLong(2, System.currentTimeMillis())
        rows += sqLiteStatement.executeUpdateDelete()

        if (rows != 0) Log.d(TAG, "Cleared $rows old request(s)")
    }

    fun clearAllRequests() {
        val db = writableDatabase
        db.execSQL("DELETE FROM $TABLE_RECENTS")
    }

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(CREATE_TABLE_RECENTS)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        throw IllegalStateException("Upgrades not supported")
    }

    companion object {
        private val TAG = SafetyNetDatabase::class.java.simpleName
        private const val DB_NAME = "snet.db"
        private const val DB_VERSION = 1
        private const val CREATE_TABLE_RECENTS = "CREATE TABLE recents (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT ," +
                "request_type TEXT," +
                "package_name TEXT," +
                "nonce TEXT," +
                "timestamp INTEGER," +
                "result_status_code INTEGER DEFAULT NULL," +
                "result_status_msg TEXT DEFAULT NULL," +
                "result_data TEXT DEFAULT NULL)"
        private const val TABLE_RECENTS = "recents"
        private const val FIELD_ID = "id"
        private const val FIELD_REQUEST_TYPE = "request_type"

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Delete the old snet.db file (clear app data or remove databases/snet.db) so onCreate() recreates it fresh
  2. Catch the IllegalStateException and fall back to deleting and reopening the database
  3. Restore a matching app/microG version so the on-disk DB version matches DB_VERSION
  4. If maintaining the code, implement a real onUpgrade migration instead of throwing

Example fix

// before
try { db = helper.writableDatabase } // throws on old schema
// after
try {
    db = helper.writableDatabase
} catch (e: IllegalStateException) {
    context.deleteDatabase("snet.db")
    db = helper.writableDatabase // recreated via onCreate
}
Defensive patterns

Strategy: try-catch

Validate before calling

val v = SQLiteDatabase.openDatabase(dbPath, null, OPEN_READONLY).version; if (v < CURRENT_DB_VERSION) context.deleteDatabase("snet.db")

Try / catch

try { helper.writableDatabase } catch (e: IllegalStateException) { context.deleteDatabase("snet.db"); helper.writableDatabase }

Prevention

When it happens

Trigger: Opening the SafetyNet recents database when the on-disk snet.db has a version lower than the current DB_VERSION, or a schema/pragma mismatch causing SQLiteOpenHelper to attempt an upgrade path.

Common situations: Downgrading the app or microG after a future schema bump, a corrupt/partially-written database reporting a wrong version, or copying a database file from a different version of the app.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/e983d76454f9899f. Report an issue: GitHub.