Merge "Add DiffUtil.ItemCallback" into oc-mr1-jetpack-dev
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index f09c777..a81a2a9 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -127,7 +127,7 @@
def configureBuildOnServer() {
def buildOnServerTask = rootProject.tasks.create("buildOnServer")
rootProject.tasks.whenTaskAdded { task ->
- if ("createArchive".equals(task.name)) {
+ if ("createArchive".equals(task.name) || "distDocs".equals(task.name)) {
buildOnServerTask.dependsOn task
}
}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/ClassesInfoCache.java b/lifecycle/common/src/main/java/android/arch/lifecycle/ClassesInfoCache.java
index f077dae..d88e276 100644
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/ClassesInfoCache.java
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/ClassesInfoCache.java
@@ -46,7 +46,7 @@
return mHasLifecycleMethods.get(klass);
}
- Method[] methods = klass.getDeclaredMethods();
+ Method[] methods = getDeclaredMethods(klass);
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation != null) {
@@ -64,6 +64,18 @@
return false;
}
+ private Method[] getDeclaredMethods(Class klass) {
+ try {
+ return klass.getDeclaredMethods();
+ } catch (NoClassDefFoundError e) {
+ throw new IllegalArgumentException("The observer class has some methods that use "
+ + "newer APIs which are not available in the current OS version. Lifecycles "
+ + "cannot access even other methods so you should make sure that your "
+ + "observer classes only access framework classes that are available "
+ + "in your min API level OR use lifecycle:compiler annotation processor.", e);
+ }
+ }
+
CallbackInfo getInfo(Class klass) {
CallbackInfo existing = mCallbackMap.get(klass);
if (existing != null) {
@@ -106,7 +118,7 @@
}
}
- Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
+ Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
boolean hasLifecycleMethods = false;
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/MediatorLiveData.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/MediatorLiveData.java
index 672b3a3..718c79e 100644
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/MediatorLiveData.java
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/MediatorLiveData.java
@@ -24,11 +24,43 @@
import java.util.Map;
/**
- * {@link LiveData} subclass which may observer other {@code LiveData} objects and react on
+ * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
* {@code OnChanged} events from them.
* <p>
* This class correctly propagates its active/inactive states down to source {@code LiveData}
* objects.
+ * <p>
+ * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
+ * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
+ * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
+ * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
+ * is called for either of them, we set a new value in {@code liveDataMerger}.
+ *
+ * <pre>
+ * LiveData<Integer> liveData1 = ...;
+ * LiveData<Integer> liveData2 = ...;
+ *
+ * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
+ * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
+ * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
+ * </pre>
+ * <p>
+ * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
+ * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
+ * liveData1} and remove it as a source.
+ * <pre>
+ * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
+ * private int count = 1;
+ *
+ * {@literal @}Override public void onChanged(@Nullable Integer s) {
+ * count++;
+ * liveDataMerger.setValue(s);
+ * if (count > 10) {
+ * liveDataMerger.removeSource(liveData1);
+ * }
+ * }
+ * });
+ * </pre>
*
* @param <T> The type of data hold by this instance
*/
diff --git a/lifecycle/runtime/build.gradle b/lifecycle/runtime/build.gradle
index 7678e95..111429c 100644
--- a/lifecycle/runtime/build.gradle
+++ b/lifecycle/runtime/build.gradle
@@ -32,6 +32,9 @@
testCompile libs.junit
testCompile libs.mockito_core
+
+ androidTestImplementation libs.junit
+ androidTestImplementation libs.test_runner, { exclude module: 'support-annotations' }
}
createAndroidCheckstyle(project)
diff --git a/lifecycle/runtime/src/androidTest/java/android/arch/lifecycle/MissingClassTest.java b/lifecycle/runtime/src/androidTest/java/android/arch/lifecycle/MissingClassTest.java
new file mode 100644
index 0000000..81a0756
--- /dev/null
+++ b/lifecycle/runtime/src/androidTest/java/android/arch/lifecycle/MissingClassTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.arch.lifecycle;
+
+import android.app.PictureInPictureParams;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+@SmallTest
+public class MissingClassTest {
+ public static class ObserverWithMissingClasses {
+ @SuppressWarnings("unused")
+ public void newApiMethod(PictureInPictureParams params) {}
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {}
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMissingApi() {
+ new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses());
+ }
+}
diff --git a/media-compat/Android.mk b/media-compat/Android.mk
index 02ed90d..4a4cd02 100644
--- a/media-compat/Android.mk
+++ b/media-compat/Android.mk
@@ -28,7 +28,6 @@
LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/java
LOCAL_SRC_FILES := \
- $(call all-java-files-under,kitkat) \
$(call all-java-files-under,api21) \
$(call all-java-files-under,api22) \
$(call all-java-files-under,api23) \
diff --git a/media-compat/build.gradle b/media-compat/build.gradle
index 2ea2483..c504996 100644
--- a/media-compat/build.gradle
+++ b/media-compat/build.gradle
@@ -18,8 +18,6 @@
sourceSets {
main.java.srcDirs = [
- 'jellybean-mr2',
- 'kitkat',
'api21',
'api22',
'api23',
diff --git a/media-compat/java/android/support/v4/media/RatingCompat.java b/media-compat/java/android/support/v4/media/RatingCompat.java
index b538cac..e70243f 100644
--- a/media-compat/java/android/support/v4/media/RatingCompat.java
+++ b/media-compat/java/android/support/v4/media/RatingCompat.java
@@ -18,6 +18,7 @@
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.media.Rating;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -326,25 +327,25 @@
*/
public static RatingCompat fromRating(Object ratingObj) {
if (ratingObj != null && Build.VERSION.SDK_INT >= 19) {
- final int ratingStyle = RatingCompatKitkat.getRatingStyle(ratingObj);
+ final int ratingStyle = ((Rating) ratingObj).getRatingStyle();
final RatingCompat rating;
- if (RatingCompatKitkat.isRated(ratingObj)) {
+ if (((Rating) ratingObj).isRated()) {
switch (ratingStyle) {
case RATING_HEART:
- rating = newHeartRating(RatingCompatKitkat.hasHeart(ratingObj));
+ rating = newHeartRating(((Rating) ratingObj).hasHeart());
break;
case RATING_THUMB_UP_DOWN:
- rating = newThumbRating(RatingCompatKitkat.isThumbUp(ratingObj));
+ rating = newThumbRating(((Rating) ratingObj).isThumbUp());
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
rating = newStarRating(ratingStyle,
- RatingCompatKitkat.getStarRating(ratingObj));
+ ((Rating) ratingObj).getStarRating());
break;
case RATING_PERCENTAGE:
rating = newPercentageRating(
- RatingCompatKitkat.getPercentRating(ratingObj));
+ ((Rating) ratingObj).getPercentRating());
break;
default:
return null;
@@ -372,25 +373,25 @@
if (isRated()) {
switch (mRatingStyle) {
case RATING_HEART:
- mRatingObj = RatingCompatKitkat.newHeartRating(hasHeart());
+ mRatingObj = Rating.newHeartRating(hasHeart());
break;
case RATING_THUMB_UP_DOWN:
- mRatingObj = RatingCompatKitkat.newThumbRating(isThumbUp());
+ mRatingObj = Rating.newThumbRating(isThumbUp());
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
- mRatingObj = RatingCompatKitkat.newStarRating(mRatingStyle,
+ mRatingObj = Rating.newStarRating(mRatingStyle,
getStarRating());
break;
case RATING_PERCENTAGE:
- mRatingObj = RatingCompatKitkat.newPercentageRating(getPercentRating());
+ mRatingObj = Rating.newPercentageRating(getPercentRating());
break;
default:
return null;
}
} else {
- mRatingObj = RatingCompatKitkat.newUnratedRating(mRatingStyle);
+ mRatingObj = Rating.newUnratedRating(mRatingStyle);
}
}
return mRatingObj;
diff --git a/media-compat/kitkat/android/support/v4/media/RatingCompatKitkat.java b/media-compat/kitkat/android/support/v4/media/RatingCompatKitkat.java
deleted file mode 100644
index 1d3fa50..0000000
--- a/media-compat/kitkat/android/support/v4/media/RatingCompatKitkat.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.support.v4.media;
-
-import android.media.Rating;
-import android.support.annotation.RequiresApi;
-
-@RequiresApi(19)
-class RatingCompatKitkat {
- public static Object newUnratedRating(int ratingStyle) {
- return Rating.newUnratedRating(ratingStyle);
- }
-
- public static Object newHeartRating(boolean hasHeart) {
- return Rating.newHeartRating(hasHeart);
- }
-
- public static Object newThumbRating(boolean thumbIsUp) {
- return Rating.newThumbRating(thumbIsUp);
- }
-
- public static Object newStarRating(int starRatingStyle, float starRating) {
- return Rating.newStarRating(starRatingStyle, starRating);
- }
-
- public static Object newPercentageRating(float percent) {
- return Rating.newPercentageRating(percent);
- }
-
- public static boolean isRated(Object ratingObj) {
- return ((Rating)ratingObj).isRated();
- }
-
- public static int getRatingStyle(Object ratingObj) {
- return ((Rating)ratingObj).getRatingStyle();
- }
-
- public static boolean hasHeart(Object ratingObj) {
- return ((Rating)ratingObj).hasHeart();
- }
-
- public static boolean isThumbUp(Object ratingObj) {
- return ((Rating)ratingObj).isThumbUp();
- }
-
- public static float getStarRating(Object ratingObj) {
- return ((Rating)ratingObj).getStarRating();
- }
-
- public static float getPercentRating(Object ratingObj) {
- return ((Rating)ratingObj).getPercentRating();
- }
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java b/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java
index c64be96..f05e6be 100644
--- a/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java
+++ b/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java
@@ -125,4 +125,12 @@
* annotation.
*/
public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+
+ /**
+ * Reported when a @Query method returns a Pojo that has relations but the method is not
+ * annotated with @Transaction. Relations are run as separate queries and if the query is not
+ * run inside a transaction, it might return inconsistent results from the database.
+ */
+ public static final String RELATION_QUERY_WITHOUT_TRANSACTION =
+ "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Transaction.java b/room/common/src/main/java/android/arch/persistence/room/Transaction.java
index 914e4f4..3b6ede9 100644
--- a/room/common/src/main/java/android/arch/persistence/room/Transaction.java
+++ b/room/common/src/main/java/android/arch/persistence/room/Transaction.java
@@ -22,9 +22,10 @@
import java.lang.annotation.Target;
/**
- * Marks a method in an abstract {@link Dao} class as a transaction method.
+ * Marks a method in a {@link Dao} class as a transaction method.
* <p>
- * The derived implementation of the method will execute the super method in a database transaction.
+ * When used on a non-abstract method of an abstract {@link Dao} class,
+ * the derived implementation of the method will execute the super method in a database transaction.
* All the parameters and return types are preserved. The transaction will be marked as successful
* unless an exception is thrown in the method body.
* <p>
@@ -44,6 +45,38 @@
* }
* }
* </pre>
+ * <p>
+ * When used on a {@link Query} method that has a {@code Select} statement, the generated code for
+ * the Query will be run in a transaction. There are 2 main cases where you may want to do that:
+ * <ol>
+ * <li>If the result of the query is fairly big, it is better to run it inside a transaction
+ * to receive a consistent result. Otherwise, if the query result does not fit into a single
+ * {@link android.database.CursorWindow CursorWindow}, the query result may be corrupted due to
+ * changes in the database in between cursor window swaps.
+ * <li>If the result of the query is a Pojo with {@link Relation} fields, these fields are
+ * queried separately. To receive consistent results between these queries, you probably want
+ * to run them in a single transaction.
+ * </ol>
+ * Example:
+ * <pre>
+ * class ProductWithReviews extends Product {
+ * {@literal @}Relation(parentColumn = "id", entityColumn = "productId", entity = Review.class)
+ * public List<Review> reviews;
+ * }
+ * {@literal @}Dao
+ * public interface ProductDao {
+ * {@literal @}Transaction {@literal @}Query("SELECT * from products")
+ * public List<ProductWithReviews> loadAll();
+ * }
+ * </pre>
+ * If the query is an async query (e.g. returns a {@link android.arch.lifecycle.LiveData LiveData}
+ * or RxJava Flowable, the transaction is properly handled when the query is run, not when the
+ * method is called.
+ * <p>
+ * Putting this annotation on an {@link Insert}, {@link Update} or {@link Delete} method has no
+ * impact because they are always run inside a transaction. Similarly, if it is annotated with
+ * {@link Query} but runs an update or delete statement, it is automatically wrapped in a
+ * transaction.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt
index 2093d91..c705eef 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt
@@ -110,9 +110,10 @@
executableElement = it).process()
} ?: emptyList()
- val transactionMethods = allMembers.filter {
- it.hasAnnotation(Transaction::class)
- && it.kind == ElementKind.METHOD
+ val transactionMethods = allMembers.filter { member ->
+ member.hasAnnotation(Transaction::class)
+ && member.kind == ElementKind.METHOD
+ && PROCESSED_ANNOTATIONS.none { member.hasAnnotation(it) }
// TODO: Exclude abstract methods and let @Query handle that case
}.map {
TransactionMethodProcessor(
@@ -161,5 +162,4 @@
element.toString(), dbType.toString()))
}
}
-
}
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 be26297..4752478 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
@@ -37,6 +37,8 @@
val INVALID_ON_CONFLICT_VALUE = "On conflict value must be one of @OnConflictStrategy values."
val INVALID_INSERTION_METHOD_RETURN_TYPE = "Methods annotated with @Insert can return either" +
" void, long, Long, long[], Long[] or List<Long>."
+ val TRANSACTION_REFERENCE_DOCS = "https://developer.android.com/reference/android/arch/" +
+ "persistence/room/Transaction.html"
fun insertionMethodReturnTypeMismatch(definedReturn : TypeName,
expectedReturnTypes : List<TypeName>) : String {
@@ -122,7 +124,13 @@
" @Update but does not have any parameters to update."
val TRANSACTION_METHOD_MODIFIERS = "Method annotated with @Transaction must not be " +
- "private, final, or abstract."
+ "private, final, or abstract. It can be abstract only if the method is also" +
+ " annotated with @Query."
+
+ val TRANSACTION_MISSING_ON_RELATION = "The return value includes a Pojo with a @Relation." +
+ " It is usually desired to annotate this method with @Transaction to avoid" +
+ " possibility of inconsistent results between the Pojo and its relations. See " +
+ TRANSACTION_REFERENCE_DOCS + " for details."
val CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER = "Type of the parameter must be a class " +
"annotated with @Entity or a collection/array of it."
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt
index fc60074..ebd586a 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt
@@ -18,15 +18,18 @@
import android.arch.persistence.room.Query
import android.arch.persistence.room.SkipQueryVerification
+import android.arch.persistence.room.Transaction
import android.arch.persistence.room.ext.hasAnnotation
import android.arch.persistence.room.parser.ParsedQuery
import android.arch.persistence.room.parser.QueryType
import android.arch.persistence.room.parser.SqlParser
import android.arch.persistence.room.solver.query.result.LiveDataQueryResultBinder
+import android.arch.persistence.room.solver.query.result.PojoRowAdapter
import android.arch.persistence.room.verifier.DatabaseVerificaitonErrors
import android.arch.persistence.room.verifier.DatabaseVerifier
import android.arch.persistence.room.vo.QueryMethod
import android.arch.persistence.room.vo.QueryParameter
+import android.arch.persistence.room.vo.Warning
import com.google.auto.common.AnnotationMirrors
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
@@ -91,6 +94,22 @@
ProcessorErrors.LIVE_DATA_QUERY_WITHOUT_SELECT)
}
+ val inTransaction = when (query.type) {
+ QueryType.SELECT -> executableElement.hasAnnotation(Transaction::class)
+ else -> true
+ }
+
+ if (query.type == QueryType.SELECT && !inTransaction) {
+ // put a warning if it is has relations and not annotated w/ transaction
+ resultBinder.adapter?.rowAdapter?.let { rowAdapter ->
+ if (rowAdapter is PojoRowAdapter
+ && rowAdapter.relationCollectors.isNotEmpty()) {
+ context.logger.w(Warning.RELATION_QUERY_WITHOUT_TRANSACTION,
+ executableElement, ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
+ }
+ }
+ }
+
val queryMethod = QueryMethod(
element = executableElement,
query = query,
@@ -101,6 +120,7 @@
baseContext = context,
containing = containing,
element = it).process() },
+ inTransaction = inTransaction,
queryResultBinder = resultBinder)
val missing = queryMethod.sectionToParamMapping
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessor.kt
index f861ebe..256304a 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessor.kt
@@ -20,9 +20,9 @@
import android.arch.persistence.room.vo.TransactionMethod
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier.ABSTRACT
-import javax.lang.model.type.DeclaredType
import javax.lang.model.element.Modifier.FINAL
import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.type.DeclaredType
class TransactionMethodProcessor(baseContext: Context,
val containing: DeclaredType,
@@ -31,7 +31,6 @@
val context = baseContext.fork(executableElement)
fun process(): TransactionMethod {
- // TODO: Remove abstract check
context.checker.check(!executableElement.hasAnyOf(PRIVATE, FINAL, ABSTRACT),
executableElement, ProcessorErrors.TRANSACTION_METHOD_MODIFIERS)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
index 84c71eb..0bd3fb1 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
@@ -38,8 +38,7 @@
override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
val typeArg = declared.typeArguments.first()
val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query)
- return RxCallableQueryResultBinder(rxType,
- typeArg, InstantQueryResultBinder(adapter), adapter)
+ return RxCallableQueryResultBinder(rxType, typeArg, adapter)
}
override fun matches(declared: DeclaredType): Boolean =
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt
index 7872c33..883180c 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt
@@ -22,6 +22,7 @@
import android.arch.persistence.room.ext.T
import android.arch.persistence.room.solver.CodeGenScope
import android.arch.persistence.room.writer.DaoWriter
+import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.MethodSpec
import javax.lang.model.element.Modifier
@@ -42,9 +43,17 @@
protected fun createRunQueryAndReturnStatements(builder: MethodSpec.Builder,
roomSQLiteQueryVar: String,
+ dbField: FieldSpec,
+ inTransaction: Boolean,
scope: CodeGenScope) {
+ val transactionWrapper = if (inTransaction) {
+ builder.transactionWrapper(dbField)
+ } else {
+ null
+ }
val outVar = scope.getTmpVar("_result")
val cursorVar = scope.getTmpVar("_cursor")
+ transactionWrapper?.beginTransactionWithControlFlow()
builder.apply {
addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
DaoWriter.dbField, roomSQLiteQueryVar)
@@ -52,6 +61,7 @@
val adapterScope = scope.fork()
adapter?.convert(outVar, cursorVar, adapterScope)
addCode(adapterScope.builder().build())
+ transactionWrapper?.commitTransaction()
addStatement("return $L", outVar)
}
nextControlFlow("finally").apply {
@@ -59,5 +69,6 @@
}
endControlFlow()
}
+ transactionWrapper?.endTransactionWithControlFlow()
}
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt
index cd8ea74..06ec339 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt
@@ -16,19 +16,34 @@
package android.arch.persistence.room.solver.query.result
+import android.arch.persistence.room.ext.AndroidTypeNames
import android.arch.persistence.room.ext.L
import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.T
import android.arch.persistence.room.solver.CodeGenScope
import android.arch.persistence.room.writer.DaoWriter
import com.squareup.javapoet.FieldSpec
class CursorQueryResultBinder : QueryResultBinder(NO_OP_RESULT_ADAPTER) {
- override fun convertAndReturn(roomSQLiteQueryVar: String, dbField: FieldSpec,
+ override fun convertAndReturn(roomSQLiteQueryVar: String,
+ dbField: FieldSpec,
+ inTransaction : Boolean,
scope: CodeGenScope) {
- scope.builder().apply {
- addStatement("return $N.query($L)", DaoWriter.dbField, roomSQLiteQueryVar)
+ val builder = scope.builder()
+ val transactionWrapper = if (inTransaction) {
+ builder.transactionWrapper(dbField)
+ } else {
+ null
}
+ transactionWrapper?.beginTransactionWithControlFlow()
+ val resultName = scope.getTmpVar("_tmpResult")
+ builder.addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, resultName,
+ dbField, roomSQLiteQueryVar)
+ transactionWrapper?.commitTransaction()
+ builder.addStatement("return $L", resultName)
+ transactionWrapper?.endTransactionWithControlFlow()
}
+
companion object {
private val NO_OP_RESULT_ADAPTER = object : QueryResultAdapter(null) {
override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt
index 5c8e3e7..e1a75b5 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt
@@ -37,7 +37,9 @@
class FlowableQueryResultBinder(val typeArg: TypeMirror, val queryTableNames: Set<String>,
adapter: QueryResultAdapter?)
: BaseObservableQueryResultBinder(adapter) {
- override fun convertAndReturn(roomSQLiteQueryVar: String, dbField: FieldSpec,
+ override fun convertAndReturn(roomSQLiteQueryVar: String,
+ dbField: FieldSpec,
+ inTransaction : Boolean,
scope: CodeGenScope) {
val callableImpl = TypeSpec.anonymousClassBuilder("").apply {
val typeName = typeArg.typeName()
@@ -47,7 +49,11 @@
returns(typeName)
addException(Exception::class.typeName())
addModifiers(Modifier.PUBLIC)
- createRunQueryAndReturnStatements(this, roomSQLiteQueryVar, scope)
+ createRunQueryAndReturnStatements(builder = this,
+ roomSQLiteQueryVar = roomSQLiteQueryVar,
+ inTransaction = inTransaction,
+ dbField = dbField,
+ scope = scope)
}.build())
addMethod(createFinalizeMethod(roomSQLiteQueryVar))
}.build()
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt
index ff15252..c18bf2d 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt
@@ -27,8 +27,16 @@
* Instantly runs and returns the query.
*/
class InstantQueryResultBinder(adapter: QueryResultAdapter?) : QueryResultBinder(adapter) {
- override fun convertAndReturn(roomSQLiteQueryVar : String, dbField: FieldSpec,
+ override fun convertAndReturn(roomSQLiteQueryVar : String,
+ dbField: FieldSpec,
+ inTransaction : Boolean,
scope: CodeGenScope) {
+ val transactionWrapper = if (inTransaction) {
+ scope.builder().transactionWrapper(dbField)
+ } else {
+ null
+ }
+ transactionWrapper?.beginTransactionWithControlFlow()
scope.builder().apply {
val outVar = scope.getTmpVar("_result")
val cursorVar = scope.getTmpVar("_cursor")
@@ -36,6 +44,7 @@
DaoWriter.dbField, roomSQLiteQueryVar)
beginControlFlow("try").apply {
adapter?.convert(outVar, cursorVar, scope)
+ transactionWrapper?.commitTransaction()
addStatement("return $L", outVar)
}
nextControlFlow("finally").apply {
@@ -44,5 +53,6 @@
}
endControlFlow()
}
+ transactionWrapper?.endTransactionWithControlFlow()
}
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt
index a385a94..0ef8a93 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -40,7 +40,9 @@
adapter: QueryResultAdapter?)
: BaseObservableQueryResultBinder(adapter) {
@Suppress("JoinDeclarationAndAssignment")
- override fun convertAndReturn(roomSQLiteQueryVar : String, dbField: FieldSpec,
+ override fun convertAndReturn(roomSQLiteQueryVar : String,
+ dbField: FieldSpec,
+ inTransaction : Boolean,
scope: CodeGenScope) {
val typeName = typeArg.typeName()
@@ -55,6 +57,7 @@
typeName = typeName,
roomSQLiteQueryVar = roomSQLiteQueryVar,
dbField = dbField,
+ inTransaction = inTransaction,
scope = scope
))
addMethod(createFinalizeMethod(roomSQLiteQueryVar))
@@ -66,6 +69,7 @@
private fun createComputeMethod(roomSQLiteQueryVar: String, typeName: TypeName,
observerField: FieldSpec, dbField: FieldSpec,
+ inTransaction: Boolean,
scope: CodeGenScope): MethodSpec {
return MethodSpec.methodBuilder("compute").apply {
addAnnotation(Override::class.java)
@@ -79,7 +83,11 @@
}
endControlFlow()
- createRunQueryAndReturnStatements(this, roomSQLiteQueryVar, scope)
+ createRunQueryAndReturnStatements(builder = this,
+ roomSQLiteQueryVar = roomSQLiteQueryVar,
+ dbField = dbField,
+ inTransaction = inTransaction,
+ scope = scope)
}.build()
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LivePagedListQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LivePagedListQueryResultBinder.kt
index 39ea32b..c10f9fb 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LivePagedListQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LivePagedListQueryResultBinder.kt
@@ -31,14 +31,20 @@
: QueryResultBinder(tiledDataSourceQueryResultBinder.listAdapter) {
@Suppress("HasPlatformType")
val typeName = tiledDataSourceQueryResultBinder.itemTypeName
- override fun convertAndReturn(roomSQLiteQueryVar: String, dbField: FieldSpec,
+ override fun convertAndReturn(roomSQLiteQueryVar: String,
+ dbField: FieldSpec,
+ inTransaction : Boolean,
scope: CodeGenScope) {
scope.builder().apply {
val pagedListProvider = TypeSpec
.anonymousClassBuilder("").apply {
superclass(ParameterizedTypeName.get(PagingTypeNames.LIVE_PAGED_LIST_PROVIDER,
Integer::class.typeName(), typeName))
- addMethod(createCreateDataSourceMethod(roomSQLiteQueryVar, dbField, scope))
+ addMethod(createCreateDataSourceMethod(
+ roomSQLiteQueryVar = roomSQLiteQueryVar,
+ dbField = dbField,
+ inTransaction = inTransaction,
+ scope = scope))
}.build()
addStatement("return $L", pagedListProvider)
}
@@ -46,14 +52,18 @@
private fun createCreateDataSourceMethod(roomSQLiteQueryVar: String,
dbField: FieldSpec,
+ inTransaction : Boolean,
scope: CodeGenScope): MethodSpec
= MethodSpec.methodBuilder("createDataSource").apply {
addAnnotation(Override::class.java)
addModifiers(Modifier.PROTECTED)
returns(tiledDataSourceQueryResultBinder.typeName)
val countedBinderScope = scope.fork()
- tiledDataSourceQueryResultBinder.convertAndReturn(roomSQLiteQueryVar, dbField,
- countedBinderScope)
+ tiledDataSourceQueryResultBinder.convertAndReturn(
+ roomSQLiteQueryVar = roomSQLiteQueryVar,
+ dbField = dbField,
+ inTransaction = inTransaction,
+ scope = countedBinderScope)
addCode(countedBinderScope.builder().build())
}.build()
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt
index 205bd88..652de46 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt
@@ -32,5 +32,7 @@
* and returns the result.
*/
abstract fun convertAndReturn(roomSQLiteQueryVar: String,
- dbField: FieldSpec, scope: CodeGenScope)
+ dbField: FieldSpec,
+ inTransaction : Boolean,
+ scope: CodeGenScope)
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RxCallableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RxCallableQueryResultBinder.kt
index da960f6..1ab91e8 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RxCallableQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RxCallableQueryResultBinder.kt
@@ -38,28 +38,42 @@
*/
class RxCallableQueryResultBinder(val rxType: RxType,
val typeArg: TypeMirror,
- val instantBinder : InstantQueryResultBinder,
- adapter: QueryResultAdapter?) : QueryResultBinder(adapter) {
- override fun convertAndReturn(roomSQLiteQueryVar: String, dbField: FieldSpec,
+ adapter: QueryResultAdapter?)
+ : QueryResultBinder(adapter) {
+ override fun convertAndReturn(roomSQLiteQueryVar: String,
+ dbField: FieldSpec,
+ inTransaction : Boolean,
scope: CodeGenScope) {
val callable = TypeSpec.anonymousClassBuilder("").apply {
val typeName = typeArg.typeName()
superclass(ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
typeName))
- addMethod(createCallMethod(roomSQLiteQueryVar, dbField, scope))
+ addMethod(createCallMethod(
+ roomSQLiteQueryVar = roomSQLiteQueryVar,
+ dbField = dbField,
+ inTransaction = inTransaction,
+ scope = scope))
}.build()
scope.builder().apply {
addStatement("return $T.fromCallable($L)", rxType.className, callable)
}
}
- fun createCallMethod(roomSQLiteQueryVar: String, dbField: FieldSpec,
+ fun createCallMethod(roomSQLiteQueryVar: String,
+ dbField: FieldSpec,
+ inTransaction: Boolean,
scope: CodeGenScope): MethodSpec {
val adapterScope = scope.fork()
return MethodSpec.methodBuilder("call").apply {
returns(typeArg.typeName())
addException(Exception::class.typeName())
addModifiers(Modifier.PUBLIC)
+ val transactionWrapper = if (inTransaction) {
+ transactionWrapper(dbField)
+ } else {
+ null
+ }
+ transactionWrapper?.beginTransactionWithControlFlow()
val outVar = scope.getTmpVar("_result")
val cursorVar = scope.getTmpVar("_cursor")
addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
@@ -76,6 +90,7 @@
}
endControlFlow()
}
+ transactionWrapper?.commitTransaction()
addStatement("return $L", outVar)
}
nextControlFlow("finally").apply {
@@ -83,10 +98,11 @@
addStatement("$L.release()", roomSQLiteQueryVar)
}
endControlFlow()
+ transactionWrapper?.endTransactionWithControlFlow()
}.build()
}
- enum class RxType(val className : ClassName, val canBeNull : Boolean) {
+ enum class RxType(val className: ClassName, val canBeNull: Boolean) {
SINGLE(RxJava2TypeNames.SINGLE, canBeNull = false),
MAYBE(RxJava2TypeNames.MAYBE, canBeNull = true);
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TiledDataSourceQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TiledDataSourceQueryResultBinder.kt
index 1b4b3fe..2281cfb 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TiledDataSourceQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TiledDataSourceQueryResultBinder.kt
@@ -37,11 +37,13 @@
val itemTypeName : TypeName = listAdapter?.rowAdapter?.out?.typeName() ?: TypeName.OBJECT
val typeName : ParameterizedTypeName = ParameterizedTypeName.get(
RoomTypeNames.LIMIT_OFFSET_DATA_SOURCE, itemTypeName)
- override fun convertAndReturn(roomSQLiteQueryVar: String, dbField: FieldSpec,
+ override fun convertAndReturn(roomSQLiteQueryVar: String,
+ dbField: FieldSpec,
+ inTransaction : Boolean,
scope: CodeGenScope) {
val tableNamesList = tableNames.joinToString(",") { "\"$it\"" }
- val spec = TypeSpec.anonymousClassBuilder("$N, $L, $L",
- dbField, roomSQLiteQueryVar, tableNamesList).apply {
+ val spec = TypeSpec.anonymousClassBuilder("$N, $L, $L, $L",
+ dbField, roomSQLiteQueryVar, inTransaction, tableNamesList).apply {
superclass(typeName)
addMethod(createConvertRowsMethod(scope))
}.build()
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TransactionWrapper.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TransactionWrapper.kt
new file mode 100644
index 0000000..30d02ee
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TransactionWrapper.kt
@@ -0,0 +1,65 @@
+/*
+ * 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 android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.N
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+
+/**
+ * helper class to create correct transaction code.
+ */
+interface TransactionWrapper {
+ fun beginTransactionWithControlFlow()
+ fun commitTransaction()
+ fun endTransactionWithControlFlow()
+}
+
+fun MethodSpec.Builder.transactionWrapper(dbField : FieldSpec) = object : TransactionWrapper {
+ override fun beginTransactionWithControlFlow() {
+ addStatement("$N.beginTransaction()", dbField)
+ beginControlFlow("try")
+ }
+
+ override fun commitTransaction() {
+ addStatement("$N.setTransactionSuccessful()", dbField)
+ }
+
+ override fun endTransactionWithControlFlow() {
+ nextControlFlow("finally")
+ addStatement("$N.endTransaction()", dbField)
+ endControlFlow()
+ }
+}
+
+fun CodeBlock.Builder.transactionWrapper(dbField: FieldSpec) = object : TransactionWrapper {
+ override fun beginTransactionWithControlFlow() {
+ addStatement("$N.beginTransaction()", dbField)
+ beginControlFlow("try")
+ }
+
+ override fun commitTransaction() {
+ addStatement("$N.setTransactionSuccessful()", dbField)
+ }
+
+ override fun endTransactionWithControlFlow() {
+ nextControlFlow("finally")
+ addStatement("$N.endTransaction()", dbField)
+ endControlFlow()
+ }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt
index 1aa8430..a6b8cb7 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt
@@ -29,6 +29,7 @@
*/
data class QueryMethod(val element: ExecutableElement, val query: ParsedQuery, val name: String,
val returnType: TypeMirror, val parameters: List<QueryParameter>,
+ val inTransaction : Boolean,
val queryResultBinder : QueryResultBinder) {
val sectionToParamMapping by lazy {
query.bindSections.map {
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt
index 91bcb33..dd154c4 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt
@@ -32,6 +32,7 @@
RELATION_TYPE_MISMATCH("ROOM_RELATION_TYPE_MISMATCH"),
MISSING_SCHEMA_LOCATION("ROOM_MISSING_SCHEMA_LOCATION"),
MISSING_INDEX_ON_FOREIGN_KEY_CHILD("ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX"),
+ RELATION_QUERY_WITHOUT_TRANSACTION("ROOM_RELATION_QUERY_WITHOUT_TRANSACTION"),
DEFAULT_CONSTRUCTOR("ROOM_DEFAULT_CONSTRUCTOR");
companion object {
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt
index 5c7d8a3..0bb9c19 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt
@@ -445,7 +445,11 @@
val sqlVar = scope.getTmpVar("_sql")
val roomSQLiteQueryVar = scope.getTmpVar("_statement")
queryWriter.prepareReadAndBind(sqlVar, roomSQLiteQueryVar, scope)
- method.queryResultBinder.convertAndReturn(roomSQLiteQueryVar, dbField, scope)
+ method.queryResultBinder.convertAndReturn(
+ roomSQLiteQueryVar = roomSQLiteQueryVar,
+ dbField = dbField,
+ inTransaction = method.inTransaction,
+ scope = scope)
return scope.builder().build()
}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt
index 3ade277..55f335d 100644
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt
@@ -192,6 +192,81 @@
}.compilesWithoutError()
}
+ @Test
+ fun query_warnIfTransactionIsMissingForRelation() {
+ if (!enableVerification) {
+ return
+ }
+ singleDao(
+ """
+ @Dao interface MyDao {
+ static class Merged extends User {
+ @Relation(parentColumn = "name", entityColumn = "lastName",
+ entity = User.class)
+ java.util.List<User> users;
+ }
+ @Query("select * from user")
+ abstract java.util.List<Merged> loadUsers();
+ }
+ """
+ ) { dao, _ ->
+ assertThat(dao.queryMethods.size, `is`(1))
+ assertThat(dao.queryMethods.first().inTransaction, `is`(false))
+ }.compilesWithoutError()
+ .withWarningContaining(ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
+ }
+
+ @Test
+ fun query_dontWarnIfTransactionIsMissingForRelation_suppressed() {
+ if (!enableVerification) {
+ return
+ }
+ singleDao(
+ """
+ @Dao interface MyDao {
+ static class Merged extends User {
+ @Relation(parentColumn = "name", entityColumn = "lastName",
+ entity = User.class)
+ java.util.List<User> users;
+ }
+ @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
+ @Query("select * from user")
+ abstract java.util.List<Merged> loadUsers();
+ }
+ """
+ ) { dao, _ ->
+ assertThat(dao.queryMethods.size, `is`(1))
+ assertThat(dao.queryMethods.first().inTransaction, `is`(false))
+ }.compilesWithoutError()
+ .withWarningCount(0)
+ }
+
+ @Test
+ fun query_dontWarnIfTransactionNotIsMissingForRelation() {
+ if (!enableVerification) {
+ return
+ }
+ singleDao(
+ """
+ @Dao interface MyDao {
+ static class Merged extends User {
+ @Relation(parentColumn = "name", entityColumn = "lastName",
+ entity = User.class)
+ java.util.List<User> users;
+ }
+ @Transaction
+ @Query("select * from user")
+ abstract java.util.List<Merged> loadUsers();
+ }
+ """
+ ) { dao, _ ->
+ // test sanity
+ assertThat(dao.queryMethods.size, `is`(1))
+ assertThat(dao.queryMethods.first().inTransaction, `is`(true))
+ }.compilesWithoutError()
+ .withWarningCount(0)
+ }
+
fun singleDao(vararg inputs: String, handler: (Dao, TestInvocation) -> Unit):
CompileTester {
return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
@@ -200,7 +275,12 @@
), COMMON.USER))
.processedWith(TestProcessor.builder()
.forAnnotations(android.arch.persistence.room.Dao::class,
- android.arch.persistence.room.Entity::class)
+ android.arch.persistence.room.Entity::class,
+ android.arch.persistence.room.Relation::class,
+ android.arch.persistence.room.Transaction::class,
+ android.arch.persistence.room.ColumnInfo::class,
+ android.arch.persistence.room.PrimaryKey::class,
+ android.arch.persistence.room.Query::class)
.nextRunHandler { invocation ->
val dao = invocation.roundEnv
.getElementsAnnotatedWith(
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt
index a2cb768..e07e04d 100644
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt
@@ -445,6 +445,55 @@
}
@Test
+ fun query_detectTransaction_delete() {
+ singleQueryMethod(
+ """
+ @Query("delete from user where uid = :id")
+ abstract int deleteUser(String id);
+ """
+ ) { method, _ ->
+ assertThat(method.inTransaction, `is`(true))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun query_detectTransaction_update() {
+ singleQueryMethod(
+ """
+ @Query("UPDATE user set uid = :id + 1 where uid = :id")
+ abstract int incrementId(String id);
+ """
+ ) { method, _ ->
+ assertThat(method.inTransaction, `is`(true))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun query_detectTransaction_select() {
+ singleQueryMethod(
+ """
+ @Query("select * from user")
+ abstract int loadUsers();
+ """
+ ) { method, _ ->
+ assertThat(method.inTransaction, `is`(false))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun query_detectTransaction_selectInTransaction() {
+ singleQueryMethod(
+ """
+ @Transaction
+ @Query("select * from user")
+ abstract int loadUsers();
+ """
+ ) { method, _ ->
+ assertThat(method.inTransaction, `is`(true))
+ }.compilesWithoutError()
+ }
+
+ @Test
fun skipVerification() {
singleQueryMethod(
"""
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
new file mode 100644
index 0000000..dcf98c9
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
@@ -0,0 +1,471 @@
+/*
+ * 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 android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
+import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.PagedList;
+import android.arch.paging.TiledDataSource;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.Ignore;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Relation;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.RoomWarnings;
+import android.arch.persistence.room.Transaction;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import io.reactivex.schedulers.Schedulers;
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class QueryTransactionTest {
+ @Rule
+ public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule();
+ private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0);
+ private TransactionDb mDb;
+ private final boolean mUseTransactionDao;
+ private Entity1Dao mDao;
+ private final LiveDataQueryTest.TestLifecycleOwner mLifecycleOwner = new LiveDataQueryTest
+ .TestLifecycleOwner();
+
+ @NonNull
+ @Parameterized.Parameters(name = "useTransaction_{0}")
+ public static Boolean[] getParams() {
+ return new Boolean[]{false, true};
+ }
+
+ public QueryTransactionTest(boolean useTransactionDao) {
+ mUseTransactionDao = useTransactionDao;
+ }
+
+ @Before
+ public void initDb() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+ }
+ });
+
+ resetTransactionCount();
+ mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+ TransactionDb.class).build();
+ mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao();
+ drain();
+ }
+
+ @After
+ public void closeDb() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mLifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY);
+ }
+ });
+ drain();
+ mDb.close();
+ }
+
+ @Test
+ public void readList() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ List<Entity1> allEntities = mDao.allEntities();
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void liveData() {
+ LiveData<List<Entity1>> listLiveData = mDao.liveData();
+ observeForever(listLiveData);
+ drain();
+ assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList()));
+
+ resetTransactionCount();
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+
+ //noinspection ConstantConditions
+ assertThat(listLiveData.getValue().size(), is(1));
+ int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+ assertTransactionCount(listLiveData.getValue(), expectedTransactionCount);
+ }
+
+ @Test
+ public void flowable() {
+ Flowable<List<Entity1>> flowable = mDao.flowable();
+ TestSubscriber<List<Entity1>> subscriber = observe(flowable);
+ drain();
+ assertThat(subscriber.values().size(), is(1));
+
+ resetTransactionCount();
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+
+ List<Entity1> allEntities = subscriber.values().get(1);
+ assertThat(allEntities.size(), is(1));
+ int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void maybe() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ Maybe<List<Entity1>> listMaybe = mDao.maybe();
+ TestObserver<List<Entity1>> observer = observe(listMaybe);
+ drain();
+ List<Entity1> allEntities = observer.values().get(0);
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void single() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ Single<List<Entity1>> listMaybe = mDao.single();
+ TestObserver<List<Entity1>> observer = observe(listMaybe);
+ drain();
+ List<Entity1> allEntities = observer.values().get(0);
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void relation() {
+ mDao.insert(new Entity1(1, "foo"));
+ mDao.insert(new Child(1, 1));
+ mDao.insert(new Child(2, 1));
+ resetTransactionCount();
+
+ List<Entity1WithChildren> result = mDao.withRelation();
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ assertTransactionCountWithChildren(result, expectedTransactionCount);
+ }
+
+ @Test
+ public void pagedList() {
+ LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10);
+ observeForever(pagedList);
+ drain();
+ assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
+
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+ //noinspection ConstantConditions
+ assertThat(pagedList.getValue().size(), is(1));
+ assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 3 : 1);
+
+ mDao.insert(new Entity1(2, "bar"));
+ drain();
+ assertThat(pagedList.getValue().size(), is(2));
+ assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 5 : 2);
+ }
+
+ @Test
+ public void dataSource() {
+ mDao.insert(new Entity1(2, "bar"));
+ drain();
+ resetTransactionCount();
+ TiledDataSource<Entity1> dataSource = mDao.dataSource();
+ dataSource.loadRange(0, 10);
+ assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
+ }
+
+ private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) {
+ assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+ assertThat(allEntities.isEmpty(), is(false));
+ for (Entity1 entity1 : allEntities) {
+ assertThat(entity1.transactionId, is(expectedTransactionCount));
+ }
+ }
+
+ private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities,
+ int expectedTransactionCount) {
+ assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+ assertThat(allEntities.isEmpty(), is(false));
+ for (Entity1WithChildren entity1 : allEntities) {
+ assertThat(entity1.transactionId, is(expectedTransactionCount));
+ assertThat(entity1.children, notNullValue());
+ assertThat(entity1.children.isEmpty(), is(false));
+ for (Child child : entity1.children) {
+ assertThat(child.transactionId, is(expectedTransactionCount));
+ }
+ }
+ }
+
+ private void resetTransactionCount() {
+ sStartedTransactionCount.set(0);
+ }
+
+ private void drain() {
+ try {
+ countingTaskExecutorRule.drainTasks(30, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new AssertionError("interrupted", e);
+ } catch (TimeoutException e) {
+ throw new AssertionError("drain timed out", e);
+ }
+ }
+
+ private <T> TestSubscriber<T> observe(final Flowable<T> flowable) {
+ TestSubscriber<T> subscriber = new TestSubscriber<>();
+ flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(subscriber);
+ return subscriber;
+ }
+
+ private <T> TestObserver<T> observe(final Maybe<T> maybe) {
+ TestObserver<T> observer = new TestObserver<>();
+ maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(observer);
+ return observer;
+ }
+
+ private <T> TestObserver<T> observe(final Single<T> single) {
+ TestObserver<T> observer = new TestObserver<>();
+ single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(observer);
+ return observer;
+ }
+
+ private <T> void observeForever(final LiveData<T> liveData) {
+ FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ liveData.observe(mLifecycleOwner, new Observer<T>() {
+ @Override
+ public void onChanged(@Nullable T t) {
+
+ }
+ });
+ return null;
+ }
+ });
+ ArchTaskExecutor.getMainThreadExecutor().execute(futureTask);
+ try {
+ futureTask.get();
+ } catch (InterruptedException e) {
+ throw new AssertionError("interrupted", e);
+ } catch (ExecutionException e) {
+ throw new AssertionError("execution error", e);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class Entity1WithChildren extends Entity1 {
+ @Relation(entity = Child.class, parentColumn = "id",
+ entityColumn = "entity1Id")
+ public List<Child> children;
+
+ Entity1WithChildren(int id, String value) {
+ super(id, value);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @Entity
+ static class Child {
+ @PrimaryKey(autoGenerate = true)
+ public int id;
+ public int entity1Id;
+ @Ignore
+ public final int transactionId = sStartedTransactionCount.get();
+
+ Child(int id, int entity1Id) {
+ this.id = id;
+ this.entity1Id = entity1Id;
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @Entity
+ static class Entity1 {
+ @PrimaryKey(autoGenerate = true)
+ public int id;
+ public String value;
+ @Ignore
+ public final int transactionId = sStartedTransactionCount.get();
+
+ Entity1(int id, String value) {
+ this.id = id;
+ this.value = value;
+ }
+ }
+
+ // we don't support dao inheritance for queries so for now, go with this
+ interface Entity1Dao {
+ String SELECT_ALL = "select * from Entity1";
+
+ List<Entity1> allEntities();
+
+ Flowable<List<Entity1>> flowable();
+
+ Maybe<List<Entity1>> maybe();
+
+ Single<List<Entity1>> single();
+
+ LiveData<List<Entity1>> liveData();
+
+ List<Entity1WithChildren> withRelation();
+
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ TiledDataSource<Entity1> dataSource();
+
+ @Insert
+ void insert(Entity1 entity1);
+
+ @Insert
+ void insert(Child entity1);
+ }
+
+ @Dao
+ interface EntityDao extends Entity1Dao {
+ @Override
+ @Query(SELECT_ALL)
+ List<Entity1> allEntities();
+
+ @Override
+ @Query(SELECT_ALL)
+ Flowable<List<Entity1>> flowable();
+
+ @Override
+ @Query(SELECT_ALL)
+ LiveData<List<Entity1>> liveData();
+
+ @Override
+ @Query(SELECT_ALL)
+ Maybe<List<Entity1>> maybe();
+
+ @Override
+ @Query(SELECT_ALL)
+ Single<List<Entity1>> single();
+
+ @Override
+ @Query(SELECT_ALL)
+ @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
+ List<Entity1WithChildren> withRelation();
+
+ @Override
+ @Query(SELECT_ALL)
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ @Override
+ @Query(SELECT_ALL)
+ TiledDataSource<Entity1> dataSource();
+ }
+
+ @Dao
+ interface TransactionDao extends Entity1Dao {
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ List<Entity1> allEntities();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Flowable<List<Entity1>> flowable();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ LiveData<List<Entity1>> liveData();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Maybe<List<Entity1>> maybe();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Single<List<Entity1>> single();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ List<Entity1WithChildren> withRelation();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ TiledDataSource<Entity1> dataSource();
+ }
+
+ @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false)
+ abstract static class TransactionDb extends RoomDatabase {
+ abstract EntityDao dao();
+
+ abstract TransactionDao transactionDao();
+
+ @Override
+ public void beginTransaction() {
+ super.beginTransaction();
+ sStartedTransactionCount.incrementAndGet();
+ }
+ }
+}
diff --git a/room/integration-tests/testapp/src/test/java/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java b/room/integration-tests/testapp/src/test/java/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
deleted file mode 100644
index 3cbffc8..0000000
--- a/room/integration-tests/testapp/src/test/java/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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 android.arch.persistence.room.integration.testapp.db;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-
-public class JDBCOpenHelper implements SupportSQLiteOpenHelper {
- @Override
- public String getDatabaseName() {
- return null;
- }
-
- @Override
- public void setWriteAheadLoggingEnabled(boolean enabled) {
-
- }
-
- @Override
- public SupportSQLiteDatabase getWritableDatabase() {
- return null;
- }
-
- @Override
- public SupportSQLiteDatabase getReadableDatabase() {
- return null;
- }
-
- @Override
- public void close() {
-
- }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/paging/LimitOffsetDataSource.java b/room/runtime/src/main/java/android/arch/persistence/room/paging/LimitOffsetDataSource.java
index 800514c..2f9a888 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/paging/LimitOffsetDataSource.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/paging/LimitOffsetDataSource.java
@@ -49,10 +49,13 @@
private final RoomDatabase mDb;
@SuppressWarnings("FieldCanBeLocal")
private final InvalidationTracker.Observer mObserver;
+ private final boolean mInTransaction;
- protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, String... tables) {
+ protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
+ boolean inTransaction, String... tables) {
mDb = db;
mSourceQuery = query;
+ mInTransaction = inTransaction;
mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
mObserver = new InvalidationTracker.Observer(tables) {
@@ -98,13 +101,30 @@
sqLiteQuery.copyArgumentsFrom(mSourceQuery);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
- Cursor cursor = mDb.query(sqLiteQuery);
-
- try {
- return convertRows(cursor);
- } finally {
- cursor.close();
- sqLiteQuery.release();
+ if (mInTransaction) {
+ mDb.beginTransaction();
+ Cursor cursor = null;
+ try {
+ cursor = mDb.query(sqLiteQuery);
+ List<T> rows = convertRows(cursor);
+ mDb.setTransactionSuccessful();
+ return rows;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ mDb.endTransaction();
+ sqLiteQuery.release();
+ }
+ } else {
+ Cursor cursor = mDb.query(sqLiteQuery);
+ //noinspection TryFinallyCanBeTryWithResources
+ try {
+ return convertRows(cursor);
+ } finally {
+ cursor.close();
+ sqLiteQuery.release();
+ }
}
}
}
diff --git a/tv-provider/api/current.txt b/tv-provider/api/current.txt
index 42cad9f..80421e9 100644
--- a/tv-provider/api/current.txt
+++ b/tv-provider/api/current.txt
@@ -531,6 +531,7 @@
method public int getWatchNextType();
method public android.content.ContentValues toContentValues();
method public java.lang.String toString();
+ field public static final int WATCH_NEXT_TYPE_UNKNOWN = -1; // 0xffffffff
}
public static final class WatchNextProgram.Builder {
diff --git a/tv-provider/lint-baseline.xml b/tv-provider/lint-baseline.xml
index 9814796..4387a5a 100644
--- a/tv-provider/lint-baseline.xml
+++ b/tv-provider/lint-baseline.xml
@@ -1,92 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 3.0.0-beta6">
-
- <issue
- id="WrongConstant"
- message="Must be one of: PreviewProgramColumns.TYPE_MOVIE, PreviewProgramColumns.TYPE_TV_SERIES, PreviewProgramColumns.TYPE_TV_SEASON, PreviewProgramColumns.TYPE_TV_EPISODE, PreviewProgramColumns.TYPE_CLIP, PreviewProgramColumns.TYPE_EVENT, PreviewProgramColumns.TYPE_CHANNEL, PreviewProgramColumns.TYPE_TRACK, PreviewProgramColumns.TYPE_ALBUM, PreviewProgramColumns.TYPE_ARTIST, PreviewProgramColumns.TYPE_PLAYLIST, PreviewProgramColumns.TYPE_STATION, PreviewProgramColumns.TYPE_GAME"
- errorLine1=" return i == null ? INVALID_INT_VALUE : i;"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
- line="130"
- column="28"/>
- </issue>
-
- <issue
- id="WrongConstant"
- message="Must be one of: PreviewProgramColumns.ASPECT_RATIO_16_9, PreviewProgramColumns.ASPECT_RATIO_3_2, PreviewProgramColumns.ASPECT_RATIO_4_3, PreviewProgramColumns.ASPECT_RATIO_1_1, PreviewProgramColumns.ASPECT_RATIO_2_3, PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER"
- errorLine1=" return i == null ? INVALID_INT_VALUE : i;"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
- line="140"
- column="28"/>
- </issue>
-
- <issue
- id="WrongConstant"
- message="Must be one of: PreviewProgramColumns.ASPECT_RATIO_16_9, PreviewProgramColumns.ASPECT_RATIO_3_2, PreviewProgramColumns.ASPECT_RATIO_4_3, PreviewProgramColumns.ASPECT_RATIO_1_1, PreviewProgramColumns.ASPECT_RATIO_2_3, PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER"
- errorLine1=" return i == null ? INVALID_INT_VALUE : i;"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
- line="150"
- column="28"/>
- </issue>
-
- <issue
- id="WrongConstant"
- message="Must be one of: PreviewProgramColumns.AVAILABILITY_AVAILABLE, PreviewProgramColumns.AVAILABILITY_FREE_WITH_SUBSCRIPTION, PreviewProgramColumns.AVAILABILITY_PAID_CONTENT, PreviewProgramColumns.AVAILABILITY_PURCHASED, PreviewProgramColumns.AVAILABILITY_FREE"
- errorLine1=" return i == null ? INVALID_INT_VALUE : i;"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
- line="168"
- column="28"/>
- </issue>
-
- <issue
- id="WrongConstant"
- message="Must be one of: PreviewProgramColumns.INTERACTION_TYPE_VIEWS, PreviewProgramColumns.INTERACTION_TYPE_LISTENS, PreviewProgramColumns.INTERACTION_TYPE_FOLLOWERS, PreviewProgramColumns.INTERACTION_TYPE_FANS, PreviewProgramColumns.INTERACTION_TYPE_LIKES, PreviewProgramColumns.INTERACTION_TYPE_THUMBS, PreviewProgramColumns.INTERACTION_TYPE_VIEWERS"
- errorLine1=" return i == null ? INVALID_INT_VALUE : i;"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
- line="219"
- column="28"/>
- </issue>
-
- <issue
- id="WrongConstant"
- message="Must be one of: ProgramColumns.REVIEW_RATING_STYLE_STARS, ProgramColumns.REVIEW_RATING_STYLE_THUMBS_UP_DOWN, ProgramColumns.REVIEW_RATING_STYLE_PERCENTAGE"
- errorLine1=" return i == null ? INVALID_INT_VALUE : i;"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/android/support/media/tv/BaseProgram.java"
- line="257"
- column="28"/>
- </issue>
-
- <issue
- id="WrongConstant"
- message="Must be one of: Genres.FAMILY_KIDS, Genres.SPORTS, Genres.SHOPPING, Genres.MOVIES, Genres.COMEDY, Genres.TRAVEL, Genres.DRAMA, Genres.EDUCATION, Genres.ANIMAL_WILDLIFE, Genres.NEWS, Genres.GAMING, Genres.ARTS, Genres.ENTERTAINMENT, Genres.LIFE_STYLE, Genres.MUSIC, Genres.PREMIER, Genres.TECH_SCIENCE"
- errorLine1=" mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres));"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/android/support/media/tv/Program.java"
- line="286"
- column="81"/>
- </issue>
-
- <issue
- id="WrongConstant"
- message="Must be one of: WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE, WatchNextPrograms.WATCH_NEXT_TYPE_NEXT, WatchNextPrograms.WATCH_NEXT_TYPE_NEW, WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST"
- errorLine1=" return i == null ? INVALID_INT_VALUE : i;"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/android/support/media/tv/WatchNextProgram.java"
- line="99"
- column="28"/>
- </issue>
+<issues format="4" by="lint 3.0.0-beta7">
</issues>
diff --git a/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java b/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
index 1423d9d..39c3014 100644
--- a/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
@@ -23,14 +23,13 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.PreviewProgramColumns;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.AspectRatio;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Availability;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.InteractionType;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Type;
import android.support.media.tv.TvContractCompat.PreviewPrograms;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -55,6 +54,89 @@
private static final int IS_LIVE = 1;
private static final int IS_BROWSABLE = 1;
+ /** @hide */
+ @IntDef({
+ TYPE_UNKNOWN,
+ PreviewProgramColumns.TYPE_MOVIE,
+ PreviewProgramColumns.TYPE_TV_SERIES,
+ PreviewProgramColumns.TYPE_TV_SEASON,
+ PreviewProgramColumns.TYPE_TV_EPISODE,
+ PreviewProgramColumns.TYPE_CLIP,
+ PreviewProgramColumns.TYPE_EVENT,
+ PreviewProgramColumns.TYPE_CHANNEL,
+ PreviewProgramColumns.TYPE_TRACK,
+ PreviewProgramColumns.TYPE_ALBUM,
+ PreviewProgramColumns.TYPE_ARTIST,
+ PreviewProgramColumns.TYPE_PLAYLIST,
+ PreviewProgramColumns.TYPE_STATION,
+ PreviewProgramColumns.TYPE_GAME
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface Type {}
+
+ /**
+ * The unknown program type.
+ */
+ private static final int TYPE_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ ASPECT_RATIO_UNKNOWN,
+ PreviewProgramColumns.ASPECT_RATIO_16_9,
+ PreviewProgramColumns.ASPECT_RATIO_3_2,
+ PreviewProgramColumns.ASPECT_RATIO_4_3,
+ PreviewProgramColumns.ASPECT_RATIO_1_1,
+ PreviewProgramColumns.ASPECT_RATIO_2_3,
+ PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface AspectRatio {}
+
+ /**
+ * The aspect ratio for unknown aspect ratios.
+ */
+ private static final int ASPECT_RATIO_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ AVAILABILITY_UNKNOWN,
+ PreviewProgramColumns.AVAILABILITY_AVAILABLE,
+ PreviewProgramColumns.AVAILABILITY_FREE_WITH_SUBSCRIPTION,
+ PreviewProgramColumns.AVAILABILITY_PAID_CONTENT,
+ PreviewProgramColumns.AVAILABILITY_PURCHASED,
+ PreviewProgramColumns.AVAILABILITY_FREE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface Availability {}
+
+ /**
+ * The unknown availability.
+ */
+ private static final int AVAILABILITY_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ INTERACTION_TYPE_UNKNOWN,
+ PreviewProgramColumns.INTERACTION_TYPE_VIEWS,
+ PreviewProgramColumns.INTERACTION_TYPE_LISTENS,
+ PreviewProgramColumns.INTERACTION_TYPE_FOLLOWERS,
+ PreviewProgramColumns.INTERACTION_TYPE_FANS,
+ PreviewProgramColumns.INTERACTION_TYPE_LIKES,
+ PreviewProgramColumns.INTERACTION_TYPE_THUMBS,
+ PreviewProgramColumns.INTERACTION_TYPE_VIEWERS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface InteractionType {}
+
+ /**
+ * The unknown interaction type.
+ */
+ private static final int INTERACTION_TYPE_UNKNOWN = -1;
+
BasePreviewProgram(Builder builder) {
super(builder);
}
@@ -127,7 +209,7 @@
*/
public @Type int getType() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? TYPE_UNKNOWN : i;
}
/**
@@ -137,7 +219,7 @@
*/
public @AspectRatio int getPosterArtAspectRatio() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? ASPECT_RATIO_UNKNOWN : i;
}
/**
@@ -147,7 +229,7 @@
*/
public @AspectRatio int getThumbnailAspectRatio() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? ASPECT_RATIO_UNKNOWN : i;
}
/**
@@ -165,7 +247,7 @@
*/
public @Availability int getAvailability() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_AVAILABILITY);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? AVAILABILITY_UNKNOWN : i;
}
/**
@@ -216,7 +298,7 @@
*/
public @InteractionType int getInteractionType() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_INTERACTION_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? INTERACTION_TYPE_UNKNOWN : i;
}
/**
diff --git a/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java b/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
index e4ce9d1..23b5cf9 100644
--- a/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
@@ -22,13 +22,16 @@
import android.media.tv.TvContentRating;
import android.net.Uri;
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.BaseTvColumns;
import android.support.media.tv.TvContractCompat.ProgramColumns;
-import android.support.media.tv.TvContractCompat.ProgramColumns.ReviewRatingStyle;
import android.support.media.tv.TvContractCompat.Programs;
import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Base class for derived classes that want to have common fields for programs defined in
* {@link TvContractCompat}.
@@ -46,6 +49,22 @@
private static final int IS_SEARCHABLE = 1;
/** @hide */
+ @IntDef({
+ REVIEW_RATING_STYLE_UNKNOWN,
+ ProgramColumns.REVIEW_RATING_STYLE_STARS,
+ ProgramColumns.REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
+ ProgramColumns.REVIEW_RATING_STYLE_PERCENTAGE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ @interface ReviewRatingStyle {}
+
+ /**
+ * The unknown review rating style.
+ */
+ private static final int REVIEW_RATING_STYLE_UNKNOWN = -1;
+
+ /** @hide */
@RestrictTo(LIBRARY_GROUP)
protected ContentValues mValues;
@@ -254,7 +273,7 @@
*/
public @ReviewRatingStyle int getReviewRatingStyle() {
Integer i = mValues.getAsInteger(Programs.COLUMN_REVIEW_RATING_STYLE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? REVIEW_RATING_STYLE_UNKNOWN : i;
}
/**
diff --git a/tv-provider/src/main/java/android/support/media/tv/Program.java b/tv-provider/src/main/java/android/support/media/tv/Program.java
index 4e3bd7a..233f1ba 100644
--- a/tv-provider/src/main/java/android/support/media/tv/Program.java
+++ b/tv-provider/src/main/java/android/support/media/tv/Program.java
@@ -25,6 +25,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.Programs;
+import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
/**
* A convenience class to access {@link TvContractCompat.Programs} entries in the system content
@@ -282,7 +283,7 @@
* @return This Builder object to allow for chaining of calls to builder methods.
* @see Programs#COLUMN_BROADCAST_GENRE
*/
- public Builder setBroadcastGenres(String[] genres) {
+ public Builder setBroadcastGenres(@Genre String[] genres) {
mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres));
return this;
}
diff --git a/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java b/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
index 5a46e79..de4fd04 100644
--- a/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
+++ b/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
@@ -30,7 +30,6 @@
import android.os.Build;
import android.os.Bundle;
import android.provider.BaseColumns;
-import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
@@ -606,16 +605,6 @@
*/
@RestrictTo(LIBRARY_GROUP)
interface ProgramColumns {
- /** @hide */
- @IntDef({
- REVIEW_RATING_STYLE_STARS,
- REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
- REVIEW_RATING_STYLE_PERCENTAGE,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- @interface ReviewRatingStyle {}
-
/**
* The review rating style for five star rating.
*
@@ -934,27 +923,6 @@
*/
@RestrictTo(LIBRARY_GROUP)
public interface PreviewProgramColumns {
-
- /** @hide */
- @IntDef({
- TYPE_MOVIE,
- TYPE_TV_SERIES,
- TYPE_TV_SEASON,
- TYPE_TV_EPISODE,
- TYPE_CLIP,
- TYPE_EVENT,
- TYPE_CHANNEL,
- TYPE_TRACK,
- TYPE_ALBUM,
- TYPE_ARTIST,
- TYPE_PLAYLIST,
- TYPE_STATION,
- TYPE_GAME
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface Type {}
-
/**
* The program type for movie.
*
@@ -1046,19 +1014,6 @@
*/
int TYPE_GAME = 12;
- /** @hide */
- @IntDef({
- ASPECT_RATIO_16_9,
- ASPECT_RATIO_3_2,
- ASPECT_RATIO_4_3,
- ASPECT_RATIO_1_1,
- ASPECT_RATIO_2_3,
- ASPECT_RATIO_MOVIE_POSTER,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface AspectRatio {}
-
/**
* The aspect ratio for 16:9.
*
@@ -1107,18 +1062,6 @@
*/
int ASPECT_RATIO_MOVIE_POSTER = 5;
- /** @hide */
- @IntDef({
- AVAILABILITY_AVAILABLE,
- AVAILABILITY_FREE_WITH_SUBSCRIPTION,
- AVAILABILITY_PAID_CONTENT,
- AVAILABILITY_PURCHASED,
- AVAILABILITY_FREE,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface Availability {}
-
/**
* The availability for "available to this user".
*
@@ -1155,20 +1098,6 @@
*/
int AVAILABILITY_FREE = 4;
- /** @hide */
- @IntDef({
- INTERACTION_TYPE_VIEWS,
- INTERACTION_TYPE_LISTENS,
- INTERACTION_TYPE_FOLLOWERS,
- INTERACTION_TYPE_FANS,
- INTERACTION_TYPE_LIKES,
- INTERACTION_TYPE_THUMBS,
- INTERACTION_TYPE_VIEWERS,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface InteractionType {}
-
/**
* The interaction type for "views".
*
@@ -2895,17 +2824,6 @@
/** The MIME type of a single preview TV program. */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watch_next_program";
- /** @hide */
- @IntDef({
- WATCH_NEXT_TYPE_CONTINUE,
- WATCH_NEXT_TYPE_NEXT,
- WATCH_NEXT_TYPE_NEW,
- WATCH_NEXT_TYPE_WATCHLIST,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface WatchNextType {}
-
/**
* The watch next type for CONTINUE. Use this type when the user has already watched more
* than 1 minute of this content.
diff --git a/tv-provider/src/main/java/android/support/media/tv/WatchNextProgram.java b/tv-provider/src/main/java/android/support/media/tv/WatchNextProgram.java
index f466584..c192745 100644
--- a/tv-provider/src/main/java/android/support/media/tv/WatchNextProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/WatchNextProgram.java
@@ -22,12 +22,15 @@
import android.database.Cursor;
import android.media.tv.TvContentRating; // For javadoc gen of super class
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.PreviewPrograms; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.Programs; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.Programs.Genres; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.WatchNextPrograms;
-import android.support.media.tv.TvContractCompat.WatchNextPrograms.WatchNextType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* A convenience class to access {@link WatchNextPrograms} entries in the system content
@@ -87,16 +90,34 @@
private static final long INVALID_LONG_VALUE = -1;
private static final int INVALID_INT_VALUE = -1;
+ /** @hide */
+ @IntDef({
+ WATCH_NEXT_TYPE_UNKNOWN,
+ WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE,
+ WatchNextPrograms.WATCH_NEXT_TYPE_NEXT,
+ WatchNextPrograms.WATCH_NEXT_TYPE_NEW,
+ WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface WatchNextType {}
+
+ /**
+ * The unknown watch next type. Use this type when the actual type is not known.
+ */
+ public static final int WATCH_NEXT_TYPE_UNKNOWN = -1;
+
private WatchNextProgram(Builder builder) {
super(builder);
}
/**
- * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program.
+ * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program,
+ * or {@link #WATCH_NEXT_TYPE_UNKNOWN} if it's unknown.
*/
public @WatchNextType int getWatchNextType() {
Integer i = mValues.getAsInteger(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? WATCH_NEXT_TYPE_UNKNOWN : i;
}
/**