Merge "Switch beginWithUniqueTag to beginWithName." into flatfoot-background
diff --git a/room/common/src/main/java/android/arch/persistence/room/RawQuery.java b/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
index b41feab..46f2193 100644
--- a/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
+++ b/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
@@ -137,8 +137,8 @@
      * Denotes the list of entities which are accessed in the provided query and should be observed
      * for invalidation if the query is observable.
      * <p>
-     * The listed classes should be {@link Entity Entities} that are linked from the containing
-     * {@link Database}.
+     * The listed classes should either be annotated with {@link Entity} or they should reference to
+     * at least 1 Entity (via {@link Embedded} or {@link Relation}).
      * <p>
      * Providing this field in a non-observable query has no impact.
      * <pre>
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
index 313f968..3859028 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
@@ -480,7 +480,7 @@
     }
 
     companion object {
-        private fun extractTableName(element: TypeElement, annotation: AnnotationMirror): String {
+        fun extractTableName(element: TypeElement, annotation: AnnotationMirror): String {
             val annotationValue = AnnotationMirrors
                     .getAnnotationValue(annotation, "tableName").value.toString()
             return if (annotationValue == "") {
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
index 3e7dd9c..9c3fecb 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
@@ -504,4 +504,13 @@
             " String or SupportSQLiteQuery"
 
     val RAW_QUERY_BAD_RETURN_TYPE = "RawQuery methods must return a non-void type."
+
+    fun rawQueryBadEntity(typeName: TypeName): String {
+        return """
+            observedEntities field in RawQuery must either reference a class that is annotated
+            with @Entity or it should reference a Pojo that either contains @Embedded fields that
+            are annotated with @Entity or @Relation fields.
+            $typeName does not have these properties, did you mean another class?
+            """.trim()
+    }
 }
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt
index 6858ccc..b26ff6e 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt
@@ -23,7 +23,6 @@
 import android.arch.persistence.room.ext.toListOfClassTypes
 import android.arch.persistence.room.ext.typeName
 import android.arch.persistence.room.parser.SqlParser
-import android.arch.persistence.room.vo.Entity
 import android.arch.persistence.room.vo.RawQueryMethod
 import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
@@ -50,9 +49,8 @@
         val returnTypeName = TypeName.get(executableType.returnType)
         context.checker.notUnbound(returnTypeName, executableElement,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
-        val observedEntities = processObservedTables()
-        val query = SqlParser.rawQueryForTables(
-                observedEntities.map { it.tableName }.toSet())
+        val observedTableNames = processObservedTables()
+        val query = SqlParser.rawQueryForTables(observedTableNames)
         // build the query but don't calculate result info since we just guessed it.
         val resultBinder = context.typeAdapterStore
                 .findQueryResultBinder(executableType.returnType, query)
@@ -62,7 +60,7 @@
         val rawQueryMethod = RawQueryMethod(
                 element = executableElement,
                 name = executableElement.simpleName.toString(),
-                observedEntities = observedEntities,
+                observedTableNames = observedTableNames,
                 returnType = executableType.returnType,
                 runtimeQueryParam = runtimeQueryParam,
                 inTransaction = inTransaction,
@@ -73,20 +71,40 @@
         return rawQueryMethod
     }
 
-    private fun processObservedTables(): List<Entity> {
+    private fun processObservedTables(): Set<String> {
         val annotation = MoreElements
                 .getAnnotationMirror(executableElement,
                         android.arch.persistence.room.RawQuery::class.java)
-                .orNull() ?: return emptyList()
+                .orNull() ?: return emptySet()
         val entityList = AnnotationMirrors.getAnnotationValue(annotation, "observedEntities")
         return entityList
                 .toListOfClassTypes()
                 .map {
-                    EntityProcessor(
-                            baseContext = context,
-                            element = MoreTypes.asTypeElement(it)
-                    ).process()
+                    MoreTypes.asTypeElement(it)
                 }
+                .flatMap {
+                    if (it.hasAnnotation(android.arch.persistence.room.Entity::class)) {
+                        val entity = EntityProcessor(
+                                baseContext = context,
+                                element = it
+                        ).process()
+                        arrayListOf(entity.tableName)
+                    } else {
+                        val pojo = PojoProcessor(
+                                baseContext = context,
+                                element = it,
+                                bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                                parent = null
+                        ).process()
+                        val tableNames = pojo.accessedTableNames()
+                        // if it is empty, report error as it does not make sense
+                        if (tableNames.isEmpty()) {
+                            context.logger.e(executableElement,
+                                    ProcessorErrors.rawQueryBadEntity(it.asType().typeName()))
+                        }
+                        tableNames
+                    }
+                }.toSet()
     }
 
     private fun findRuntimeQueryParameter(): RawQueryMethod.RuntimeQueryParameter? {
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
index 629c6b8..34ec61c 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
@@ -17,6 +17,8 @@
 package android.arch.persistence.room.vo
 
 import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.processor.EntityProcessor
+import com.google.auto.common.MoreElements
 import com.squareup.javapoet.TypeName
 import javax.lang.model.element.TypeElement
 import javax.lang.model.type.DeclaredType
@@ -24,9 +26,30 @@
 /**
  * A class is turned into a Pojo if it is used in a query response.
  */
-// TODO make data class when move to kotlin 1.1
-open class Pojo(val element: TypeElement, val type: DeclaredType, val fields: List<Field>,
-                val embeddedFields: List<EmbeddedField>, val relations: List<Relation>,
-                val constructor: Constructor? = null) {
+open class Pojo(
+        val element: TypeElement,
+        val type: DeclaredType,
+        val fields: List<Field>,
+        val embeddedFields: List<EmbeddedField>,
+        val relations: List<Relation>,
+        val constructor: Constructor? = null) {
     val typeName: TypeName by lazy { type.typeName() }
+
+    /**
+     * All table names that are somehow accessed by this Pojo.
+     * Might be via Embedded or Relation.
+     */
+    fun accessedTableNames(): List<String> {
+        val entityAnnotation = MoreElements.getAnnotationMirror(element,
+                android.arch.persistence.room.Entity::class.java).orNull()
+        return if (entityAnnotation != null) {
+            listOf(EntityProcessor.extractTableName(element, entityAnnotation))
+        } else {
+            embeddedFields.flatMap {
+                it.pojo.accessedTableNames()
+            } + relations.map {
+                it.entity.tableName
+            }
+        }
+    }
 }
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
index ad1f10d..5099906 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
@@ -33,7 +33,7 @@
         val name: String,
         val returnType: TypeMirror,
         val inTransaction: Boolean,
-        val observedEntities: List<Entity>,
+        val observedTableNames: Set<String>,
         val runtimeQueryParam: RuntimeQueryParameter?,
         val queryResultBinder: QueryResultBinder) {
     val returnsValue by lazy {
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
index 17e8ad9..0a9be4a 100644
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
@@ -97,10 +97,8 @@
                             type = SupportDbTypeNames.QUERY
                     )
             ))
-            assertThat(query.observedEntities.size, `is`(1))
-            assertThat(
-                    query.observedEntities.first().typeName,
-                    `is`(COMMON.USER_TYPE_NAME as TypeName))
+            assertThat(query.observedTableNames.size, `is`(1))
+            assertThat(query.observedTableNames, `is`(setOf("User")))
         }.compilesWithoutError()
     }
 
@@ -118,7 +116,7 @@
                             type = SupportDbTypeNames.QUERY
                     )
             ))
