[go: nahoru, domu]

Supporting dependency injection in auto migrations.

Bug: 185235075
Test: Added test to use .addAutoMigrationSpec(...) in AutoMigrationTest.kt
Relnote: Updating the API to support dependency injection in auto migrations.
Change-Id: I100c8796ce18ebc1dddb3e46a00ef56d4f8f63f5
diff --git a/room/common/api/current.txt b/room/common/api/current.txt
index 63d6692..a7a1d84 100644
--- a/room/common/api/current.txt
+++ b/room/common/api/current.txt
@@ -166,6 +166,9 @@
     method public abstract boolean autoGenerate() default false;
   }
 
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+  }
+
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
   }
 
diff --git a/room/common/api/public_plus_experimental_current.txt b/room/common/api/public_plus_experimental_current.txt
index 63d6692..a7a1d84 100644
--- a/room/common/api/public_plus_experimental_current.txt
+++ b/room/common/api/public_plus_experimental_current.txt
@@ -166,6 +166,9 @@
     method public abstract boolean autoGenerate() default false;
   }
 
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+  }
+
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
   }
 
diff --git a/room/common/api/restricted_current.txt b/room/common/api/restricted_current.txt
index 30a6f37..220d387 100644
--- a/room/common/api/restricted_current.txt
+++ b/room/common/api/restricted_current.txt
@@ -166,6 +166,9 @@
     method public abstract boolean autoGenerate() default false;
   }
 
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+  }
+
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
   }
 
