Merge changes from topic "index_order" into androidx-main

* changes:
  Use enums for @Index#Order API
  Workaround KSP enum annotation value inconsistency.
  Adding support for column index order in @Index
diff --git a/room/room-common/api/current.txt b/room/room-common/api/current.txt
index 3ad7e2a..89127f9 100644
--- a/room/room-common/api/current.txt
+++ b/room/room-common/api/current.txt
@@ -139,10 +139,16 @@
 
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
     method public abstract String name() default "";
+    method public abstract androidx.room.Index.Order[] orders() default {};
     method public abstract boolean unique() default false;
     method public abstract String[] value();
   }
 
+  public enum Index.Order {
+    enum_constant public static final androidx.room.Index.Order ASC;
+    enum_constant public static final androidx.room.Index.Order DESC;
+  }
+
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
     method public abstract Class<?> entity() default java.lang.Object.class;
     method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
diff --git a/room/room-common/api/public_plus_experimental_current.txt b/room/room-common/api/public_plus_experimental_current.txt
index 3ad7e2a..89127f9 100644
--- a/room/room-common/api/public_plus_experimental_current.txt
+++ b/room/room-common/api/public_plus_experimental_current.txt
@@ -139,10 +139,16 @@
 
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
     method public abstract String name() default "";
+    method public abstract androidx.room.Index.Order[] orders() default {};
     method public abstract boolean unique() default false;
     method public abstract String[] value();
   }
 
+  public enum Index.Order {
+    enum_constant public static final androidx.room.Index.Order ASC;
+    enum_constant public static final androidx.room.Index.Order DESC;
+  }
+
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
     method public abstract Class<?> entity() default java.lang.Object.class;
     method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
diff --git a/room/room-common/api/restricted_current.txt b/room/room-common/api/restricted_current.txt
index 722a507..dbdc6af 100644
--- a/room/room-common/api/restricted_current.txt
+++ b/room/room-common/api/restricted_current.txt
@@ -139,10 +139,16 @@
 
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
     method public abstract String name() default "";
+    method public abstract androidx.room.Index.Order[] orders() default {};
     method public abstract boolean unique() default false;
     method public abstract String[] value();
   }
 
+  public enum Index.Order {
+    enum_constant public static final androidx.room.Index.Order ASC;
+    enum_constant public static final androidx.room.Index.Order DESC;
+  }
+
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
     method public abstract Class<?> entity() default java.lang.Object.class;
     method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
diff --git a/room/room-common/src/main/java/androidx/room/Index.java b/room/room-common/src/main/java/androidx/room/Index.java
index 47242b3..dc9206a 100644
--- a/room/room-common/src/main/java/androidx/room/Index.java
+++ b/room/room-common/src/main/java/androidx/room/Index.java
@@ -55,6 +55,31 @@
     String[] value();
 
     /**
+     * List of column sort orders in the Index.
+     * <p>
+     * The number of entries in the array should be equal to size of columns in {@link #value()}.
+     * <p>
+     * The default order of all columns in the index is {@link Order#ASC}.
+     * <p>
+     * Note that there is no value in providing a sort order on a single-column index. Column sort
+     * order of an index are relevant on multi-column indices and specifically in those that are
+     * considered 'covering indices', for such indices specifying an order can have performance
+     * improvements on queries containing ORDER BY clauses.SchemaDifferTest See
+     * <a href="https://www.sqlite.org/queryplanner.html#_sorting_by_index">SQLite documentation</a>
+     * for details on sorting by index and the usage of the sort order by the query optimizer.
+     * <p>
+     * As an example, consider a table called 'Song' with two columns, 'name' and 'length'. If a
+     * covering index is created for it: <code>CREATE INDEX `song_name_length` on `Song`
+     * (`name` ASC, `length` DESC)</code>, then a query containing an ORDER BY clause with matching
+     * order of the index will be able to avoid a table scan by using the index, but a mismatch in
+     * order won't. Therefore the columns order of the index should be the same as the most
+     * frequently executed query with sort order.
+     *
+     * @return The list of column sort orders in the Index.
+     */
+    Order[] orders() default {};
+
+    /**
      * Name of the index. If not set, Room will set it to the list of columns joined by '_' and
      * prefixed by "index_${tableName}". So if you have a table with name "Foo" and with an index
      * of {"bar", "baz"}, generated index name will be  "index_Foo_bar_baz". If you need to specify
@@ -71,4 +96,20 @@
      * @return True if index is unique. False by default.
      */
     boolean unique() default false;