-            assertThat(query.observedEntities, `is`(emptyList()))
+            assertThat(query.observedTableNames, `is`(emptySet()))
         }.failsToCompile()
                 .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
     }
@@ -180,7 +178,7 @@
                     )
             ))
             assertThat(query.returnType.typeName(), `is`(pojo))
-            assertThat(query.observedEntities, `is`(emptyList()))
+            assertThat(query.observedTableNames, `is`(emptySet()))
         }.compilesWithoutError()
     }
 
@@ -232,6 +230,53 @@
         )
     }
 
+    @Test
+    fun observed_notAnEntity() {
+        singleQueryMethod(
+                """
+                @RawQuery(observedEntities = {${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class})
+                abstract public int[] foo(String query);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.rawQueryBadEntity(COMMON.NOT_AN_ENTITY_TYPE_NAME)
+        )
+    }
+
+    @Test
+    fun observed_relationPojo() {
+        singleQueryMethod(
+                """
+                public static class MyPojo {
+                    public String foo;
+                    @Relation(
+                        parentColumn = "foo",
+                        entityColumn = "name"
+                    )
+                    public java.util.List<User> users;
+                }
+                @RawQuery(observedEntities = MyPojo.class)
+                abstract public int[] foo(String query);
+                """) { method, _ ->
+            assertThat(method.observedTableNames, `is`(setOf("User")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun observed_embedded() {
+        singleQueryMethod(
+                """
+                public static class MyPojo {
+                    public String foo;
+                    @Embedded
+                    public User users;
+                }
+                @RawQuery(observedEntities = MyPojo.class)
+                abstract public int[] foo(String query);
+                """) { method, _ ->
+            assertThat(method.observedTableNames, `is`(setOf("User")))
+        }.compilesWithoutError()
+    }
+
     private fun singleQueryMethod(
             vararg input: String,
             handler: (RawQueryMethod, TestInvocation) -> Unit
@@ -242,7 +287,8 @@
                                 + input.joinToString("\n")
                                 + DAO_SUFFIX
                 ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER,
-                        COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE))
+                        COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE,
+                        COMMON.NOT_AN_ENTITY))
                 .processedWith(TestProcessor.builder()
                         .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
                                 Entity::class, PrimaryKey::class, RawQuery::class)
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java
index 0f4a73d..ae09350 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java
@@ -33,28 +33,46 @@
 public interface RawDao {
     @RawQuery
     User getUser(String query);
+
     @RawQuery
     UserAndAllPets getUserAndAllPets(String query);
+
+    @RawQuery(observedEntities = UserAndAllPets.class)
+    LiveData<UserAndAllPets> getUserAndAllPetsObservable(String query);
+
     @RawQuery
     User getUser(SupportSQLiteQuery query);
+
     @RawQuery
     UserAndPet getUserAndPet(String query);
+
     @RawQuery
     NameAndLastName getUserNameAndLastName(String query);
+
     @RawQuery(observedEntities = User.class)
     NameAndLastName getUserNameAndLastName(SupportSQLiteQuery query);
+
     @RawQuery
     int count(String query);
+
     @RawQuery
     List<User> getUserList(String query);
+
     @RawQuery
     List<UserAndPet> getUserAndPetList(String query);
+
+    @RawQuery(observedEntities = UserAndPet.class)
+    LiveData<List<UserAndPet>> getUserAndPetListObservable(String query);
+
     @RawQuery(observedEntities = User.class)
     LiveData<User> getUserLiveData(String query);
+
     @RawQuery(observedEntities = User.class)
     LiveData<User> getUserLiveData(SupportSQLiteQuery query);
+
     @RawQuery
     UserNameAndBirthday getUserAndBirthday(String query);
+
     class UserNameAndBirthday {
         @ColumnInfo(name = "mName")
         public final String name;
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
index 310eb5c..0957901 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
@@ -20,6 +20,8 @@
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import static java.util.Collections.emptyList;
+
 import android.arch.core.executor.testing.CountingTaskExecutorRule;
 import android.arch.lifecycle.LiveData;
 import android.arch.persistence.db.SimpleSQLiteQuery;
@@ -30,6 +32,7 @@
 import android.arch.persistence.room.integration.testapp.vo.User;
 import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
 import android.arch.persistence.room.integration.testapp.vo.UserAndPet;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -204,6 +207,71 @@
         assertThat(count, is(4));
     }
 
+    @Test
+    public void embedded_liveData() throws TimeoutException, InterruptedException {
+        LiveData<List<UserAndPet>> liveData = mRawDao.getUserAndPetListObservable(
+                "SELECT * FROM User LEFT JOIN Pet ON (User.mId = Pet.mUserId)"
+                        + " ORDER BY mId ASC, mPetId ASC");
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> liveData.observeForever(user -> {
+                })
+        );
+        drain();
+        assertThat(liveData.getValue(), is(emptyList()));
+
+        User[] users = TestUtil.createUsersArray(3, 5);
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+        mUserDao.insertAll(users);
+        drain();
+        List<UserAndPet> justUsers = liveData.getValue();
+        //noinspection ConstantConditions
+        assertThat(justUsers.size(), is(2));
+        assertThat(justUsers.get(0).getUser(), is(users[0]));
+        assertThat(justUsers.get(1).getUser(), is(users[1]));
+        assertThat(justUsers.get(0).getPet(), is(nullValue()));
+        assertThat(justUsers.get(1).getPet(), is(nullValue()));
+
+        mPetDao.insertAll(pets);
+        drain();
+        List<UserAndPet> allItems = liveData.getValue();
+        //noinspection ConstantConditions
+        assertThat(allItems.size(), is(3));
+        // row 0
+        assertThat(allItems.get(0).getUser(), is(users[0]));
+        assertThat(allItems.get(0).getPet(), is(pets[0]));
+        // row 1
+        assertThat(allItems.get(1).getUser(), is(users[0]));
+        assertThat(allItems.get(1).getPet(), is(pets[1]));
+        // row 2
+        assertThat(allItems.get(2).getUser(), is(users[1]));
+        assertThat(allItems.get(2).getPet(), is(nullValue()));
+
+        mDatabase.clearAllTables();
+        drain();
+        assertThat(liveData.getValue(), is(emptyList()));
+    }
+
+    @Test
+    public void relation_liveData() throws TimeoutException, InterruptedException {
+        LiveData<UserAndAllPets> liveData = mRawDao
+                .getUserAndAllPetsObservable("SELECT * FROM User WHERE mId = 3");
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> liveData.observeForever(user -> {
+                })
+        );
+        drain();
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        assertThat(liveData.getValue().user, is(user));
+        assertThat(liveData.getValue().pets, is(emptyList()));
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 5);
+        mPetDao.insertAll(pets);
+        drain();
+        assertThat(liveData.getValue().user, is(user));
+        assertThat(liveData.getValue().pets, is(Arrays.asList(pets)));
+    }
+
     private void drain() throws TimeoutException, InterruptedException {
         mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
     }
diff --git a/work/integration-tests/testapp/build.gradle b/work/integration-tests/testapp/build.gradle
index 89c87f6..ab5b162 100644
--- a/work/integration-tests/testapp/build.gradle
+++ b/work/integration-tests/testapp/build.gradle
@@ -22,6 +22,15 @@
 
 project.ext.noDocs = true
 
+android {
+    buildTypes {
+        getByName("release") {
+            minifyEnabled = true
+            proguardFiles("proguard-rules.pro")
+        }
+    }
+}
+
 dependencies {
     implementation 'com.android.support.constraint:constraint-layout:1.0.2'
     compile project(':arch:runtime')
diff --git a/work/workmanager/proguard-rules.pro b/work/workmanager/proguard-rules.pro
index 19a807d..202a02c 100644
--- a/work/workmanager/proguard-rules.pro
+++ b/work/workmanager/proguard-rules.pro
@@ -3,3 +3,6 @@
  public void debug(...);
  public void info(...);
 }
+
+-keep class * extends androidx.work.Worker
+-keep class * extends androidx.work.InputMerger
diff --git a/work/workmanager/src/main/java/androidx/work/State.java b/work/workmanager/src/main/java/androidx/work/State.java
index 8390df4..a654456 100644
--- a/work/workmanager/src/main/java/androidx/work/State.java
+++ b/work/workmanager/src/main/java/androidx/work/State.java
@@ -50,5 +50,14 @@
     /**
      * The status for work that has been cancelled and will not execute
      */
-    CANCELLED
+    CANCELLED;
+
+    /**
+     * Returns {@code true} if this State is considered finished.
+     *
+     * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and {@link #CANCELLED} States
+     */
+    public boolean isFinished() {
+        return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
+    }
 }
diff --git a/work/workmanager/src/main/java/androidx/work/Worker.java b/work/workmanager/src/main/java/androidx/work/Worker.java
index 8dcb8be..cac7984 100644
--- a/work/workmanager/src/main/java/androidx/work/Worker.java
+++ b/work/workmanager/src/main/java/androidx/work/Worker.java
@@ -17,6 +17,7 @@
 package androidx.work;
 
 import android.content.Context;
+import android.support.annotation.Keep;
 import android.support.annotation.NonNull;
 import android.support.annotation.WorkerThread;
 
@@ -90,6 +91,7 @@
         return mOutput;
     }
 
+    @Keep
     private void internalInit(
             Context appContext,
             @NonNull String id,