diff --git a/room/common/src/main/java/androidx/room/ProvidedAutoMigrationSpec.java b/room/common/src/main/java/androidx/room/ProvidedAutoMigrationSpec.java
new file mode 100644
index 0000000..ee63b76
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/ProvidedAutoMigrationSpec.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class as an auto migration spec that will be provided to Room at runtime.
+ * <p>
+ * An instance of a class annotated with this annotation has to be provided to Room using
+ * {@code Room.databaseBuilder.addAutoMigrationSpec(AutoMigrationSpec)}. Room will verify that
+ * the spec is provided in the builder configuration and if not, an
+ * {@link IllegalArgumentException} will be thrown.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface ProvidedAutoMigrationSpec {
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
index 9d2bdc7..98837fc 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
@@ -18,6 +18,7 @@
 
 import androidx.room.DeleteColumn
 import androidx.room.DeleteTable
+import androidx.room.ProvidedAutoMigrationSpec
 import androidx.room.RenameColumn
 import androidx.room.RenameTable
 import androidx.room.compiler.processing.XType
@@ -50,19 +51,25 @@
      * @return the AutoMigrationResult containing the schema changes detected
      */
     fun process(): AutoMigrationResult? {
+        val isSpecProvided = spec.typeElement?.hasAnnotation(
+            ProvidedAutoMigrationSpec::class
+        ) ?: false
         val specElement = if (!spec.isTypeOf(Any::class)) {
             val typeElement = spec.typeElement
+
             if (typeElement == null || typeElement.isInterface() || typeElement.isAbstract()) {
                 context.logger.e(element, AUTOMIGRATION_SPEC_MUST_BE_CLASS)
                 return null
             }
 
-            val constructors = element.getConstructors()
-            context.checker.check(
-                constructors.isEmpty() || constructors.any { it.parameters.isEmpty() },
-                element,
-                ProcessorErrors.AUTOMIGRATION_SPEC_MISSING_NOARG_CONSTRUCTOR
-            )
+            if (!isSpecProvided) {
+                val constructors = element.getConstructors()
+                context.checker.check(
+                    constructors.isEmpty() || constructors.any { it.parameters.isEmpty() },
+                    element,
+                    ProcessorErrors.AUTOMIGRATION_SPEC_MISSING_NOARG_CONSTRUCTOR
+                )
+            }
 
             context.checker.check(
                 typeElement.enclosingTypeElement == null || typeElement.isStatic(),
@@ -163,11 +170,12 @@
             from = fromSchemaBundle.version,
             to = toSchemaBundle.version,
             schemaDiff = schemaDiff,
-            specElement = specElement
+            specElement = specElement,
+            isSpecProvided = isSpecProvided
         )
     }
 
-    // TODO: (b/180389433) Verify automigration schemas before calling the AutoMigrationProcessor
+    // TODO: (b/180389433) Verify auto migration schemas before calling the AutoMigrationProcessor
     private fun getValidatedSchemaFile(version: Int): File? {
         val schemaFile = File(
             context.schemaOutFolder,
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt b/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
index fc2a5f7..8166475 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
@@ -30,12 +30,13 @@
     val from: Int?,
     val to: Int?,
     val specElement: XTypeElement?,
-    val schemaDiff: SchemaDiffResult
+    val schemaDiff: SchemaDiffResult,
+    val isSpecProvided: Boolean
 ) {
     val implTypeName: ClassName by lazy {
         ClassName.get(
             element.className.packageName(),
-            "AutoMigration_${from}_${to}_Impl"
+            "${element.className.simpleName()}_AutoMigration_${from}_${to}_Impl"
         )
     }
 
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
index d62073e..0f9efb9 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
@@ -54,16 +54,18 @@
             superclass(RoomTypeNames.MIGRATION)
 
             if (autoMigrationResult.specClassName != null) {
-                builder.addField(
+                val callbackField =
                     FieldSpec.builder(
-                        autoMigrationResult.specClassName,
+                        RoomTypeNames.AUTO_MIGRATION_SPEC,
                         "callback",
                         Modifier.PRIVATE,
                         Modifier.FINAL
-                    ).initializer(
-                        "new $T()", autoMigrationResult.specClassName
-                    ).build()
-                )
+                    ).apply {
+                        if (!autoMigrationResult.isSpecProvided) {
+                            initializer("new $T()", autoMigrationResult.specClassName)
+                        }
+                    }
+                builder.addField(callbackField.build())
             }
             addMethod(createConstructor())
             addMethod(createMigrateMethod())
@@ -80,6 +82,15 @@
         return MethodSpec.constructorBuilder().apply {
             addModifiers(Modifier.PUBLIC)
             addStatement("super($L, $L)", autoMigrationResult.from, autoMigrationResult.to)
+            if (autoMigrationResult.isSpecProvided) {
+                addParameter(
+                    ParameterSpec.builder(
+                        RoomTypeNames.AUTO_MIGRATION_SPEC,
+                        "callback"
+                    ).addAnnotation(NonNull::class.java).build()
+                )
+                addStatement("this.callback = callback")
+            }
         }.build()
     }
 
@@ -111,6 +122,8 @@
      * @param migrateBuilder Builder for the migrate() function to be generated
      */
     private fun addAutoMigrationResultToMigrate(migrateBuilder: MethodSpec.Builder) {
+        // TODO: (b/185934598) Handle views here, the order in which the views themselves are
+        // recreated is important
         addSimpleChangeStatements(migrateBuilder)
         addComplexChangeStatements(migrateBuilder)
     }
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
index a8ac300..1f75228 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
@@ -64,6 +64,7 @@
             addMethod(createCreateInvalidationTracker())
             addMethod(createClearAllTables())
             addMethod(createCreateTypeConvertersMap())
+            addMethod(createCreateAutoMigrationSpecsSet())
             addMethod(getAutoMigrations())
         }
         addDaoImpls(builder)
@@ -125,6 +126,47 @@
         }.build()
     }
 