+
+    enum Order {
+        /**
+         * Ascending returning order.
+         *
+         * @see Index#orders()
+         */
+        ASC,
+
+        /**
+         * Descending returning order.
+         *
+         * @see Index#orders()
+         */
+        DESC
+    }
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
index dc10afa..72f786e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
@@ -169,6 +169,12 @@
 }
 
 private fun <R> Any.readAsEnum(enumClass: Class<R>): R? {
+    // TODO: https://github.com/google/ksp/issues/429
+    // If the enum value is from compiled code KSP gives us the actual value an not the KSType,
+    // so return it instead of using valueOf() to get an instance of the entry.
+    if (enumClass.isAssignableFrom(this::class.java)) {
+        return enumClass.cast(this)
+    }
     val ksType = this as? KSType ?: return null
     val classDeclaration = ksType.declaration as? KSClassDeclaration ?: return null
     val enumValue = classDeclaration.simpleName.asString()
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationValue.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationValue.kt
index 71c0519..7fd29c2 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationValue.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationValue.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.processing.ksp
 
 import androidx.room.compiler.processing.XAnnotationValue
+import com.google.devtools.ksp.getClassDeclarationByName
 import com.google.devtools.ksp.symbol.ClassKind
 import com.google.devtools.ksp.symbol.KSAnnotation
 import com.google.devtools.ksp.symbol.KSClassDeclaration
@@ -56,6 +57,20 @@
                 // with, and using "Any" prevents the array from being cast to the correct
                 // type later on.
                 is List<*> -> value.map { unwrap(it) }
+                // TODO: https://github.com/google/ksp/issues/429
+                // If the enum value is from compiled code KSP gives us the actual value an not
+                // the KSType, so we wrap it as KspEnumEntry for consistency.
+                is Enum<*> -> {
+                    val declaration =
+                        env.resolver.getClassDeclarationByName(value::class.java.canonicalName)
+                            ?: error("Cannot find KSClassDeclaration for Enum '$value'.")
+                    val valueDeclaration = declaration.declarations
+                        .filterIsInstance<KSClassDeclaration>()
+                        .filter { it.classKind == ClassKind.ENUM_ENTRY }
+                        .firstOrNull() { it.simpleName.getShortName() == value.name }
+                        ?: error("Cannot find ENUM_ENTRY '$value' in '$declaration'.")
+                    KspEnumEntry.create(env, valueDeclaration)
+                }
                 else -> value
             }
         }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
index d7ce271..9518163 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
@@ -17,6 +17,8 @@
 package androidx.room.compiler.processing
 
 import androidx.room.compiler.processing.testcode.JavaAnnotationWithDefaults
+import androidx.room.compiler.processing.testcode.JavaAnnotationWithEnum
+import androidx.room.compiler.processing.testcode.JavaAnnotationWithEnumArray
 import androidx.room.compiler.processing.testcode.JavaAnnotationWithPrimitiveArray
 import androidx.room.compiler.processing.testcode.JavaAnnotationWithTypeReferences
 import androidx.room.compiler.processing.testcode.JavaEnum
@@ -512,6 +514,86 @@
     }
 
     @Test