+    private fun createCreateAutoMigrationSpecsSet(): MethodSpec {
+        val scope = CodeGenScope(this)
+        return MethodSpec.methodBuilder("getRequiredAutoMigrationSpecs").apply {
+            addAnnotation(Override::class.java)
+            addModifiers(PROTECTED)
+            returns(
+                ParameterizedTypeName.get(
+                    CommonTypeNames.SET,
+                    ParameterizedTypeName.get(
+                        ClassName.get(Class::class.java),
+                        WildcardTypeName.subtypeOf(RoomTypeNames.AUTO_MIGRATION_SPEC)
+                    )
+                )
+            )
+            val autoMigrationSpecsVar = scope.getTmpVar("_autoMigrationSpecsSet")
+            val autoMigrationSpecsTypeName = ParameterizedTypeName.get(
+                ClassName.get(HashSet::class.java),
+                ParameterizedTypeName.get(
+                    ClassName.get(Class::class.java),
+                    WildcardTypeName.subtypeOf(RoomTypeNames.AUTO_MIGRATION_SPEC)
+                )
+            )
+            addStatement(
+                "final $T $L = new $T()",
+                autoMigrationSpecsTypeName,
+                autoMigrationSpecsVar,
+                autoMigrationSpecsTypeName
+            )
+            database.autoMigrations.map { autoMigrationResult ->
+                if (autoMigrationResult.isSpecProvided) {
+                    addStatement(
+                        "$L.add($T.class)",
+                        autoMigrationSpecsVar,
+                        autoMigrationResult.specClassName
+                    )
+                }
+            }
+            addStatement("return $L", autoMigrationSpecsVar)
+        }.build()
+    }
+
     private fun createClearAllTables(): MethodSpec {
         val scope = CodeGenScope(this)
         return MethodSpec.methodBuilder("clearAllTables").apply {
@@ -310,8 +352,16 @@
             addModifiers(PROTECTED)
             addAnnotation(Override::class.java)
             returns(ParameterizedTypeName.get(CommonTypeNames.LIST, RoomTypeNames.MIGRATION))
-            val autoMigrationsList = database.autoMigrations.map {
-                CodeBlock.of("new $T()", it.implTypeName)
+            val autoMigrationsList = database.autoMigrations.map { autoMigrationResult ->
+                if (autoMigrationResult.isSpecProvided) {
+                    CodeBlock.of(
+                        "new $T(mAutoMigrationSpecs.get($T.class))",
+                        autoMigrationResult.implTypeName,
+                        autoMigrationResult.specClassName
+                    )
+                } else {
+                    CodeBlock.of("new $T()", autoMigrationResult.implTypeName)
+                }
             }
             addStatement(
                 "return $T.asList($L)",
diff --git a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
index ba487c3..530c21b 100644
--- a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
+++ b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
@@ -1,6 +1,7 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
+import androidx.room.migration.AutoMigrationSpec;
 import androidx.room.migration.Migration;
 import androidx.sqlite.db.SupportSQLiteDatabase;
 import java.lang.Override;
@@ -9,10 +10,10 @@
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation"})
-class AutoMigration_1_2_Impl extends Migration {
-    private final ValidAutoMigrationWithDefault callback = new ValidAutoMigrationWithDefault();
+class ValidAutoMigrationWithDefault_AutoMigration_1_2_Impl extends Migration {
+    private final AutoMigrationSpec callback = new ValidAutoMigrationWithDefault();
 
-    public AutoMigration_1_2_Impl() {
+    public ValidAutoMigrationWithDefault_AutoMigration_1_2_Impl() {
         super(1, 2);
     }
 
diff --git a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java
index 0a65d6f..b8ab84c 100644
--- a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java
+++ b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java
@@ -1,6 +1,7 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
+import androidx.room.migration.AutoMigrationSpec;
 import androidx.room.migration.Migration;
 import androidx.sqlite.db.SupportSQLiteDatabase;
 import java.lang.Override;
@@ -9,10 +10,10 @@
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation"})
-class AutoMigration_1_2_Impl extends Migration {
-    private final ValidAutoMigrationWithoutDefault callback = new ValidAutoMigrationWithoutDefault();
+class ValidAutoMigrationWithoutDefault_AutoMigration_1_2_Impl extends Migration {
+    private final AutoMigrationSpec callback = new ValidAutoMigrationWithoutDefault();
 
-    public AutoMigration_1_2_Impl() {
+    public ValidAutoMigrationWithoutDefault_AutoMigration_1_2_Impl() {
         super(1, 2);
     }
 
diff --git a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
index 86359d5..5dd84db 100644
--- a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
+++ b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
@@ -5,6 +5,7 @@
 import androidx.room.RoomOpenHelper;
 import androidx.room.RoomOpenHelper.Delegate;
 import androidx.room.RoomOpenHelper.ValidationResult;
+import androidx.room.migration.AutoMigrationSpec;
 import androidx.room.migration.Migration;
 import androidx.room.util.DBUtil;
 import androidx.room.util.TableInfo;
@@ -187,6 +188,12 @@
     }
 
     @Override
+    protected Set<Class<? extends AutoMigrationSpec>> getRequiredAutoMigrationSpecs() {
+        final HashSet<Class<? extends AutoMigrationSpec>> _autoMigrationSpecsSet = new HashSet<Class<? extends AutoMigrationSpec>>();
+        return _autoMigrationSpecsSet;
+    }
+
+    @Override
     protected List<Migration> getAutoMigrations() {
         return Arrays.asList();
     }
diff --git a/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
index a7d0fc0e..57203fde 100644
--- a/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
@@ -73,6 +73,7 @@
                 specElement = invocation.processingEnv.requireTypeElement(
                     "foo.bar.ValidAutoMigrationWithDefault"
                 ),
+                isSpecProvided = false
             )
             AutoMigrationWriter(
                 autoMigrationResultWithNewAddedColumn.element,
@@ -85,7 +86,7 @@
                     loadTestSource(
                         "autoMigrationWriter/output/ValidAutoMigrationWithDefault" +
                             ".java",
-                        "foo.bar.AutoMigration_1_2_Impl"
+                        "foo.bar.ValidAutoMigrationWithDefault_AutoMigration_1_2_Impl"
                     )
                 )
             }
@@ -136,6 +137,7 @@
                 specElement = invocation.processingEnv.requireTypeElement(
                     "foo.bar.ValidAutoMigrationWithoutDefault"
                 ),
+                isSpecProvided = false
             )
             AutoMigrationWriter(
                 autoMigrationResultWithNewAddedColumn.element,
@@ -147,7 +149,7 @@
                 generatedSource(
                     loadTestSource(
                         "autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java",
-                        "foo.bar.AutoMigration_1_2_Impl"
+                        "foo.bar.ValidAutoMigrationWithoutDefault_AutoMigration_1_2_Impl"
                     )
                 )
             }
diff --git a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json
new file mode 100644
index 0000000..a47d59f
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json
@@ -0,0 +1,47 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "250b199947a14a67da3f70823e90f8bc",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "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, '250b199947a14a67da3f70823e90f8bc')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json
new file mode 100644
index 0000000..96cda7c
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json
@@ -0,0 +1,87 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "553bb9e0bb6c2673f0687f76e66f6304",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "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, '553bb9e0bb6c2673f0687f76e66f6304')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationDb.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationDb.java
index 997a9d1..64796f1 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationDb.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationDb.java
@@ -16,9 +16,6 @@
 
 package androidx.room.integration.testapp.migration;
 
-
-import android.database.sqlite.SQLiteDatabase;
-
 import androidx.annotation.NonNull;
 import androidx.room.AutoMigration;
 import androidx.room.ColumnInfo;
@@ -38,6 +35,7 @@
 import androidx.room.RenameTable;
 import androidx.room.RoomDatabase;
 import androidx.room.migration.AutoMigrationSpec;
+import androidx.sqlite.db.SupportSQLiteDatabase;
 
 import java.util.List;
 
@@ -413,7 +411,8 @@
             toColumnName = "renamedInV2")
     @DeleteColumn(tableName = "Entity15", columnName = "addedInV1")
     static class SimpleAutoMigration1 implements AutoMigrationSpec {
-        void onPostMigrate(SQLiteDatabase db) {
+        @Override
+        public void onPostMigrate(@NonNull SupportSQLiteDatabase db) {
             // Do something
         }
     }
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/ProvidedAutoMigrationSpecTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/ProvidedAutoMigrationSpecTest.java
new file mode 100644
index 0000000..da5c158
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/ProvidedAutoMigrationSpecTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.migration;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import androidx.annotation.NonNull;
+import androidx.room.AutoMigration;
+import androidx.room.ColumnInfo;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+import androidx.room.ProvidedAutoMigrationSpec;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.migration.AutoMigrationSpec;
+import androidx.room.testing.MigrationTestHelper;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Test custom database migrations.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ProvidedAutoMigrationSpecTest {
+    private static final String TEST_DB = "auto-migration-test";
+    private ProvidedAutoMigrationDb.MyProvidedAutoMigration mProvidedSpec =
+            new ProvidedAutoMigrationDb.MyProvidedAutoMigration("Hi");
+
+    @Rule
+    public MigrationTestHelper helper;
+
+    public ProvidedAutoMigrationSpecTest() {
+        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
+                ProvidedAutoMigrationDb.class.getCanonicalName());
+    }
+
+    @Database(
+            version = ProvidedAutoMigrationDb.LATEST_VERSION,
+            entities = {
+                    ProvidedAutoMigrationDb.Entity1.class,
+                    ProvidedAutoMigrationDb.Entity2.class
+            },
+            autoMigrations = {
+                    @AutoMigration(
+                            from = 1, to = 2, spec =
+                            ProvidedAutoMigrationDb.MyProvidedAutoMigration.class
+                    )
+            },
+            exportSchema = true
+    )
+    public abstract static class ProvidedAutoMigrationDb extends RoomDatabase {
+        static final int LATEST_VERSION = 2;
+
+        /**
+         * No change between versions.
+         */
+        @Entity
+        static class Entity1 {
+            public static final String TABLE_NAME = "Entity1";
+            @PrimaryKey
+            public int id;
+            public String name;
+            @ColumnInfo(defaultValue = "1")
+            public int addedInV1;
+        }
+
+        /**
+         * A new table added.
+         */
+        @Entity
+        static class Entity2 {
+            public static final String TABLE_NAME = "Entity2";
+            @PrimaryKey
+            public int id;
+            public String name;
+            @ColumnInfo(defaultValue = "1")
+            public int addedInV1;
+            @ColumnInfo(defaultValue = "2")
+            public int addedInV2;
+        }
+
+        @ProvidedAutoMigrationSpec
+        static class MyProvidedAutoMigration implements AutoMigrationSpec {
+            private final String mPrefString;
+            public boolean mOnPostMigrateCalled = false;
+
+            MyProvidedAutoMigration(String prefString) {
+                this.mPrefString = prefString;
+            }
+
+            @Override
+            public void onPostMigrate(@NonNull SupportSQLiteDatabase db) {
+                mOnPostMigrateCalled = true;
+            }
+        }
+    }
+
+    // Run this to create the very 1st version of the db.
+    public void createFirstVersion() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
+        db.close();
+    }
+
+    @Test
+    public void testOnPostMigrate() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
+        ProvidedAutoMigrationDb autoMigrationDbV2 = getLatestDb();
+        helper.runMigrationsAndValidate(
+                TEST_DB,
+                2,
+                true
+        );
+        assertThat(mProvidedSpec.mOnPostMigrateCalled, is(true));
+    }
+
+    /**
+     * Verifies that the user defined migration is selected over using an autoMigration.
+     */
+    @Test
+    public void testNoSpecProvidedInConfig() {
+        try {
+            ProvidedAutoMigrationDb autoMigrationDbV2 = Room.databaseBuilder(
+                    InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                    ProvidedAutoMigrationDb.class, TEST_DB).build();
+        } catch (IllegalArgumentException exception) {
+            assertThat(
+                    exception.getMessage(),
+                    containsString(
+                            "A required auto migration spec (androidx.room.integration.testapp"
+                                    + ".migration.ProvidedAutoMigrationSpecTest"
+                                    + ".ProvidedAutoMigrationDb.MyProvidedAutoMigration) is "
+                                    + "missing in the database configuration."
+                    )
+            );
+        }
+    }
+
+    private ProvidedAutoMigrationDb getLatestDb() {
+        ProvidedAutoMigrationDb db = Room.databaseBuilder(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                ProvidedAutoMigrationDb.class, TEST_DB)
+                .addAutoMigrationSpec(mProvidedSpec)
+                .build();
+        db.getOpenHelper().getWritableDatabase(); // trigger open
+        helper.closeWhenFinished(db);
+        return db;
+    }
+}
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index e093582..50f212f 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -6,6 +6,7 @@
     method @Deprecated public boolean isMigrationRequiredFrom(int);
     field public final boolean allowDestructiveMigrationOnDowngrade;
     field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
     field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
     field public final android.content.Context context;
     field public final String? copyFromAssetPath;
@@ -71,6 +72,7 @@
   }
 
   public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
     method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
     method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
     method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
diff --git a/room/runtime/api/public_plus_experimental_current.txt b/room/runtime/api/public_plus_experimental_current.txt
index ec1cb85..758a9f6 100644
--- a/room/runtime/api/public_plus_experimental_current.txt
+++ b/room/runtime/api/public_plus_experimental_current.txt
@@ -6,6 +6,7 @@
     method @Deprecated public boolean isMigrationRequiredFrom(int);
     field public final boolean allowDestructiveMigrationOnDowngrade;
     field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
     field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
     field public final android.content.Context context;
     field public final String? copyFromAssetPath;
@@ -75,6 +76,7 @@
   }
 
   public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
     method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
     method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
     method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
diff --git a/room/runtime/api/restricted_current.txt b/room/runtime/api/restricted_current.txt
index 8075022..14c301a 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -7,11 +7,13 @@
     ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?);
     ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?);
     ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?, java.util.List<androidx.room.migration.AutoMigrationSpec!>?);
     method public boolean isMigrationRequired(int, int);
     method @Deprecated public boolean isMigrationRequiredFrom(int);
     field public final boolean allowDestructiveMigrationOnDowngrade;
     field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
     field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
     field public final android.content.Context context;
     field public final String? copyFromAssetPath;
@@ -114,6 +116,7 @@
   }
 
   public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
     method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
     method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
     method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
diff --git a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
index d19c0cb..1861836 100644
--- a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
+++ b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
@@ -22,6 +22,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.room.migration.AutoMigrationSpec;
 import androidx.sqlite.db.SupportSQLiteOpenHelper;
 
 import java.io.File;
@@ -69,6 +70,9 @@
     @NonNull
     public final List<Object> typeConverters;
 
+    @NonNull
+    public final List<AutoMigrationSpec> autoMigrationSpecs;
+
     /**
      * Whether Room should throw an exception for queries run on the main thread.
      */
@@ -133,14 +137,13 @@
     @Nullable
     public final Callable<InputStream> copyFromInputStream;
 
-
     /**
      * Creates a database configuration with the given values.
      *
      * @deprecated Use {@link #DatabaseConfiguration(Context, String,
      * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
      * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
-     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
      *
      * @param context The application context.
      * @param name Name of the database, can be null if it is in memory.
@@ -170,7 +173,8 @@
             @Nullable Set<Integer> migrationNotRequiredFrom) {
         this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
                 allowMainThreadQueries, journalMode, queryExecutor, queryExecutor, false,
-                requireMigration, false, migrationNotRequiredFrom, null, null, null, null, null);
+                requireMigration, false, migrationNotRequiredFrom, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -179,7 +183,7 @@
      * @deprecated Use {@link #DatabaseConfiguration(Context, String,
      * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
      * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
-     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
      *
      * @param context The application context.
      * @param name Name of the database, can be null if it is in memory.
@@ -216,7 +220,7 @@
         this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
                 allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
                 multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
-                migrationNotRequiredFrom, null, null, null, null, null);
+                migrationNotRequiredFrom, null, null, null, null, null, null);
     }
 
     /**
@@ -225,7 +229,7 @@
      * @deprecated Use {@link #DatabaseConfiguration(Context, String,
      * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
      * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
-     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
      *
      * @param context The application context.
      * @param name Name of the database, can be null if it is in memory.
@@ -266,7 +270,7 @@
         this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
                 allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
                 multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
-                migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, null, null, null);
+                migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, null, null, null, null);
     }
 
     /**
@@ -275,7 +279,7 @@
      * @deprecated Use {@link #DatabaseConfiguration(Context, String,
      * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
      * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
-     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
      *
      * @param context The application context.
      * @param name Name of the database, can be null if it is in memory.
@@ -320,16 +324,16 @@
                 allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
                 multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
                 migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
-                null, null);
+                null, null, null);
     }
 
-     /**
-      * Creates a database configuration with the given values.
-      *
-      * @deprecated Use {@link #DatabaseConfiguration(Context, String,
+    /**
+     * Creates a database configuration with the given values.
+     *
+     * @deprecated Use {@link #DatabaseConfiguration(Context, String,
      * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
      * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
-     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
      *
      * @param context The application context.
      * @param name Name of the database, can be null if it is in memory.
@@ -377,7 +381,66 @@
                 allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
                 multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
                 migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
-                prepackagedDatabaseCallback, null);
+                prepackagedDatabaseCallback, null, null);
+    }
+
+    /**
+     * Creates a database configuration with the given values.
+     *
+     * @deprecated Use {@link #DatabaseConfiguration(Context, String,
+     * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
+     * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
+     * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
+     *
+     * @param context The application context.
+     * @param name Name of the database, can be null if it is in memory.
+     * @param sqliteOpenHelperFactory The open helper factory to use.
+     * @param migrationContainer The migration container for migrations.
+     * @param callbacks The list of callbacks for database events.
+     * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
+     * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
+     * @param queryExecutor The Executor used to execute asynchronous queries.
+     * @param transactionExecutor The Executor used to execute asynchronous transactions.
+     * @param multiInstanceInvalidation True if Room should perform multi-instance invalidation.
+     * @param requireMigration True if Room should require a valid migration if version changes,
+     * @param allowDestructiveMigrationOnDowngrade True if Room should recreate tables if no
+     *                                             migration is supplied during a downgrade.
+     * @param migrationNotRequiredFrom The collection of schema versions from which migrations
+     *                                 aren't required.
+     * @param copyFromAssetPath The assets path to the pre-packaged database.
+     * @param copyFromFile The pre-packaged database file.
+     * @param copyFromInputStream The callable to get the input stream from which a
+     *                            pre-package database file will be copied from.
+     * @param prepackagedDatabaseCallback The pre-packaged callback.
+     * @param typeConverters The type converters.
+     *
+     * @hide
+     */
+    @Deprecated
+    @SuppressLint("LambdaLast")
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
+            @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
+            @NonNull RoomDatabase.MigrationContainer migrationContainer,
+            @Nullable List<RoomDatabase.Callback> callbacks,
+            boolean allowMainThreadQueries,
+            @NonNull RoomDatabase.JournalMode journalMode,
+            @NonNull Executor queryExecutor,
+            @NonNull Executor transactionExecutor,
+            boolean multiInstanceInvalidation,
+            boolean requireMigration,
+            boolean allowDestructiveMigrationOnDowngrade,
+            @Nullable Set<Integer> migrationNotRequiredFrom,
+            @Nullable String copyFromAssetPath,
+            @Nullable File copyFromFile,
+            @Nullable Callable<InputStream> copyFromInputStream,
+            @Nullable RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback,
+            @Nullable List<Object> typeConverters) {
+        this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
+                allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
+                multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
+                migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
+                prepackagedDatabaseCallback, typeConverters, null);
     }
 
     /**
@@ -404,6 +467,7 @@
      *                            pre-package database file will be copied from.
      * @param prepackagedDatabaseCallback The pre-packaged callback.
      * @param typeConverters The type converters.
+     * @param autoMigrationSpecs The auto migration specs.
      *
      * @hide
      */