+    fun javaEnum() {
+        val javaSrc = Source.java(
+            "JavaSubject.java",
+            """
+            import androidx.room.compiler.processing.testcode.*;
+            class JavaSubject {
+                @JavaAnnotationWithEnum(JavaEnum.VAL1)
+                Object annotated1;
+            }
+            """.trimIndent()
+        )
+        val kotlinSrc = Source.kotlin(
+            "KotlinSubject.kt",
+            """
+            import androidx.room.compiler.processing.testcode.*;
+            class KotlinSubject {
+                @JavaAnnotationWithEnum(JavaEnum.VAL1)
+                val annotated1: Any = TODO()
+            }
+            """.trimIndent()
+        )
+        runTest(
+            sources = listOf(javaSrc, kotlinSrc)
+        ) { invocation ->
+            arrayOf("JavaSubject", "KotlinSubject").map {
+                invocation.processingEnv.requireTypeElement(it)
+            }.forEach { subject ->
+                val annotation = subject.getField("annotated1").getAnnotation(
+                    JavaAnnotationWithEnum::class
+                )
+                assertThat(
+                    annotation?.value?.value
+                ).isEqualTo(
+                    JavaEnum.VAL1
+                )
+            }
+        }
+    }
+
+    @Test
+    fun javaEnumArray() {
+        val javaSrc = Source.java(
+            "JavaSubject.java",
+            """
+            import androidx.room.compiler.processing.testcode.*;
+            class JavaSubject {
+                @JavaAnnotationWithEnumArray(enumArray = {JavaEnum.VAL1, JavaEnum.VAL2})
+                Object annotated1;
+            }
+            """.trimIndent()
+        )
+        val kotlinSrc = Source.kotlin(
+            "KotlinSubject.kt",
+            """
+            import androidx.room.compiler.processing.testcode.*;
+            class KotlinSubject {
+                @JavaAnnotationWithEnumArray(enumArray = [JavaEnum.VAL1, JavaEnum.VAL2])
+                val annotated1:Any = TODO()
+            }
+            """.trimIndent()
+        )
+        runTest(
+            sources = listOf(javaSrc, kotlinSrc)
+        ) { invocation ->
+            arrayOf("JavaSubject", "KotlinSubject").map {
+                invocation.processingEnv.requireTypeElement(it)
+            }.forEach { subject ->
+                val annotation = subject.getField("annotated1").getAnnotation(
+                    JavaAnnotationWithEnumArray::class
+                )
+                assertThat(
+                    annotation?.value?.enumArray
+                ).isEqualTo(
+                    arrayOf(JavaEnum.VAL1, JavaEnum.VAL2)
+                )
+            }
+        }
+    }
+
+    @Test
     fun javaRepeatableAnnotation() {
         val javaSrc = Source.java(
             "JavaSubject",
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index 05526ea..f7b5a10 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -17,6 +17,8 @@
 package androidx.room.compiler.processing
 
 import androidx.room.compiler.processing.testcode.JavaAnnotationWithDefaults
+import androidx.room.compiler.processing.testcode.JavaAnnotationWithEnum
+import androidx.room.compiler.processing.testcode.JavaAnnotationWithEnumArray
 import androidx.room.compiler.processing.testcode.JavaAnnotationWithPrimitiveArray
 import androidx.room.compiler.processing.testcode.JavaAnnotationWithTypeReferences
 import androidx.room.compiler.processing.testcode.JavaEnum
@@ -587,6 +589,84 @@
     }
 
     @Test
+    fun javaEnum() {
+        val javaSrc = Source.java(
+            "JavaSubject.java",
+            """
+            import androidx.room.compiler.processing.testcode.*;
+            class JavaSubject {
+                @JavaAnnotationWithEnum(JavaEnum.VAL1)
+                Object annotated1;
+            }
+            """.trimIndent()
+        )
+        val kotlinSrc = Source.kotlin(
+            "KotlinSubject.kt",
+            """
+            import androidx.room.compiler.processing.testcode.*;
+            class KotlinSubject {
+                @JavaAnnotationWithEnum(JavaEnum.VAL1)
+                val annotated1: Any = TODO()
+            }
+            """.trimIndent()
+        )
+        runTest(
+            sources = listOf(javaSrc, kotlinSrc)
+        ) { invocation ->
+            listOf("JavaSubject", "KotlinSubject").map {
+                invocation.processingEnv.requireTypeElement(it)
+            }.forEach { subject ->
+                val annotation = subject.getField("annotated1")
+                    .requireAnnotation<JavaAnnotationWithEnum>()
+                assertThat(
+                    annotation.getAsEnum("value").name
+                ).isEqualTo(
+                    JavaEnum.VAL1.name
+                )
+            }
+        }
+    }
+
+    @Test
+    fun javaEnumArray() {
+        val javaSrc = Source.java(
+            "JavaSubject.java",
+            """
+            import androidx.room.compiler.processing.testcode.*;
+            class JavaSubject {
+                @JavaAnnotationWithEnumArray(enumArray = {JavaEnum.VAL1, JavaEnum.VAL2})
+                Object annotated1;
+            }
+            """.trimIndent()
+        )
+        val kotlinSrc = Source.kotlin(
+            "KotlinSubject.kt",
+            """
+            import androidx.room.compiler.processing.testcode.*;
+            class KotlinSubject {
+                @JavaAnnotationWithEnumArray(enumArray = [JavaEnum.VAL1, JavaEnum.VAL2])
+                val annotated1: Any = TODO()
+            }
+            """.trimIndent()
+        )
+        runTest(
+            sources = listOf(javaSrc, kotlinSrc)
+        ) { invocation ->
+            listOf("JavaSubject", "KotlinSubject").map {
+                invocation.processingEnv.requireTypeElement(it)
+            }.forEach { subject ->
+                val annotation = subject.getField("annotated1")
+                    .requireAnnotation<JavaAnnotationWithEnumArray>()
+                assertThat(
+                    annotation.getAsEnumList("enumArray").map { it.name }
+                ).isEqualTo(
+                    listOf(JavaEnum.VAL1.name, JavaEnum.VAL2.name)
+                )
+            }
+        }
+    }
+
+    @Test
     fun javaRepeatableAnnotation() {
         val javaSrc = Source.java(
             "JavaSubject",
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithEnum.java b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithEnum.java
new file mode 100644
index 0000000..f074db1
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithEnum.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 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.compiler.processing.testcode;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+public @interface JavaAnnotationWithEnum {
+    JavaEnum value() default JavaEnum.DEFAULT;
+}
+
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithEnumArray.java b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithEnumArray.java
new file mode 100644
index 0000000..80b70b6
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithEnumArray.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 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.compiler.processing.testcode;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+public @interface JavaAnnotationWithEnumArray {
+    JavaEnum[] enumArray() default {};
+}
+
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/EntityProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/EntityProcessor.kt
index 9a9998e..9ff4211 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/EntityProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/EntityProcessor.kt
@@ -45,12 +45,14 @@
             return annotation.getAsAnnotationBoxArray<androidx.room.Index>("indices").map {
                 val indexAnnotation = it.value
                 val nameValue = indexAnnotation.name
+                val columns = indexAnnotation.value.asList()
+                val orders = indexAnnotation.orders.asList()
                 val name = if (nameValue == "") {
-                    createIndexName(indexAnnotation.value.asList(), tableName)
+                    createIndexName(columns, tableName)
                 } else {
                     nameValue
                 }
-                IndexInput(name, indexAnnotation.unique, indexAnnotation.value.asList())
+                IndexInput(name, indexAnnotation.unique, columns, orders)
             }
         }
 
@@ -83,7 +85,12 @@
 /**
  * Processed Index annotation output.
  */
-data class IndexInput(val name: String, val unique: Boolean, val columnNames: List<String>)
+data class IndexInput(
+    val name: String,
+    val unique: Boolean,
+    val columnNames: List<String>,
+    val orders: List<androidx.room.Index.Order>
+)
 
 /**
  * ForeignKey, before it is processed in the context of a database.
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 656b9e8..9638dac 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -66,6 +66,8 @@
         " autoGenerate, its type must be int, Integer, long or Long."
     val AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_FIELDS = "When @PrimaryKey annotation is used on a" +
         " field annotated with @Embedded, the embedded class should have only 1 field."
+    val INVALID_INDEX_ORDERS_SIZE = "The number of entries in @Index#orders() should be " +
+        "equal to the amount of columns defined in the @Index value."
 
     val DO_NOT_USE_GENERIC_IMMUTABLE_MULTIMAP = "Do not use ImmutableMultimap as a type (as with" +
         " Multimap itself). Instead use the subtypes such as ImmutableSetMultimap or " +
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
index 030e56f..73f549f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
@@ -26,6 +26,7 @@
 import androidx.room.processor.EntityProcessor.Companion.extractIndices
 import androidx.room.processor.EntityProcessor.Companion.extractTableName
 import androidx.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
+import androidx.room.processor.ProcessorErrors.INVALID_INDEX_ORDERS_SIZE
 import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
 import androidx.room.processor.cache.Cache
 import androidx.room.vo.EmbeddedField
@@ -117,7 +118,8 @@
                     IndexInput(
                         name = createIndexName(listOf(it.columnName), tableName),
                         unique = false,
-                        columnNames = listOf(it.columnName)
+                        columnNames = listOf(it.columnName),
+                        orders = emptyList()
                     )
                 }
             }
@@ -476,10 +478,21 @@
                 )
                 field
             }
+            if (input.orders.isNotEmpty()) {
+                context.checker.check(
+                    input.columnNames.size == input.orders.size, element,
+                    INVALID_INDEX_ORDERS_SIZE
+                )
+            }
             if (fields.isEmpty()) {
                 null
             } else {
-                Index(name = input.name, unique = input.unique, fields = fields)
+                Index(
+                    name = input.name,
+                    unique = input.unique,
+                    fields = fields,
+                    orders = input.orders
+                )
             }
         }
 
@@ -538,7 +551,8 @@
                         IndexInput(
                             name = createIndexName(it.columnNames, tableName),
                             unique = it.unique,
-                            columnNames = it.columnNames
+                            columnNames = it.columnNames,
+                            orders = it.orders
                         )
                     }
                 } else {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/Index.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/Index.kt
index 88a8c56..81a703f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/Index.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/Index.kt
@@ -19,20 +19,36 @@
 import androidx.room.migration.bundle.BundleUtil
 import androidx.room.migration.bundle.IndexBundle
 
+private typealias IndexOrder = androidx.room.Index.Order
+
 /**
  * Represents a processed index.
  */
-data class Index(val name: String, val unique: Boolean, override val fields: Fields) :
-    HasSchemaIdentity, HasFields {
+data class Index(
+    val name: String,
+    val unique: Boolean,
+    override val fields: Fields,
+    val orders: List<IndexOrder>
+) : HasSchemaIdentity, HasFields {
     companion object {
         // should match the value in TableInfo.Index.DEFAULT_PREFIX
         const val DEFAULT_PREFIX = "index_"
     }
 
-    constructor(name: String, unique: Boolean, fields: List<Field>) :
-        this(name, unique, Fields(fields))
+    constructor(
+        name: String,
+        unique: Boolean,
+        fields: List<Field>,
+        orders: List<IndexOrder>
+    ) : this(name, unique, Fields(fields), orders)
 
-    override fun getIdKey() = "$unique-$name-${columnNames.joinToString(",")}"
+    override fun getIdKey() = buildString {
+        append("$unique-$name-${columnNames.joinToString(",")}")
+        // orders was newly added; it should affect the ID only when declared.
+        if (orders.isNotEmpty()) {
+            append("-${orders.joinToString(",")}")
+        }
+    }
 
     fun createQuery(tableName: String): String {
         val indexSQL = if (unique) {
@@ -40,14 +56,21 @@
         } else {
             "INDEX"
         }
+
+        val columns = if (orders.isNotEmpty()) {
+            columnNames.mapIndexed { index, columnName -> "`$columnName` ${orders[index]}" }
+        } else {
+            columnNames.map { "`$it`" }
+        }.joinToString(", ")
+
         return """
             CREATE $indexSQL IF NOT EXISTS `$name`
-            ON `$tableName` (${columnNames.joinToString(", ") { "`$it`" }})
+            ON `$tableName` ($columns)
         """.trimIndent().replace("\n", " ")
     }
 
     fun toBundle(): IndexBundle = IndexBundle(
-        name, unique, columnNames,
+        name, unique, columnNames, orders.map { it.name },
         createQuery(BundleUtil.TABLE_NAME_PLACEHOLDER)
     )
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt
index 8916275..1edc663 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt
@@ -108,14 +108,21 @@
             )
             entity.indices.forEach { index ->
                 val columnNames = index.columnNames.joinToString(",") { "\"$it\"" }
+                val orders = if (index.orders.isEmpty()) {
+                    index.columnNames.map { "ASC" }.joinToString(",") { "\"$it\"" }
+                } else {
+                    index.orders.joinToString(",") { "\"$it\"" }
+                }
                 addStatement(
-                    "$L.add(new $T($S, $L, $T.asList($L)))",
+                    "$L.add(new $T($S, $L, $T.asList($L), $T.asList($L)))",
                     indicesSetVar,
                     RoomTypeNames.TABLE_INFO_INDEX,
                     index.name,
                     index.unique,
                     Arrays::class.typeName,
-                    columnNames
+                    columnNames,
+                    Arrays::class.typeName,
+                    orders,
                 )
             }
 
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
index a880cb0..66b9271 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
@@ -41,6 +41,8 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+private typealias IndexOrder = androidx.room.Index.Order
+
 @RunWith(JUnit4::class)
 class TableEntityProcessorTest : BaseEntityParserTest() {
     @Test
@@ -171,6 +173,85 @@
     }
 
     @Test
+    fun index_sort_desc() {
+        val annotation = mapOf(
+            "indices" to """@Index(value = {"foo"}, orders = {Index.Order.DESC})"""
+        )
+        singleEntity(
+            """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """,
+            annotation
+        ) { entity, _ ->
+            assertThat(
+                entity.indices,
+                `is`(
+                    listOf(
+                        Index(
+                            name = "index_MyEntity_foo",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo"),
+                            orders = listOf(IndexOrder.DESC)
+                        )
+                    )
+                )
+            )
+        }
+    }
+
+    @Test
+    fun index_sort_multiple() {
+        val annotation = mapOf(
+            "tableName" to "\"MyTable\"",
+            "indices" to
+                """@Index(value = {"foo", "id"}, orders = {Index.Order.DESC, Index.Order.ASC})"""
+        )
+        singleEntity(
+            """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """,
+            annotation
+        ) { entity, _ ->
+            assertThat(
+                entity.indices,
+                `is`(
+                    listOf(
+                        Index(
+                            name = "index_MyTable_foo_id",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo", "id"),
+                            orders = listOf(IndexOrder.DESC, IndexOrder.ASC)
+                        )
+                    )
+                )
+            )
+        }
+    }
+
+    @Test
+    fun index_invalidOrdersSize() {
+        val annotation = mapOf(
+            "indices" to """@Index(value = {"foo", "id"}, orders = {Index.Order.DESC})"""
+        )
+        singleEntity(
+            """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """,
+            annotation
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasError(ProcessorErrors.INVALID_INDEX_ORDERS_SIZE)
+            }
+        }
+    }
+
+    @Test
     fun getterWithAssignableType() {
         singleEntity(
             """
@@ -530,7 +611,8 @@
                         Index(
                             name = "index_MyEntity_foo",
                             unique = false,
-                            fields = fieldsByName(entity, "foo")
+                            fields = fieldsByName(entity, "foo"),
+                            emptyList()
                         )
                     )
                 )
@@ -555,7 +637,8 @@
                         Index(
                             name = "index_MyEntity_foo",
                             unique = false,
-                            fields = fieldsByName(entity, "foo")
+                            fields = fieldsByName(entity, "foo"),
+                            orders = emptyList()
                         )
                     )
                 )
@@ -583,7 +666,8 @@
                         Index(
                             name = "index_MyEntity_foo_id",
                             unique = false,
-                            fields = fieldsByName(entity, "foo", "id")
+                            fields = fieldsByName(entity, "foo", "id"),
+                            orders = emptyList()
                         )
                     )
                 )
@@ -613,12 +697,14 @@
                         Index(
                             name = "index_MyEntity_foo_id",
                             unique = false,
-                            fields = fieldsByName(entity, "foo", "id")
+                            fields = fieldsByName(entity, "foo", "id"),
+                            orders = emptyList()
                         ),
                         Index(
                             name = "index_MyEntity_bar_column_foo",
                             unique = false,
-                            fields = fieldsByName(entity, "bar", "foo")
+                            fields = fieldsByName(entity, "bar", "foo"),
+                            orders = emptyList()
                         )
                     )
                 )
@@ -646,7 +732,8 @@
                         Index(
                             name = "index_MyEntity_foo_id",
                             unique = true,
-                            fields = fieldsByName(entity, "foo", "id")
+                            fields = fieldsByName(entity, "foo", "id"),
+                            orders = emptyList()
                         )
                     )
                 )
@@ -674,7 +761,8 @@
                         Index(
                             name = "myName",
                             unique = false,
-                            fields = fieldsByName(entity, "foo")
+                            fields = fieldsByName(entity, "foo"),
+                            orders = emptyList()
                         )
                     )
                 )
@@ -703,7 +791,8 @@
                         Index(
                             name = "index_MyTable_foo",
                             unique = false,
-                            fields = fieldsByName(entity, "foo")
+                            fields = fieldsByName(entity, "foo"),
+                            orders = emptyList()
                         )
                     )
                 )
@@ -851,7 +940,8 @@
                     Index(
                         name = "index_MyEntity_name_lastName",
                         unique = false,
-                        fields = fieldsByName(entity, "name", "lastName")
+                        fields = fieldsByName(entity, "name", "lastName"),
+                        orders = emptyList()
                     )
                 )
             )
@@ -892,7 +982,8 @@
                     Index(
                         name = "index_MyEntity_name_lastName",
                         unique = false,
-                        fields = fieldsByName(entity, "name", "lastName")
+                        fields = fieldsByName(entity, "name", "lastName"),
+                        orders = emptyList()
                     )
                 )
             )
@@ -965,7 +1056,8 @@
                     Index(
                         name = "index_MyEntity_name",
                         unique = false,
-                        fields = fieldsByName(entity, "name")
+                        fields = fieldsByName(entity, "name"),
+                        orders = emptyList()
                     )
                 )
             )
@@ -1151,7 +1243,8 @@
                     Index(
                         name = "index_MyEntity_a",
                         unique = false,
-                        fields = fieldsByName(entity, "a")
+                        fields = fieldsByName(entity, "a"),
+                        orders = emptyList()
                     )
                 )
             )
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
index 55a0fda..b5be2a8 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
@@ -1195,7 +1195,8 @@
                         IndexBundle(
                             "index1",
                             true,
-                            listOf("title"),
+                            emptyList<String>(),
+                            emptyList<String>(),
                             "CREATE UNIQUE INDEX IF NOT EXISTS `index1` ON `Song`" +
                                 "(`title`)"
                         )
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
index 2c90155..f9c70b1 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
@@ -47,12 +47,14 @@
                         Index(
                             name = "leIndex",
                             unique = false,
-                            fields = Fields()
+                            fields = Fields(),
+                            orders = emptyList()
                         ),
                         Index(
                             name = "leIndex2",
                             unique = true,
-                            fields = Fields()
+                            fields = Fields(),
+                            orders = emptyList()
                         )
                     ),
                     foreignKeys = emptyList(),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/vo/IndexTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/vo/IndexTest.kt
index 248b681..ad62980 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/vo/IndexTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/vo/IndexTest.kt
@@ -24,11 +24,13 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+private typealias IndexOrder = androidx.room.Index.Order
+
 @RunWith(JUnit4::class)
 class IndexTest {
     @Test
     fun createSimpleSQL() {
-        val index = Index("foo", false, listOf(mockField("bar"), mockField("baz")))
+        val index = Index("foo", false, listOf(mockField("bar"), mockField("baz")), emptyList())
         MatcherAssert.assertThat(
             index.createQuery("my_table"),
             CoreMatchers.`is`(
@@ -39,7 +41,7 @@
 
     @Test
     fun createUnique() {
-        val index = Index("foo", true, listOf(mockField("bar"), mockField("baz")))
+        val index = Index("foo", true, listOf(mockField("bar"), mockField("baz")), emptyList())
         MatcherAssert.assertThat(
             index.createQuery("my_table"),
             CoreMatchers.`is`(
@@ -48,6 +50,22 @@
         )
     }
 
+    @Test
+    fun createWithSortOrder() {
+        val index = Index(
+            name = "foo",
+            unique = false,
+            fields = listOf(mockField("bar"), mockField("baz")),
+            orders = listOf(IndexOrder.ASC, IndexOrder.DESC)
+        )
+        MatcherAssert.assertThat(
+            index.createQuery("my_table"),
+            CoreMatchers.`is`(
+                "CREATE INDEX IF NOT EXISTS `foo` ON `my_table` (`bar` ASC, `baz` DESC)"
+            )
+        )
+    }
+
     private fun mockField(columnName: String): Field {
         val (element, type) = mockElementAndType()
         return Field(
diff --git a/room/room-migration/api/restricted_current.txt b/room/room-migration/api/restricted_current.txt
index d4f4fff..50f8227 100644
--- a/room/room-migration/api/restricted_current.txt
+++ b/room/room-migration/api/restricted_current.txt
@@ -77,11 +77,13 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class IndexBundle {
-    ctor public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, String!);
+    ctor @Deprecated public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, String!);
+    ctor public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, java.util.List<java.lang.String!>!, String!);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! create(String);
     method public java.util.List<java.lang.String!>! getColumnNames();
     method public String! getCreateSql(String!);
     method public String! getName();
+    method public java.util.List<java.lang.String!>! getOrders();
     method public boolean isSchemaEqual(androidx.room.migration.bundle.IndexBundle);
     method public boolean isUnique();
     field public static final String DEFAULT_PREFIX = "index_";
diff --git a/room/room-migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java b/room/room-migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java
index ae0693e..9fdd582 100644
--- a/room/room-migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java
+++ b/room/room-migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java
@@ -18,9 +18,11 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.room.Index;
 
 import com.google.gson.annotations.SerializedName;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -38,14 +40,25 @@
     private boolean mUnique;
     @SerializedName("columnNames")
     private List<String> mColumnNames;
+    @SerializedName("orders")
+    private List<String> mOrders;
     @SerializedName("createSql")
     private String mCreateSql;
 
-    public IndexBundle(String name, boolean unique, List<String> columnNames,
+    /**
+     * @deprecated Use {@link #IndexBundle(String, boolean, List, List, String)}
+     */
+    @Deprecated
+    public IndexBundle(String name, boolean unique, List<String> columnNames, String createSql) {
+        this(name, unique, columnNames, null, createSql);
+    }
+
+    public IndexBundle(String name, boolean unique, List<String> columnNames, List<String> orders,
             String createSql) {
         mName = name;
         mUnique = unique;
         mColumnNames = columnNames;
+        mOrders = orders;
         mCreateSql = createSql;
     }
 
@@ -61,6 +74,10 @@
         return mColumnNames;
     }
 
+    public List<String> getOrders() {
+        return mOrders;
+    }
+
     /**
      * @param tableName The table name.
      * @return Create index SQL query that uses the given table name.
@@ -95,6 +112,16 @@
                 : other.mColumnNames != null) {
             return false;
         }
+
+        // order matters and null orders is considered equal to all ASC orders, to be backward
+        // compatible with schemas where orders are not present in the schema file
+        int columnsSize = mColumnNames != null ? mColumnNames.size() : 0;
+        List<String> orders = mOrders == null || mOrders.isEmpty()
+                ? Collections.nCopies(columnsSize, Index.Order.ASC.name()) : mOrders;
+        List<String> otherOrders = other.mOrders == null || other.mOrders.isEmpty()
+                ? Collections.nCopies(columnsSize, Index.Order.ASC.name()) : other.mOrders;
+        if (!orders.equals(otherOrders)) return false;
+
         return true;
     }
 }
diff --git a/room/room-migration/src/test/java/androidx/room/migration/bundle/EntityBundleTest.java b/room/room-migration/src/test/java/androidx/room/migration/bundle/EntityBundleTest.java
index 3b9bce2..b40efb5 100644
--- a/room/room-migration/src/test/java/androidx/room/migration/bundle/EntityBundleTest.java
+++ b/room/room-migration/src/test/java/androidx/room/migration/bundle/EntityBundleTest.java
@@ -158,7 +158,7 @@
 
     private IndexBundle createIndexBundle(String colName) {
         return new IndexBundle("ind_" + colName, false,
-                asList(colName), "create");
+                asList(colName), Collections.emptyList(), "create");
     }
 
     private ForeignKeyBundle createForeignKeyBundle(String targetTable, String column) {
diff --git a/room/room-migration/src/test/java/androidx/room/migration/bundle/IndexBundleTest.java b/room/room-migration/src/test/java/androidx/room/migration/bundle/IndexBundleTest.java
index 82d66e2..812f7d3 100644
--- a/room/room-migration/src/test/java/androidx/room/migration/bundle/IndexBundleTest.java
+++ b/room/room-migration/src/test/java/androidx/room/migration/bundle/IndexBundleTest.java
@@ -24,60 +24,88 @@
 import org.junit.runners.JUnit4;
 
 import java.util.Arrays;
+import java.util.Collections;
 
 @RunWith(JUnit4.class)
 public class IndexBundleTest {
     @Test
     public void schemaEquality_same_equal() {
         IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         IndexBundle other = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         assertThat(bundle.isSchemaEqual(other), is(true));
     }
 
     @Test
     public void schemaEquality_diffName_notEqual() {
         IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"),  "sql");
         IndexBundle other = new IndexBundle("index3", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         assertThat(bundle.isSchemaEqual(other), is(false));
     }
 
     @Test
     public void schemaEquality_diffGenericName_equal() {
         IndexBundle bundle = new IndexBundle(IndexBundle.DEFAULT_PREFIX + "x", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         IndexBundle other = new IndexBundle(IndexBundle.DEFAULT_PREFIX + "y", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         assertThat(bundle.isSchemaEqual(other), is(true));
     }
 
     @Test
     public void schemaEquality_diffUnique_notEqual() {
         IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         IndexBundle other = new IndexBundle("index1", true,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         assertThat(bundle.isSchemaEqual(other), is(false));
     }
 
     @Test
     public void schemaEquality_diffColumns_notEqual() {
         IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         IndexBundle other = new IndexBundle("index1", false,
-                Arrays.asList("col2", "col1"), "sql");
+                Arrays.asList("col2", "col1"), Arrays.asList("ASC", "ASC"), "sql");
         assertThat(bundle.isSchemaEqual(other), is(false));
     }
 
     @Test
     public void schemaEquality_diffSql_equal() {
         IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
         IndexBundle other = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql22");
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql22");
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffSort_notEqual() {
+        IndexBundle bundle = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "DESC"), "sql");
+        IndexBundle other = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), Arrays.asList("DESC", "ASC"), "sql");
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_sortNullVsAllAsc_isEqual() {
+        IndexBundle bundle = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
+        IndexBundle other = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), null, "sql");
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_sortEmptyVsAllAsc_isEqual() {
+        IndexBundle bundle = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), Arrays.asList("ASC", "ASC"), "sql");
+        IndexBundle other = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), Collections.emptyList(), "sql");
         assertThat(bundle.isSchemaEqual(other), is(true));
     }
 }
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index 9eaf43a..60b720c 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -330,10 +330,12 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class TableInfo.Index {
-    ctor public TableInfo.Index(String!, boolean, java.util.List<java.lang.String!>!);
+    ctor @Deprecated public TableInfo.Index(String!, boolean, java.util.List<java.lang.String!>!);
+    ctor public TableInfo.Index(String!, boolean, java.util.List<java.lang.String!>!, java.util.List<java.lang.String!>!);
     field public static final String DEFAULT_PREFIX = "index_";
     field public final java.util.List<java.lang.String!>! columns;
     field public final String! name;
+    field public final java.util.List<java.lang.String!>! orders;
     field public final boolean unique;
   }
 
diff --git a/room/room-runtime/src/androidTest/java/androidx/room/migration/TableInfoTest.java b/room/room-runtime/src/androidTest/java/androidx/room/migration/TableInfoTest.java
index f7eb2f8..9a200b3 100644
--- a/room/room-runtime/src/androidTest/java/androidx/room/migration/TableInfoTest.java
+++ b/room/room-runtime/src/androidTest/java/androidx/room/migration/TableInfoTest.java
@@ -327,11 +327,11 @@
                                 TableInfo.CREATED_FROM_ENTITY)),
                 Collections.<TableInfo.ForeignKey>emptySet(),
                 toSet(new TableInfo.Index("index_foo_blahblah", false,
-                        Arrays.asList("a", "b")),
+                        Arrays.asList("a", "b"), Collections.emptyList()),
                         new TableInfo.Index("foo_unique_indexed", true,
-                                Arrays.asList("unique_indexed")),
+                                Arrays.asList("unique_indexed"), Arrays.asList("DESC")),
                         new TableInfo.Index("foo_indexed", false,
-                                Arrays.asList("indexed"))));
+                                Arrays.asList("indexed"), Arrays.asList("ASC"))));
         assertThat(expectedInfo, is(dbInfo));
     }
 
diff --git a/room/room-runtime/src/main/java/androidx/room/util/TableInfo.java b/room/room-runtime/src/main/java/androidx/room/util/TableInfo.java
index 37b41d6..2d3c8be 100644
--- a/room/room-runtime/src/main/java/androidx/room/util/TableInfo.java
+++ b/room/room-runtime/src/main/java/androidx/room/util/TableInfo.java
@@ -313,11 +313,14 @@
             final int seqnoColumnIndex = cursor.getColumnIndex("seqno");
             final int cidColumnIndex = cursor.getColumnIndex("cid");
             final int nameColumnIndex = cursor.getColumnIndex("name");
-            if (seqnoColumnIndex == -1 || cidColumnIndex == -1 || nameColumnIndex == -1) {
+            final int descColumnIndex = cursor.getColumnIndex("desc");
+            if (seqnoColumnIndex == -1 || cidColumnIndex == -1
+                    || nameColumnIndex == -1 || descColumnIndex == -1) {
                 // we cannot read them so better not validate any index.
                 return null;
             }
-            final TreeMap<Integer, String> results = new TreeMap<>();
+            final TreeMap<Integer, String> columnsMap = new TreeMap<>();
+            final TreeMap<Integer, String> ordersMap = new TreeMap<>();
 
             while (cursor.moveToNext()) {
                 int cid = cursor.getInt(cidColumnIndex);
@@ -327,11 +330,16 @@
                 }
                 int seq = cursor.getInt(seqnoColumnIndex);
                 String columnName = cursor.getString(nameColumnIndex);
-                results.put(seq, columnName);
+                String order = cursor.getInt(descColumnIndex) > 0 ? "DESC" : "ASC";
+
+                columnsMap.put(seq, columnName);
+                ordersMap.put(seq, order);
             }
-            final List<String> columns = new ArrayList<>(results.size());
-            columns.addAll(results.values());
-            return new Index(name, unique, columns);
+            final List<String> columns = new ArrayList<>(columnsMap.size());
+            columns.addAll(columnsMap.values());
+            final List<String> orders = new ArrayList<>(ordersMap.size());
+            orders.addAll(ordersMap.values());
+            return new Index(name, unique, columns, orders);
         } finally {
             cursor.close();
         }
@@ -668,11 +676,22 @@
         public final String name;
         public final boolean unique;
         public final List<String> columns;
+        public final List<String> orders;
 
+        /**
+         * @deprecated Use {@link #Index(String, boolean, List, List)}
+         */
         public Index(String name, boolean unique, List<String> columns) {
+            this(name, unique, columns, null);
+        }
+
+        public Index(String name, boolean unique, List<String> columns, List<String> orders) {
             this.name = name;
             this.unique = unique;
             this.columns = columns;
+            this.orders = orders == null || orders.size() == 0
+                    ? Collections.nCopies(columns.size(), androidx.room.Index.Order.ASC.name())
+                    : orders;
         }
 
         @Override
@@ -687,6 +706,9 @@
             if (!columns.equals(index.columns)) {
                 return false;
             }
+            if (!orders.equals(index.orders)) {
+                return false;
+            }
             if (name.startsWith(Index.DEFAULT_PREFIX)) {
                 return index.name.startsWith(Index.DEFAULT_PREFIX);
             } else {
@@ -704,6 +726,7 @@
             }
             result = 31 * result + (unique ? 1 : 0);
             result = 31 * result + columns.hashCode();
+            result = 31 * result + orders.hashCode();
             return result;
         }
 
@@ -713,6 +736,7 @@
                     + "name='" + name + '\''
                     + ", unique=" + unique
                     + ", columns=" + columns
+                    + ", orders=" + orders
                     + '}';
         }
     }
diff --git a/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.java b/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
index 02c5192..1431498 100644
--- a/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
+++ b/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
@@ -515,7 +515,7 @@
         Set<TableInfo.Index> result = new HashSet<>();
         for (IndexBundle bundle : indices) {
             result.add(new TableInfo.Index(bundle.getName(), bundle.isUnique(),
-                    bundle.getColumnNames()));
+                    bundle.getColumnNames(), bundle.getOrders()));
         }
         return result;
     }