@@ -425,7 +489,8 @@
             @Nullable File copyFromFile,
             @Nullable Callable<InputStream> copyFromInputStream,
             @Nullable RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback,
-            @Nullable List<Object> typeConverters) {
+            @Nullable List<Object> typeConverters,
+            @Nullable List<AutoMigrationSpec> autoMigrationSpecs) {
         this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
         this.context = context;
         this.name = name;
@@ -444,6 +509,8 @@
         this.copyFromInputStream = copyFromInputStream;
         this.prepackagedDatabaseCallback = prepackagedDatabaseCallback;
         this.typeConverters = typeConverters == null ? Collections.emptyList() : typeConverters;
+        this.autoMigrationSpecs = autoMigrationSpecs == null
+                ? Collections.emptyList() : autoMigrationSpecs;
     }
 
     /**
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index 2dfbf93..f08ff2b 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -33,6 +33,7 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.WorkerThread;
 import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.room.migration.AutoMigrationSpec;
 import androidx.room.migration.Migration;
 import androidx.room.util.SneakyThrow;
 import androidx.sqlite.db.SimpleSQLiteQuery;
@@ -100,6 +101,15 @@
     @Deprecated
     protected List<Callback> mCallbacks;
 
+    /**
+     * A map of auto migration spec classes to their provided instance.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    protected Map<Class<? extends AutoMigrationSpec>, AutoMigrationSpec> mAutoMigrationSpecs;
+
     private final ReentrantReadWriteLock mCloseLock = new ReentrantReadWriteLock();
 
     @Nullable
@@ -151,7 +161,6 @@
     // Updated later to an unmodifiable map when init is called.
     private final Map<Class<?>, Object> mTypeConverters;
 
-
     /**
      * Gets the instance of the given Type Converter.
      *
@@ -175,6 +184,7 @@
     public RoomDatabase() {
         mInvalidationTracker = createInvalidationTracker();
         mTypeConverters = new HashMap<>();
+        mAutoMigrationSpecs = new HashMap<>();
     }
 
     /**
@@ -185,6 +195,39 @@
     @CallSuper
     public void init(@NonNull DatabaseConfiguration configuration) {
         mOpenHelper = createOpenHelper(configuration);
+        Set<Class<? extends AutoMigrationSpec>> requiredAutoMigrationSpecs =
+                getRequiredAutoMigrationSpecs();
+        BitSet usedSpecs = new BitSet();
+        for (Class<? extends AutoMigrationSpec> spec : requiredAutoMigrationSpecs) {
+            int foundIndex = -1;
+            for (int providedIndex = configuration.autoMigrationSpecs.size() - 1;
+                    providedIndex >= 0; providedIndex--
+            ) {
+                Object provided = configuration.autoMigrationSpecs.get(providedIndex);
+                if (spec.isAssignableFrom(provided.getClass())) {
+                    foundIndex = providedIndex;
+                    usedSpecs.set(foundIndex);
+                    break;
+                }
+            }
+            if (foundIndex < 0) {
+                throw new IllegalArgumentException(
+                        "A required auto migration spec (" + spec.getCanonicalName()
+                                + ") is missing in the database configuration.");
+            }
+            mAutoMigrationSpecs.put(spec, configuration.autoMigrationSpecs.get(foundIndex));
+        }
+
+        for (int providedIndex = configuration.autoMigrationSpecs.size() - 1;
+                providedIndex >= 0; providedIndex--) {
+            if (!usedSpecs.get(providedIndex)) {
+                throw new IllegalArgumentException("Unexpected auto migration specs found. "
+                        + "Annotate AutoMigrationSpec implementation with "
+                        + "@ProvidedAutoMigrationSpec annotation or remove this spec from the "
+                        + "builder.");
+            }
+        }
+
         List<Migration> autoMigrations = getAutoMigrations();
         for (Migration autoMigration : autoMigrations) {
             boolean migrationExists = configuration.migrationContainer.getMigrations()
@@ -376,6 +419,21 @@
     }
 
     /**
+     * Returns a Set of required AutoMigrationSpec classes.
+     * <p>
+     * This is implemented by the generated code.
+     *
+     * @return Creates a set that will include all required auto migration specs for this database.
+     *
+     * @hide
+     */
+    @NonNull
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    protected Set<Class<? extends AutoMigrationSpec>> getRequiredAutoMigrationSpecs() {
+        return Collections.emptySet();
+    }
+
+    /**
      * Deletes all rows from all the tables that are registered to this database as
      * {@link Database#entities()}.
      * <p>
@@ -743,6 +801,7 @@
         private QueryCallback mQueryCallback;
         private Executor mQueryCallbackExecutor;
         private List<Object> mTypeConverters;
+        private List<AutoMigrationSpec> mAutoMigrationSpecs;
 
         /** The Executor used to run database queries. This should be background-threaded. */
         private Executor mQueryExecutor;
@@ -1015,6 +1074,23 @@
         }
 
         /**
+         * Adds an auto migration spec to the builder.
+         *
+         * @param autoMigrationSpec The auto migration object that is annotated with
+         * {@link AutoMigrationSpec} and is declared in an {@link AutoMigration} annotation.
+         * @return This {@link Builder} instance.
+         */
+        @NonNull
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        public Builder<T> addAutoMigrationSpec(@NonNull AutoMigrationSpec autoMigrationSpec) {
+            if (mAutoMigrationSpecs == null) {
+                mAutoMigrationSpecs = new ArrayList<>();
+            }
+            mAutoMigrationSpecs.add(autoMigrationSpec);
+            return this;
+        }
+
+        /**
          * Disables the main thread query check for Room.
          * <p>
          * Room ensures that Database is never accessed on the main thread because it may lock the
@@ -1407,7 +1483,8 @@
                             mCopyFromFile,
                             mCopyFromInputStream,
                             mPrepackagedDatabaseCallback,
-                            mTypeConverters);
+                            mTypeConverters,
+                            mAutoMigrationSpecs);
             T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
             db.init(configuration);
             return db;
diff --git a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
index fcb1b87..c846037 100644
--- a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
+++ b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
@@ -169,6 +169,7 @@
                 null,
                 null,
                 null,
+                null,
                 null);
         RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
                 new CreatingDelegate(schemaBundle.getDatabase()),
@@ -232,6 +233,7 @@
                 null,
                 null,
                 null,
+                null,
                 null);
         RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
                 new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),