Merge "Properly log see more event for row view"
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 03d2e6c..15274fe 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportJavaLibraryPlugin")
diff --git a/app-toolkit/common/build.gradle b/app-toolkit/common/build.gradle
index db61b3f..81a34ad 100644
--- a/app-toolkit/common/build.gradle
+++ b/app-toolkit/common/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension;
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension;
plugins {
id("SupportJavaLibraryPlugin")
diff --git a/app-toolkit/core-testing/build.gradle b/app-toolkit/core-testing/build.gradle
index 8e7ccfb..e4c2e17 100644
--- a/app-toolkit/core-testing/build.gradle
+++ b/app-toolkit/core-testing/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/app-toolkit/init.gradle b/app-toolkit/init.gradle
index d3d7754..3c58368 100644
--- a/app-toolkit/init.gradle
+++ b/app-toolkit/init.gradle
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-import android.support.DacOptions
-import android.support.license.CheckExternalDependencyLicensesTask
+import androidx.build.DacOptions
+import androidx.build.license.CheckExternalDependencyLicensesTask
+
apply from: "${ext.supportRootFolder}/buildSrc/init.gradle"
init.setSdkInLocalPropertiesFile()
diff --git a/app-toolkit/runtime/build.gradle b/app-toolkit/runtime/build.gradle
index cd74aae..5c8f2d9 100644
--- a/app-toolkit/runtime/build.gradle
+++ b/app-toolkit/runtime/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.SupportLibraryExtension
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.SupportLibraryExtension
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/asynclayoutinflater/build.gradle b/asynclayoutinflater/build.gradle
index 597b01d..a83d876 100644
--- a/asynclayoutinflater/build.gradle
+++ b/asynclayoutinflater/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/build.gradle b/build.gradle
index 0307507..c8683d4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import android.support.DacOptions
+
+import androidx.build.DacOptions
def currentJvmVersion = org.gradle.api.JavaVersion.current()
if (currentJvmVersion.getMajorVersion() != "8") {
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index 38b41e8..4faeac5 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -15,9 +15,9 @@
*/
-import android.support.DiffAndDocs
-import android.support.gmaven.GMavenVersionChecker
-import android.support.license.CheckExternalDependencyLicensesTask
+import androidx.build.DiffAndDocs
+import androidx.build.gmaven.GMavenVersionChecker
+import androidx.build.license.CheckExternalDependencyLicensesTask
import com.android.build.gradle.internal.coverage.JacocoReportTask
import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
import org.gradle.api.logging.configuration.ShowStacktrace
diff --git a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
similarity index 93%
rename from buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
rename to buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
index 31e2026..213a8a8 100644
--- a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
-import android.support.checkapi.ApiXmlConversionTask
-import android.support.checkapi.CheckApiTask
-import android.support.checkapi.UpdateApiTask
-import android.support.doclava.DoclavaTask
-import android.support.docs.GenerateDocsTask
-import android.support.jdiff.JDiffTask
+import androidx.build.checkapi.ApiXmlConversionTask
+import androidx.build.checkapi.CheckApiTask
+import androidx.build.checkapi.UpdateApiTask
+import androidx.build.doclava.DoclavaTask
+import androidx.build.docs.GenerateDocsTask
+import androidx.build.jdiff.JDiffTask
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.LibraryVariant
import org.gradle.api.GradleException
@@ -137,28 +137,6 @@
return File(apiDir, "current.txt")
}
-private fun createVerifyUpdateApiAllowedTask(project: Project) =
- project.tasks.createWithConfig("verifyUpdateApiAllowed") {
- // This could be moved to doFirst inside updateApi, but using it as a
- // dependency with no inputs forces it to run even when updateApi is a
- // no-op.
- doLast {
- val rootFolder = project.projectDir
- val version = Version(project.version as String)
-
- if (version.isPatch()) {
- throw GradleException("Public APIs may not be modified in patch releases.")
- } else if (!version.isFinalApi() && getApiFile(rootFolder,
- version,
- true).exists()) {
- throw GradleException("Inconsistent version. Public API file already exists.")
- } else if (version.isFinalApi() && getApiFile(rootFolder, version).exists()
- && !project.hasProperty("force")) {
- throw GradleException("Public APIs may not be modified in finalized releases.")
- }
- }
- }
-
// Generates API files
private fun createGenerateApiTask(project: Project, docletpathParam: Collection<File>) =
project.tasks.createWithConfig("generateApi", DoclavaTask::class.java) {
@@ -232,8 +210,12 @@
oldApiFile = getApiFile(project.projectDir, project.version())
whitelistErrors = checkApiRelease.whitelistErrors
whitelistErrorsFile = checkApiRelease.whitelistErrorsFile
-
doFirst {
+ val version = project.version()
+ if (!version.isFinalApi() &&
+ getApiFile(project.projectDir, version, true).exists()) {
+ throw GradleException("Inconsistent version. Public API file already exists.")
+ }
// Replace the expected whitelist with the detected whitelist.
whitelistErrors = checkApiRelease.detectedWhitelistErrors
}
@@ -246,7 +228,9 @@
*/
private fun createOldApiXml(project: Project, doclavaConfig: Configuration) =
project.tasks.createWithConfig("oldApiXml", ApiXmlConversionTask::class.java) {
- val toApi = project.processProperty("toApi")?.let { Version.parseOrNull(it) }
+ val toApi = project.processProperty("toApi")?.let {
+ Version.parseOrNull(it)
+ }
val fromApi = project.processProperty("fromApi")
classpath = project.files(doclavaConfig.resolve())
val rootFolder = project.projectDir
@@ -450,7 +434,6 @@
val docletClasspath = doclavaConfiguration.resolve()
val generateApi = createGenerateApiTask(project, docletClasspath)
generateApi.dependsOn(doclavaConfiguration)
- val verifyUpdateTask = createVerifyUpdateApiAllowedTask(project)
// Make sure the API surface has not broken since the last release.
val lastReleasedApiFile = getLastReleasedApiFile(workingDir, version)
@@ -489,7 +472,7 @@
checkApi.description = "Verify the API surface."
val updateApiTask = createUpdateApiTask(project, checkApiRelease)
- updateApiTask.dependsOn(checkApiRelease, verifyUpdateTask)
+ updateApiTask.dependsOn(checkApiRelease)
val newApiTask = createNewApiXmlTask(project, generateApi, doclavaConfiguration)
val oldApiTask = createOldApiXml(project, doclavaConfiguration)
@@ -578,9 +561,6 @@
private fun sdkApiFile(project: Project) = File(project.docsDir(), "release/sdk_current.txt")
private fun removedSdkApiFile(project: Project) = File(project.docsDir(), "release/sdk_removed.txt")
-private fun TaskContainer.createWithConfig(name: String, config: Task.() -> Unit) =
- create(name) { task -> task.config() }
-
private fun <T : Task> TaskContainer.createWithConfig(
name: String, taskClass: Class<T>,
config: T.() -> Unit) =
diff --git a/buildSrc/src/main/kotlin/android/support/ErrorProneConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/ErrorProneConfiguration.kt
rename to buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index 74f0505..8c8fbbb 100644
--- a/buildSrc/src/main/kotlin/android/support/ErrorProneConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
import net.ltgt.gradle.errorprone.ErrorProneToolChain
import org.gradle.api.tasks.compile.JavaCompile
diff --git a/buildSrc/src/main/kotlin/android/support/LibraryGroups.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
similarity index 97%
rename from buildSrc/src/main/kotlin/android/support/LibraryGroups.kt
rename to buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
index 2f64e5d..385c04c 100644
--- a/buildSrc/src/main/kotlin/android/support/LibraryGroups.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
/**
* The list of maven group names of all the libraries in this project.
diff --git a/buildSrc/src/main/kotlin/android/support/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
similarity index 96%
rename from buildSrc/src/main/kotlin/android/support/LibraryVersions.kt
rename to buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index cf5c647..fb4ab98 100644
--- a/buildSrc/src/main/kotlin/android/support/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
/**
* The list of versions codes of all the libraries in this project.
diff --git a/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt
rename to buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index c8be47a..dc86936 100644
--- a/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
import com.android.build.gradle.LibraryPlugin
import org.gradle.api.Project
diff --git a/buildSrc/src/main/kotlin/android/support/SourceJarTaskHelper.kt b/buildSrc/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/SourceJarTaskHelper.kt
rename to buildSrc/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
index 992171e..5a11175 100644
--- a/buildSrc/src/main/kotlin/android/support/SourceJarTaskHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
import com.android.build.gradle.LibraryExtension
import com.android.builder.core.BuilderConstants
diff --git a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
similarity index 96%
rename from buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
rename to buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
index 14e4669..9c2d82c 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
-import android.support.SupportConfig.INSTRUMENTATION_RUNNER
-import android.support.license.CheckExternalDependencyLicensesTask
+import androidx.build.SupportConfig.INSTRUMENTATION_RUNNER
+import androidx.build.license.CheckExternalDependencyLicensesTask
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.internal.dsl.LintOptions
import com.android.build.gradle.tasks.GenerateBuildConfig
diff --git a/buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppExtension.kt b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidTestAppExtension.kt
similarity index 89%
rename from buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppExtension.kt
rename to buildSrc/src/main/kotlin/androidx/build/SupportAndroidTestAppExtension.kt
index 66dd873..3d3e0aa 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppExtension.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidTestAppExtension.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
-import android.support.SupportConfig.DEFAULT_MIN_SDK_VERSION
+import androidx.build.SupportConfig.DEFAULT_MIN_SDK_VERSION
import org.gradle.api.Project
/**
diff --git a/buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidTestAppPlugin.kt
similarity index 94%
rename from buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppPlugin.kt
rename to buildSrc/src/main/kotlin/androidx/build/SupportAndroidTestAppPlugin.kt
index 1604bbe..1142ea0 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidTestAppPlugin.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
-import android.support.SupportConfig.INSTRUMENTATION_RUNNER
-import android.support.license.CheckExternalDependencyLicensesTask
+import androidx.build.SupportConfig.INSTRUMENTATION_RUNNER
+import androidx.build.license.CheckExternalDependencyLicensesTask
import com.android.build.gradle.AppExtension
import net.ltgt.gradle.errorprone.ErrorProneBasePlugin
import net.ltgt.gradle.errorprone.ErrorProneToolChain
diff --git a/buildSrc/src/main/kotlin/android/support/SupportConfig.kt b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
similarity index 97%
rename from buildSrc/src/main/kotlin/android/support/SupportConfig.kt
rename to buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
index 42b298c..1f216f7 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportConfig.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
import org.gradle.api.Project
import org.gradle.api.plugins.ExtraPropertiesExtension
diff --git a/buildSrc/src/main/kotlin/android/support/SupportJavaLibraryPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/SupportJavaLibraryPlugin.kt
similarity index 93%
rename from buildSrc/src/main/kotlin/android/support/SupportJavaLibraryPlugin.kt
rename to buildSrc/src/main/kotlin/androidx/build/SupportJavaLibraryPlugin.kt
index f3651ab..24546f1 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportJavaLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportJavaLibraryPlugin.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
-import android.support.license.CheckExternalDependencyLicensesTask
+import androidx.build.license.CheckExternalDependencyLicensesTask
import net.ltgt.gradle.errorprone.ErrorProneBasePlugin
import net.ltgt.gradle.errorprone.ErrorProneToolChain
import org.gradle.api.JavaVersion
diff --git a/buildSrc/src/main/kotlin/android/support/SupportKotlinLibraryPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/SupportKotlinLibraryPlugin.kt
similarity index 90%
rename from buildSrc/src/main/kotlin/android/support/SupportKotlinLibraryPlugin.kt
rename to buildSrc/src/main/kotlin/androidx/build/SupportKotlinLibraryPlugin.kt
index 8651ef8..4103aad 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportKotlinLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportKotlinLibraryPlugin.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.support
+package androidx.build
-import android.support.license.CheckExternalDependencyLicensesTask
+import androidx.build.license.CheckExternalDependencyLicensesTask
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
diff --git a/buildSrc/src/main/kotlin/android/support/SupportLibraryExtension.kt b/buildSrc/src/main/kotlin/androidx/build/SupportLibraryExtension.kt
similarity index 93%
rename from buildSrc/src/main/kotlin/android/support/SupportLibraryExtension.kt
rename to buildSrc/src/main/kotlin/androidx/build/SupportLibraryExtension.kt
index fb2f3b5..4f69460 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportLibraryExtension.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportLibraryExtension.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
-import android.support.SupportConfig.DEFAULT_MIN_SDK_VERSION
+import androidx.build.SupportConfig.DEFAULT_MIN_SDK_VERSION
import groovy.lang.Closure
import org.gradle.api.Project
import java.util.ArrayList
diff --git a/buildSrc/src/main/kotlin/android/support/Version.kt b/buildSrc/src/main/kotlin/androidx/build/Version.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/Version.kt
rename to buildSrc/src/main/kotlin/androidx/build/Version.kt
index 6348b0c..75855d5 100644
--- a/buildSrc/src/main/kotlin/android/support/Version.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/Version.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
import java.io.File
import java.util.regex.Matcher
diff --git a/buildSrc/src/main/kotlin/android/support/VersionFileWriterTask.kt b/buildSrc/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/VersionFileWriterTask.kt
rename to buildSrc/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
index cde11e6..9fbe3ba 100644
--- a/buildSrc/src/main/kotlin/android/support/VersionFileWriterTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
import com.android.build.gradle.LibraryExtension
import org.gradle.api.DefaultTask
diff --git a/buildSrc/src/main/kotlin/android/support/checkapi/ApiXmlConversionTask.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt
similarity index 97%
rename from buildSrc/src/main/kotlin/android/support/checkapi/ApiXmlConversionTask.kt
rename to buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt
index a4733b2..419d66f 100644
--- a/buildSrc/src/main/kotlin/android/support/checkapi/ApiXmlConversionTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.checkapi
+package androidx.build.checkapi
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.JavaExec
diff --git a/buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApiTask.kt
similarity index 99%
rename from buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt
rename to buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApiTask.kt
index be9f877..44f7d39 100644
--- a/buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApiTask.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.checkapi
+package androidx.build.checkapi
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
diff --git a/buildSrc/src/main/kotlin/android/support/checkapi/UpdateApiTask.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/UpdateApiTask.kt
similarity index 79%
rename from buildSrc/src/main/kotlin/android/support/checkapi/UpdateApiTask.kt
rename to buildSrc/src/main/kotlin/androidx/build/checkapi/UpdateApiTask.kt
index e1c0d3f..810d50b 100644
--- a/buildSrc/src/main/kotlin/android/support/checkapi/UpdateApiTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/checkapi/UpdateApiTask.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package android.support.checkapi
+package androidx.build.checkapi
+import androidx.build.Version
import com.google.common.io.Files
import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
@@ -52,6 +54,18 @@
*/
@TaskAction
fun doUpdate() {
+ if (oldApiFile.exists() && newApiFile.readText() == oldApiFile.readText()) {
+ // whatever nothing changed
+ return
+ }
+
+ val version = Version(project.version as String)
+ if (version.isPatch()) {
+ throw GradleException("Public APIs may not be modified in patch releases.")
+ } else if (version.isFinalApi() && oldApiFile.exists() && !project.hasProperty("force")) {
+ throw GradleException("Public APIs may not be modified in finalized releases.")
+ }
+
Files.copy(newApiFile, oldApiFile)
if (oldRemovedApiFile != null) {
diff --git a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
rename to buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 0844832..4199fae 100644
--- a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.dependencies
+package androidx.build.dependencies
const val AUTO_COMMON = "com.google.auto:auto-common:0.6"
const val ANTLR = "org.antlr:antlr4:4.5.3"
diff --git a/buildSrc/src/main/kotlin/android/support/doclava/DoclavaTask.kt b/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
similarity index 99%
rename from buildSrc/src/main/kotlin/android/support/doclava/DoclavaTask.kt
rename to buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
index b30611d..3f0a6f5 100644
--- a/buildSrc/src/main/kotlin/android/support/doclava/DoclavaTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.doclava
+package androidx.build.doclava
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
diff --git a/buildSrc/src/main/kotlin/android/support/docs/GenerateDocsTask.kt b/buildSrc/src/main/kotlin/androidx/build/docs/GenerateDocsTask.kt
similarity index 94%
rename from buildSrc/src/main/kotlin/android/support/docs/GenerateDocsTask.kt
rename to buildSrc/src/main/kotlin/androidx/build/docs/GenerateDocsTask.kt
index e889a54..4bbb830 100644
--- a/buildSrc/src/main/kotlin/android/support/docs/GenerateDocsTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/docs/GenerateDocsTask.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.support.docs
+package androidx.build.docs
-import android.support.Version
-import android.support.doclava.DoclavaTask
+import androidx.build.Version
+import androidx.build.doclava.DoclavaTask
import java.io.File
open class GenerateDocsTask : DoclavaTask() {
diff --git a/buildSrc/src/main/kotlin/android/support/gmaven/GMavenVersionChecker.kt b/buildSrc/src/main/kotlin/androidx/build/gmaven/GMavenVersionChecker.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/gmaven/GMavenVersionChecker.kt
rename to buildSrc/src/main/kotlin/androidx/build/gmaven/GMavenVersionChecker.kt
index 7fc4836..3c1b8d0 100644
--- a/buildSrc/src/main/kotlin/android/support/gmaven/GMavenVersionChecker.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/gmaven/GMavenVersionChecker.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.support.gmaven
+package androidx.build.gmaven
-import android.support.Version
+import androidx.build.Version
import groovy.util.XmlSlurper
import groovy.util.slurpersupport.Node
import groovy.util.slurpersupport.NodeChild
diff --git a/buildSrc/src/main/kotlin/android/support/jdiff/JDiffTask.kt b/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/jdiff/JDiffTask.kt
rename to buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt
index a6abc57..dfeb322 100644
--- a/buildSrc/src/main/kotlin/android/support/jdiff/JDiffTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.jdiff
+package androidx.build.jdiff
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
diff --git a/buildSrc/src/main/kotlin/android/support/license/CheckExternalDependencyLicensesTask.kt b/buildSrc/src/main/kotlin/androidx/build/license/CheckExternalDependencyLicensesTask.kt
similarity index 98%
rename from buildSrc/src/main/kotlin/android/support/license/CheckExternalDependencyLicensesTask.kt
rename to buildSrc/src/main/kotlin/androidx/build/license/CheckExternalDependencyLicensesTask.kt
index 771f222..2dc4e01 100644
--- a/buildSrc/src/main/kotlin/android/support/license/CheckExternalDependencyLicensesTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/license/CheckExternalDependencyLicensesTask.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.support.license
+package androidx.build.license
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidLibraryPlugin.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidLibraryPlugin.properties
index 222602b..25c8e4e 100644
--- a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidLibraryPlugin.properties
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidLibraryPlugin.properties
@@ -1 +1 @@
-implementation-class=android.support.SupportAndroidLibraryPlugin
\ No newline at end of file
+implementation-class=androidx.build.SupportAndroidLibraryPlugin
\ No newline at end of file
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidTestAppPlugin.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidTestAppPlugin.properties
index 4c794f1..8bb9716 100644
--- a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidTestAppPlugin.properties
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidTestAppPlugin.properties
@@ -1 +1 @@
-implementation-class=android.support.SupportAndroidTestAppPlugin
\ No newline at end of file
+implementation-class=androidx.build.SupportAndroidTestAppPlugin
\ No newline at end of file
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportJavaLibraryPlugin.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportJavaLibraryPlugin.properties
index 89755b7..acc6819 100644
--- a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportJavaLibraryPlugin.properties
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportJavaLibraryPlugin.properties
@@ -1 +1 @@
-implementation-class=android.support.SupportJavaLibraryPlugin
\ No newline at end of file
+implementation-class=androidx.build.SupportJavaLibraryPlugin
\ No newline at end of file
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportKotlinLibraryPlugin.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportKotlinLibraryPlugin.properties
index d93c20a..f50d743 100644
--- a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportKotlinLibraryPlugin.properties
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportKotlinLibraryPlugin.properties
@@ -1 +1 @@
-implementation-class=android.support.SupportKotlinLibraryPlugin
+implementation-class=androidx.build.SupportKotlinLibraryPlugin
diff --git a/buildSrc/src/test/kotlin/android/support/VersionTest.kt b/buildSrc/src/test/kotlin/androidx/build/VersionTest.kt
similarity index 95%
rename from buildSrc/src/test/kotlin/android/support/VersionTest.kt
rename to buildSrc/src/test/kotlin/androidx/build/VersionTest.kt
index 339acab..fc803d9 100644
--- a/buildSrc/src/test/kotlin/android/support/VersionTest.kt
+++ b/buildSrc/src/test/kotlin/androidx/build/VersionTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support
+package androidx.build
import org.junit.Assert.assertEquals
import org.junit.Test
diff --git a/car/build.gradle b/car/build.gradle
index 819f0ad..6c8eb03 100644
--- a/car/build.gradle
+++ b/car/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/car/src/androidTest/java/androidx/car/widget/TextListItemTest.java b/car/src/androidTest/java/androidx/car/widget/TextListItemTest.java
index e7e8ec6..d489c50 100644
--- a/car/src/androidTest/java/androidx/car/widget/TextListItemTest.java
+++ b/car/src/androidTest/java/androidx/car/widget/TextListItemTest.java
@@ -730,6 +730,29 @@
}
@Test
+ public void testNoCarriedOverOnClickListener() throws Throwable {
+ boolean[] clicked = new boolean[] {false};
+ TextListItem item0 = new TextListItem(mActivity);
+ item0.setOnClickListener(v -> clicked[0] = true);
+
+ setupPagedListView(Arrays.asList(item0));
+
+ onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
+ assertTrue(clicked[0]);
+
+ // item1 does not have onClickListener.
+ TextListItem item1 = new TextListItem(mActivity);
+ TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+ // Manually rebind the view holder.
+ mActivityRule.runOnUiThread(() -> item1.bind(viewHolder));
+
+ // Reset for testing.
+ clicked[0] = false;
+ onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
+ assertFalse(clicked[0]);
+ }
+
+ @Test
public void testUpdateItem() throws Throwable {
TextListItem item = new TextListItem(mActivity);
setupPagedListView(Arrays.asList(item));
diff --git a/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java b/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java
index 8994444..a374f8d 100644
--- a/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java
+++ b/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java
@@ -95,7 +95,7 @@
*
* @param restrictions current car UX restrictions.
*/
- public void complyWithUxRestrictions(CarUxRestrictions restrictions) {
+ void complyWithUxRestrictions(CarUxRestrictions restrictions) {
CarUxRestrictionsUtils.comply(itemView.getContext(), restrictions, getText());
}
}
diff --git a/car/src/main/java/androidx/car/moderator/SpeedBumpController.java b/car/src/main/java/androidx/car/moderator/SpeedBumpController.java
index 21cf124..dcaedc1 100644
--- a/car/src/main/java/androidx/car/moderator/SpeedBumpController.java
+++ b/car/src/main/java/androidx/car/moderator/SpeedBumpController.java
@@ -245,8 +245,17 @@
try {
mCarUxRestrictionsManager = (CarUxRestrictionsManager)
mCar.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+
+ // Use explicit class definition instead of lambda. Using lambda makes compiler to
+ // desugar, which will lead to failure due to class definition not being available
+ // to dependencies at compile time (e.g. sample apk).
mCarUxRestrictionsManager.registerListener(
- SpeedBumpController.this::updateUnlimitedModeEnabled);
+ new CarUxRestrictionsManager.onUxRestrictionsChangedListener() {
+ @Override
+ public void onUxRestrictionsChanged(CarUxRestrictions uxRestrictions) {
+ updateUnlimitedModeEnabled(uxRestrictions);
+ }
+ });
updateUnlimitedModeEnabled(
mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
diff --git a/car/src/main/java/androidx/car/utils/CarUxRestrictionsUtils.java b/car/src/main/java/androidx/car/utils/CarUxRestrictionsUtils.java
index 6b4019c..73d4c98 100644
--- a/car/src/main/java/androidx/car/utils/CarUxRestrictionsUtils.java
+++ b/car/src/main/java/androidx/car/utils/CarUxRestrictionsUtils.java
@@ -16,8 +16,11 @@
package androidx.car.utils;
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
import android.car.drivingstate.CarUxRestrictions;
import android.content.Context;
+import android.support.annotation.RestrictTo;
import android.text.InputFilter;
import android.widget.TextView;
@@ -29,7 +32,10 @@
/**
* Utility class that helps {@code View}s comply with {@link CarUxRestrictions}.
+ *
+ * @hide
*/
+@RestrictTo(LIBRARY_GROUP)
public class CarUxRestrictionsUtils {
private CarUxRestrictionsUtils() {};
diff --git a/car/src/main/java/androidx/car/widget/ListItem.java b/car/src/main/java/androidx/car/widget/ListItem.java
index 014ffd7..dd319e8 100644
--- a/car/src/main/java/androidx/car/widget/ListItem.java
+++ b/car/src/main/java/androidx/car/widget/ListItem.java
@@ -221,6 +221,6 @@
*
* @param restrictions current car UX restrictions.
*/
- public abstract void complyWithUxRestrictions(CarUxRestrictions restrictions);
+ abstract void complyWithUxRestrictions(CarUxRestrictions restrictions);
}
}
diff --git a/car/src/main/java/androidx/car/widget/SeekbarListItem.java b/car/src/main/java/androidx/car/widget/SeekbarListItem.java
index 0136cd2..796fd24 100644
--- a/car/src/main/java/androidx/car/widget/SeekbarListItem.java
+++ b/car/src/main/java/androidx/car/widget/SeekbarListItem.java
@@ -520,7 +520,7 @@
}
@Override
- public void complyWithUxRestrictions(CarUxRestrictions restrictions) {
+ void complyWithUxRestrictions(CarUxRestrictions restrictions) {
CarUxRestrictionsUtils.comply(itemView.getContext(), restrictions, getText());
}
diff --git a/car/src/main/java/androidx/car/widget/TextListItem.java b/car/src/main/java/androidx/car/widget/TextListItem.java
index df8d5fa..24c1b12 100644
--- a/car/src/main/java/androidx/car/widget/TextListItem.java
+++ b/car/src/main/java/androidx/car/widget/TextListItem.java
@@ -230,9 +230,7 @@
}
private void setOnClickListener() {
- if (mOnClickListener != null) {
- mBinders.add(vh -> vh.itemView.setOnClickListener(mOnClickListener));
- }
+ mBinders.add(vh -> vh.itemView.setOnClickListener(mOnClickListener));
}
private void setPrimaryIconContent() {
@@ -764,7 +762,7 @@
* @param restrictions current car UX restrictions.
*/
@Override
- public void complyWithUxRestrictions(CarUxRestrictions restrictions) {
+ void complyWithUxRestrictions(CarUxRestrictions restrictions) {
CarUxRestrictionsUtils.comply(itemView.getContext(), restrictions, getBody());
}
diff --git a/collections/build.gradle b/collections/build.gradle
index 3145c4a..a5ebc43 100644
--- a/collections/build.gradle
+++ b/collections/build.gradle
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportJavaLibraryPlugin")
diff --git a/compat/build.gradle b/compat/build.gradle
index f52c5ff..3cb9b06 100644
--- a/compat/build.gradle
+++ b/compat/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -15,7 +15,7 @@
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation project(':support-testutils'), {
+ androidTestImplementation project(':internal-testutils'), {
exclude group: 'com.android.support', module: 'support-compat'
}
}
diff --git a/compat/src/androidTest/java/android/support/v4/widget/ContentLoadingProgressBarTest.java b/compat/src/androidTest/java/android/support/v4/widget/ContentLoadingProgressBarTest.java
index c8bc229..9375aad 100644
--- a/compat/src/androidTest/java/android/support/v4/widget/ContentLoadingProgressBarTest.java
+++ b/compat/src/androidTest/java/android/support/v4/widget/ContentLoadingProgressBarTest.java
@@ -22,7 +22,6 @@
import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import android.view.View;
import org.junit.Before;
@@ -30,6 +29,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.testutils.PollingCheck;
+
/**
* Tests for {@link ContentLoadingProgressBar}
*/
diff --git a/content/build.gradle b/content/build.gradle
index 990f308..ea39651 100644
--- a/content/build.gradle
+++ b/content/build.gradle
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/coordinatorlayout/build.gradle b/coordinatorlayout/build.gradle
index c6ce55b..0f9e7ab 100644
--- a/coordinatorlayout/build.gradle
+++ b/coordinatorlayout/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -15,7 +15,7 @@
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation project(':support-testutils'), {
+ androidTestImplementation project(':internal-testutils'), {
exclude group: 'com.android.support', module: 'support-core-ui'
}
}
diff --git a/coordinatorlayout/src/androidTest/java/android/support/design/widget/BaseTestActivity.java b/coordinatorlayout/src/androidTest/java/android/support/design/widget/BaseTestActivity.java
index 0dd56e4..4272f56 100755
--- a/coordinatorlayout/src/androidTest/java/android/support/design/widget/BaseTestActivity.java
+++ b/coordinatorlayout/src/androidTest/java/android/support/design/widget/BaseTestActivity.java
@@ -18,9 +18,10 @@
import android.os.Bundle;
import android.support.annotation.LayoutRes;
-import android.support.testutils.RecreatedActivity;
import android.view.WindowManager;
+import androidx.testutils.RecreatedActivity;
+
abstract class BaseTestActivity extends RecreatedActivity {
private boolean mDestroyed;
diff --git a/core-ui/build.gradle b/core-ui/build.gradle
index 12f5525..9fd62d1 100644
--- a/core-ui/build.gradle
+++ b/core-ui/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -24,7 +24,7 @@
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation project(':support-testutils'), {
+ androidTestImplementation project(':internal-testutils'), {
exclude group: 'com.android.support', module: 'support-core-ui'
}
diff --git a/core-utils/build.gradle b/core-utils/build.gradle
index ffc0ab8..9f66444 100644
--- a/core-utils/build.gradle
+++ b/core-utils/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/cursoradapter/build.gradle b/cursoradapter/build.gradle
index d08a8bc..2e379a1 100644
--- a/cursoradapter/build.gradle
+++ b/cursoradapter/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index ced95b8..8df42d0 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -14,7 +14,7 @@
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(ESPRESSO_CORE)
- androidTestImplementation(project(":support-testutils"))
+ androidTestImplementation(project(":internal-testutils"))
}
supportLibrary {
diff --git a/customtabs/src/androidTest/java/android/support/customtabs/PostMessageServiceConnectionTest.java b/customtabs/src/androidTest/java/android/support/customtabs/PostMessageServiceConnectionTest.java
index 07d21a8..381afae 100644
--- a/customtabs/src/androidTest/java/android/support/customtabs/PostMessageServiceConnectionTest.java
+++ b/customtabs/src/androidTest/java/android/support/customtabs/PostMessageServiceConnectionTest.java
@@ -26,7 +26,6 @@
import android.support.test.rule.ActivityTestRule;
import android.support.test.rule.ServiceTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import org.junit.Before;
import org.junit.Rule;
@@ -35,6 +34,8 @@
import java.util.concurrent.TimeoutException;
+import androidx.testutils.PollingCheck;
+
/**
* Tests for {@link PostMessageServiceConnection} with no {@link CustomTabsService} component.
*/
diff --git a/customtabs/src/androidTest/java/android/support/customtabs/PostMessageTest.java b/customtabs/src/androidTest/java/android/support/customtabs/PostMessageTest.java
index 7e342b4..6e96b45 100644
--- a/customtabs/src/androidTest/java/android/support/customtabs/PostMessageTest.java
+++ b/customtabs/src/androidTest/java/android/support/customtabs/PostMessageTest.java
@@ -29,7 +29,6 @@
import android.support.test.rule.ActivityTestRule;
import android.support.test.rule.ServiceTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import org.junit.Before;
import org.junit.Rule;
@@ -38,6 +37,8 @@
import java.util.concurrent.atomic.AtomicBoolean;
+import androidx.testutils.PollingCheck;
+
/**
* Tests for a complete loop between a browser side {@link CustomTabsService}
diff --git a/customview/build.gradle b/customview/build.gradle
index 108ec71..421a2e0 100644
--- a/customview/build.gradle
+++ b/customview/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/documentfile/build.gradle b/documentfile/build.gradle
index 2f59518..460332f 100644
--- a/documentfile/build.gradle
+++ b/documentfile/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/drawerlayout/build.gradle b/drawerlayout/build.gradle
index 1d1476b..b4cc482 100644
--- a/drawerlayout/build.gradle
+++ b/drawerlayout/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index 451e874..04d3c33 100644
--- a/dynamic-animation/build.gradle
+++ b/dynamic-animation/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/emoji/appcompat/build.gradle b/emoji/appcompat/build.gradle
index 8a75f66..5c00c8e 100644
--- a/emoji/appcompat/build.gradle
+++ b/emoji/appcompat/build.gradle
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/emoji/bundled/build.gradle b/emoji/bundled/build.gradle
index 7c3a76a..181f893 100644
--- a/emoji/bundled/build.gradle
+++ b/emoji/bundled/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index 449ebb1..4936635 100644
--- a/emoji/core/build.gradle
+++ b/emoji/core/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
import java.util.zip.ZipException
import java.util.zip.ZipFile
@@ -28,7 +28,7 @@
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation project(':support-testutils')
+ androidTestImplementation project(':internal-testutils')
}
android {
diff --git a/emoji/core/src/androidTest/java/android/support/text/emoji/EmojiKeyboardTest.java b/emoji/core/src/androidTest/java/android/support/text/emoji/EmojiKeyboardTest.java
index 1c647ce..aafa0d1 100644
--- a/emoji/core/src/androidTest/java/android/support/text/emoji/EmojiKeyboardTest.java
+++ b/emoji/core/src/androidTest/java/android/support/text/emoji/EmojiKeyboardTest.java
@@ -30,7 +30,6 @@
import android.support.test.filters.Suppress;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import android.support.text.emoji.test.R;
import android.support.text.emoji.util.KeyboardUtil;
import android.support.text.emoji.util.TestString;
@@ -44,6 +43,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.testutils.PollingCheck;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
@Suppress
diff --git a/exifinterface/build.gradle b/exifinterface/build.gradle
index e81d422..1914d9c 100644
--- a/exifinterface/build.gradle
+++ b/exifinterface/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/fragment/build.gradle b/fragment/build.gradle
index 4a3c1d1..9e7f478 100644
--- a/fragment/build.gradle
+++ b/fragment/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -18,7 +18,7 @@
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation project(':support-testutils'), {
+ androidTestImplementation project(':internal-testutils'), {
exclude group: 'com.android.support', module: 'support-fragment'
}
}
diff --git a/fragment/src/androidTest/java/android/support/v4/app/FragmentManagerNonConfigTest.java b/fragment/src/androidTest/java/android/support/v4/app/FragmentManagerNonConfigTest.java
index dc62c01..c3bea05 100644
--- a/fragment/src/androidTest/java/android/support/v4/app/FragmentManagerNonConfigTest.java
+++ b/fragment/src/androidTest/java/android/support/v4/app/FragmentManagerNonConfigTest.java
@@ -21,13 +21,14 @@
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.FragmentActivityUtils;
import android.support.v4.app.test.NonConfigOnStopActivity;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.testutils.FragmentActivityUtils;
+
@MediumTest
@RunWith(AndroidJUnit4.class)
public class FragmentManagerNonConfigTest {
diff --git a/fragment/src/androidTest/java/android/support/v4/app/HangingFragmentTest.java b/fragment/src/androidTest/java/android/support/v4/app/HangingFragmentTest.java
index bf8726f..bb8d1db 100644
--- a/fragment/src/androidTest/java/android/support/v4/app/HangingFragmentTest.java
+++ b/fragment/src/androidTest/java/android/support/v4/app/HangingFragmentTest.java
@@ -19,7 +19,6 @@
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.FragmentActivityUtils;
import android.support.v4.app.test.HangingFragmentActivity;
import org.junit.Assert;
@@ -27,6 +26,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.testutils.FragmentActivityUtils;
@RunWith(AndroidJUnit4.class)
@SmallTest
diff --git a/fragment/src/androidTest/java/android/support/v4/app/LoaderTest.java b/fragment/src/androidTest/java/android/support/v4/app/LoaderTest.java
index 1d89e27..4a1837f 100644
--- a/fragment/src/androidTest/java/android/support/v4/app/LoaderTest.java
+++ b/fragment/src/androidTest/java/android/support/v4/app/LoaderTest.java
@@ -32,8 +32,6 @@
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.FragmentActivityUtils;
-import android.support.testutils.RecreatedActivity;
import android.support.v4.app.test.LoaderActivity;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
@@ -46,6 +44,9 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import androidx.testutils.FragmentActivityUtils;
+import androidx.testutils.RecreatedActivity;
+
@RunWith(AndroidJUnit4.class)
@MediumTest
public class LoaderTest {
diff --git a/fragment/src/androidTest/java/android/support/v4/app/test/HangingFragmentActivity.java b/fragment/src/androidTest/java/android/support/v4/app/test/HangingFragmentActivity.java
index 80b9aa5..bdf29cb 100644
--- a/fragment/src/androidTest/java/android/support/v4/app/test/HangingFragmentActivity.java
+++ b/fragment/src/androidTest/java/android/support/v4/app/test/HangingFragmentActivity.java
@@ -19,7 +19,8 @@
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.fragment.test.R;
-import android.support.testutils.RecreatedActivity;
+
+import androidx.testutils.RecreatedActivity;
public class HangingFragmentActivity extends RecreatedActivity {
diff --git a/fragment/src/androidTest/java/android/support/v4/app/test/LoaderActivity.java b/fragment/src/androidTest/java/android/support/v4/app/test/LoaderActivity.java
index f6ddf9c..1c88dbe 100644
--- a/fragment/src/androidTest/java/android/support/v4/app/test/LoaderActivity.java
+++ b/fragment/src/androidTest/java/android/support/v4/app/test/LoaderActivity.java
@@ -21,7 +21,6 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.fragment.test.R;
-import android.support.testutils.RecreatedActivity;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
@@ -31,6 +30,8 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.testutils.RecreatedActivity;
+
public class LoaderActivity extends RecreatedActivity
implements LoaderManager.LoaderCallbacks<String> {
private static final int TEXT_LOADER_ID = 14;
diff --git a/fragment/src/androidTest/java/android/support/v4/app/test/NonConfigOnStopActivity.java b/fragment/src/androidTest/java/android/support/v4/app/test/NonConfigOnStopActivity.java
index 9d71388..f52ddbd 100644
--- a/fragment/src/androidTest/java/android/support/v4/app/test/NonConfigOnStopActivity.java
+++ b/fragment/src/androidTest/java/android/support/v4/app/test/NonConfigOnStopActivity.java
@@ -16,9 +16,10 @@
package android.support.v4.app.test;
-import android.support.testutils.RecreatedActivity;
import android.support.v4.app.Fragment;
+import androidx.testutils.RecreatedActivity;
+
public class NonConfigOnStopActivity extends RecreatedActivity {
@Override
protected void onStop() {
diff --git a/fragment/src/main/java/android/support/v4/app/DialogFragment.java b/fragment/src/main/java/android/support/v4/app/DialogFragment.java
index b6bbb48..b585863 100644
--- a/fragment/src/main/java/android/support/v4/app/DialogFragment.java
+++ b/fragment/src/main/java/android/support/v4/app/DialogFragment.java
@@ -320,7 +320,8 @@
}
@Override
- public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
+ @NonNull
+ public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
if (!mShowsDialog) {
return super.onGetLayoutInflater(savedInstanceState);
}
@@ -375,7 +376,7 @@
* @return Return a new Dialog instance to be displayed by the Fragment.
*/
@NonNull
- public Dialog onCreateDialog(Bundle savedInstanceState) {
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new Dialog(getActivity(), getTheme());
}
@@ -395,7 +396,7 @@
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!mShowsDialog) {
@@ -436,7 +437,7 @@
}
@Override
- public void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (mDialog != null) {
Bundle dialogState = mDialog.onSaveInstanceState();
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index 2f18053..ffaceff 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 9388480..63282c0 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/heifwriter/build.gradle b/heifwriter/build.gradle
index b66d8a8..133a926 100644
--- a/heifwriter/build.gradle
+++ b/heifwriter/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/interpolator/build.gradle b/interpolator/build.gradle
index 6bc6e04..49c31e2 100644
--- a/interpolator/build.gradle
+++ b/interpolator/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/jetifier/jetifier/core/build.gradle b/jetifier/jetifier/core/build.gradle
index 683a020..5e4f5c0 100644
--- a/jetifier/jetifier/core/build.gradle
+++ b/jetifier/jetifier/core/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License
*/
-import static android.support.dependencies.DependenciesKt.KOTLIN_STDLIB
+import static androidx.build.dependencies.DependenciesKt.KOTLIN_STDLIB
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportKotlinLibraryPlugin")
diff --git a/jetifier/jetifier/gradle-plugin/build.gradle b/jetifier/jetifier/gradle-plugin/build.gradle
index 4a8d0fb..a2b1cfc 100644
--- a/jetifier/jetifier/gradle-plugin/build.gradle
+++ b/jetifier/jetifier/gradle-plugin/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License
*/
-import static android.support.dependencies.DependenciesKt.KOTLIN_STDLIB
+import static androidx.build.dependencies.DependenciesKt.KOTLIN_STDLIB
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportKotlinLibraryPlugin")
diff --git a/leanback/build.gradle b/leanback/build.gradle
index 36ba9e2..da4b168 100644
--- a/leanback/build.gradle
+++ b/leanback/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/lifecycle/common-java8/build.gradle b/lifecycle/common-java8/build.gradle
index bee8b9c..0f4ffd8 100644
--- a/lifecycle/common-java8/build.gradle
+++ b/lifecycle/common-java8/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions;
-import android.support.SupportLibraryExtension;
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions;
+import androidx.build.SupportLibraryExtension;
plugins {
id("SupportJavaLibraryPlugin")
diff --git a/lifecycle/common/build.gradle b/lifecycle/common/build.gradle
index 866a656..89f46a7 100644
--- a/lifecycle/common/build.gradle
+++ b/lifecycle/common/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions;
-import android.support.SupportLibraryExtension;
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions;
+import androidx.build.SupportLibraryExtension;
plugins {
id("SupportJavaLibraryPlugin")
diff --git a/lifecycle/compiler/build.gradle b/lifecycle/compiler/build.gradle
index 534d1ec..fec3d99 100644
--- a/lifecycle/compiler/build.gradle
+++ b/lifecycle/compiler/build.gradle
@@ -1,8 +1,11 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
-apply plugin: android.support.SupportKotlinLibraryPlugin
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
+
+plugins {
+ id("SupportKotlinLibraryPlugin")
+}
sourceSets {
test.java.srcDirs += 'src/tests/kotlin'
diff --git a/lifecycle/extensions/build.gradle b/lifecycle/extensions/build.gradle
index e8ab2f6..2875ab4 100644
--- a/lifecycle/extensions/build.gradle
+++ b/lifecycle/extensions/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/lifecycle/integration-tests/testapp/build.gradle b/lifecycle/integration-tests/testapp/build.gradle
index 25b5b75..c8fba58 100644
--- a/lifecycle/integration-tests/testapp/build.gradle
+++ b/lifecycle/integration-tests/testapp/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidTestAppPlugin")
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
index 836cfff..d5ba9df 100644
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
@@ -116,8 +116,8 @@
mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
-
- mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+ mActivityTestRule.runOnUiThread(() ->
+ mutableLiveData.observe(lifecycleOwner, atomicInteger::set));
final FragmentActivity dialogActivity = launchDialog();
@@ -143,7 +143,8 @@
TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
- mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+ mActivityTestRule.runOnUiThread(() ->
+ mutableLiveData.observe(lifecycleOwner, atomicInteger::set));
// Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
// lifecycleOwner to hit onPause (or enter the STARTED state). On API 24 and above, this
diff --git a/lifecycle/livedata-core/build.gradle b/lifecycle/livedata-core/build.gradle
index 58765a0..becb17e 100644
--- a/lifecycle/livedata-core/build.gradle
+++ b/lifecycle/livedata-core/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java
index 3a753a1..fc8fb31 100644
--- a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java
+++ b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java
@@ -163,6 +163,7 @@
*/
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
+ assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
@@ -195,6 +196,7 @@
*/
@MainThread
public void observeForever(@NonNull Observer<T> observer) {
+ assertMainThread("observeForever");
AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && existing instanceof LiveData.LifecycleBoundObserver) {
diff --git a/lifecycle/livedata/build.gradle b/lifecycle/livedata/build.gradle
index 7047886..cf3b50c 100644
--- a/lifecycle/livedata/build.gradle
+++ b/lifecycle/livedata/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/lifecycle/livedata/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java b/lifecycle/livedata/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java
index d0ff1b2..9130b25 100644
--- a/lifecycle/livedata/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java
+++ b/lifecycle/livedata/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java
@@ -98,9 +98,14 @@
};
final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
//noinspection unchecked
- Observer<Integer> observer = mock(Observer.class);
- computable.getLiveData().observeForever(observer);
- verify(observer, never()).onChanged(anyInt());
+ final Observer<Integer> observer = mock(Observer.class);
+ executor.postToMainThread(new Runnable() {
+ @Override
+ public void run() {
+ computable.getLiveData().observeForever(observer);
+ verify(observer, never()).onChanged(anyInt());
+ }
+ });
// wait for first compute call
assertThat(computeCounter.tryAcquire(1, 2, TimeUnit.SECONDS), is(true));
// re-invalidate while in compute
diff --git a/lifecycle/livedata/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java b/lifecycle/livedata/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java
index e2eadbe..9dbeb44 100644
--- a/lifecycle/livedata/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java
+++ b/lifecycle/livedata/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java
@@ -26,10 +26,12 @@
import static org.mockito.Mockito.when;
import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.executor.testing.InstantTaskExecutorRule;
import android.arch.lifecycle.util.InstantTaskExecutor;
import android.support.annotation.Nullable;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -38,6 +40,9 @@
@RunWith(JUnit4.class)
public class MediatorLiveDataTest {
+ @Rule
+ public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
+
private LifecycleOwner mOwner;
private LifecycleRegistry mRegistry;
private MediatorLiveData<String> mMediator;
diff --git a/lifecycle/reactivestreams/build.gradle b/lifecycle/reactivestreams/build.gradle
index 5c22e4a..12f12b1 100644
--- a/lifecycle/reactivestreams/build.gradle
+++ b/lifecycle/reactivestreams/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/lifecycle/runtime/build.gradle b/lifecycle/runtime/build.gradle
index aee73c0..f7f6ff8 100644
--- a/lifecycle/runtime/build.gradle
+++ b/lifecycle/runtime/build.gradle
@@ -1,7 +1,7 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/lifecycle/viewmodel/build.gradle b/lifecycle/viewmodel/build.gradle
index 914d9ce..1494733 100644
--- a/lifecycle/viewmodel/build.gradle
+++ b/lifecycle/viewmodel/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/loader/build.gradle b/loader/build.gradle
index 45cad2a..b89ac0c 100644
--- a/loader/build.gradle
+++ b/loader/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/localbroadcastmanager/build.gradle b/localbroadcastmanager/build.gradle
index a3d35bf..2709f7d 100644
--- a/localbroadcastmanager/build.gradle
+++ b/localbroadcastmanager/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/media-compat/build.gradle b/media-compat/build.gradle
index 29c91b7..f952731 100644
--- a/media-compat/build.gradle
+++ b/media-compat/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -14,7 +14,7 @@
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation project(':support-testutils')
+ androidTestImplementation project(':internal-testutils')
}
android {
diff --git a/media-compat/version-compat-tests/current/client/build.gradle b/media-compat/version-compat-tests/current/client/build.gradle
index 2a247df..385462f 100644
--- a/media-compat/version-compat-tests/current/client/build.gradle
+++ b/media-compat/version-compat-tests/current/client/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/media-compat/version-compat-tests/current/service/build.gradle b/media-compat/version-compat-tests/current/service/build.gradle
index 2a247df..385462f 100644
--- a/media-compat/version-compat-tests/current/service/build.gradle
+++ b/media-compat/version-compat-tests/current/service/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/media-compat/version-compat-tests/lib/build.gradle b/media-compat/version-compat-tests/lib/build.gradle
index caa6c7e..df9e75c 100644
--- a/media-compat/version-compat-tests/lib/build.gradle
+++ b/media-compat/version-compat-tests/lib/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/media-compat/version-compat-tests/previous/client/build.gradle b/media-compat/version-compat-tests/previous/client/build.gradle
index 31f33fe..4319f69 100644
--- a/media-compat/version-compat-tests/previous/client/build.gradle
+++ b/media-compat/version-compat-tests/previous/client/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/media-compat/version-compat-tests/previous/service/build.gradle b/media-compat/version-compat-tests/previous/service/build.gradle
index 765e406..28c88c3 100644
--- a/media-compat/version-compat-tests/previous/service/build.gradle
+++ b/media-compat/version-compat-tests/previous/service/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index dbe6b06..bc1d3a8 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension;
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension;
plugins {
id("SupportJavaLibraryPlugin")
diff --git a/paging/integration-tests/testapp/build.gradle b/paging/integration-tests/testapp/build.gradle
index d1b0aec..9aab333 100644
--- a/paging/integration-tests/testapp/build.gradle
+++ b/paging/integration-tests/testapp/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
diff --git a/paging/runtime/build.gradle b/paging/runtime/build.gradle
index 4c42b30..20a39d4 100644
--- a/paging/runtime/build.gradle
+++ b/paging/runtime/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/percent/build.gradle b/percent/build.gradle
index 9585323..7209ac2 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/persistence/db-framework/build.gradle b/persistence/db-framework/build.gradle
index bae3d02..15f638b 100644
--- a/persistence/db-framework/build.gradle
+++ b/persistence/db-framework/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/persistence/db/api/current.txt b/persistence/db/api/current.txt
index f96f17a..e0abe20 100644
--- a/persistence/db/api/current.txt
+++ b/persistence/db/api/current.txt
@@ -5,6 +5,7 @@
ctor public SimpleSQLiteQuery(java.lang.String);
method public static void bind(android.arch.persistence.db.SupportSQLiteProgram, java.lang.Object[]);
method public void bindTo(android.arch.persistence.db.SupportSQLiteProgram);
+ method public int getArgCount();
method public java.lang.String getSql();
}
@@ -96,6 +97,7 @@
public abstract interface SupportSQLiteQuery {
method public abstract void bindTo(android.arch.persistence.db.SupportSQLiteProgram);
+ method public abstract int getArgCount();
method public abstract java.lang.String getSql();
}
diff --git a/persistence/db/build.gradle b/persistence/db/build.gradle
index 657ef1e..5c9cb2d 100644
--- a/persistence/db/build.gradle
+++ b/persistence/db/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java b/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
index bcf4f49..13faf24 100644
--- a/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
+++ b/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
@@ -54,6 +54,11 @@
bind(statement, mBindArgs);
}
+ @Override
+ public int getArgCount() {
+ return mBindArgs.length;
+ }
+
/**
* Binds the given arguments into the given sqlite statement.
*
@@ -91,6 +96,8 @@
statement.bindLong(index, (Byte) arg);
} else if (arg instanceof String) {
statement.bindString(index, (String) arg);
+ } else if (arg instanceof Boolean) {
+ statement.bindLong(index, ((Boolean) arg) ? 1 : 0);
} else {
throw new IllegalArgumentException("Cannot bind " + arg + " at index " + index
+ " Supported types: null, byte[], float, double, long, int, short, byte,"
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java b/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java
index 2007634..03e0a91 100644
--- a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java
+++ b/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java
@@ -35,4 +35,12 @@
* @param statement The compiled statement
*/
void bindTo(SupportSQLiteProgram statement);
+
+ /**
+ * Returns the number of arguments in this query. This is equal to the number of placeholders
+ * in the query string. See: https://www.sqlite.org/c3ref/bind_blob.html for details.
+ *
+ * @return The number of arguments in the query.
+ */
+ int getArgCount();
}
diff --git a/preference-leanback/build.gradle b/preference-leanback/build.gradle
index fc88f28..e5c19da 100644
--- a/preference-leanback/build.gradle
+++ b/preference-leanback/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/print/build.gradle b/print/build.gradle
index 78fc9db..d3af888 100644
--- a/print/build.gradle
+++ b/print/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/print/src/main/java/android/support/v4/print/PrintHelper.java b/print/src/main/java/android/support/v4/print/PrintHelper.java
index ce342e3..5f445b7 100644
--- a/print/src/main/java/android/support/v4/print/PrintHelper.java
+++ b/print/src/main/java/android/support/v4/print/PrintHelper.java
@@ -39,6 +39,8 @@
import android.print.PrintManager;
import android.print.pdf.PrintedPdfDocument;
import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.Log;
@@ -136,9 +138,11 @@
int getOrientation();
- void printBitmap(String jobName, Bitmap bitmap, OnPrintFinishCallback callback);
+ void printBitmap(@NonNull String jobName, @NonNull Bitmap bitmap,
+ @Nullable OnPrintFinishCallback callback);
- void printBitmap(String jobName, Uri imageFile, OnPrintFinishCallback callback)
+ void printBitmap(@NonNull String jobName, @NonNull Uri imageFile,
+ @Nullable OnPrintFinishCallback callback)
throws FileNotFoundException;
}
@@ -885,7 +889,7 @@
*
* @param context A context for accessing system resources.
*/
- public PrintHelper(Context context) {
+ public PrintHelper(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 24) {
mImpl = new PrintHelperApi24(context);
} else if (Build.VERSION.SDK_INT >= 23) {
@@ -975,7 +979,7 @@
* @param jobName The print job name.
* @param bitmap The bitmap to print.
*/
- public void printBitmap(String jobName, Bitmap bitmap) {
+ public void printBitmap(@NonNull String jobName, @NonNull Bitmap bitmap) {
mImpl.printBitmap(jobName, bitmap, null);
}
@@ -986,7 +990,8 @@
* @param bitmap The bitmap to print.
* @param callback Optional callback to observe when printing is finished.
*/
- public void printBitmap(String jobName, Bitmap bitmap, OnPrintFinishCallback callback) {
+ public void printBitmap(@NonNull String jobName, @NonNull Bitmap bitmap,
+ @Nullable OnPrintFinishCallback callback) {
mImpl.printBitmap(jobName, bitmap, callback);
}
@@ -999,7 +1004,8 @@
* @param imageFile The <code>Uri</code> pointing to an image to print.
* @throws FileNotFoundException if <code>Uri</code> is not pointing to a valid image.
*/
- public void printBitmap(String jobName, Uri imageFile) throws FileNotFoundException {
+ public void printBitmap(@NonNull String jobName, @NonNull Uri imageFile)
+ throws FileNotFoundException {
mImpl.printBitmap(jobName, imageFile, null);
}
@@ -1013,7 +1019,8 @@
* @throws FileNotFoundException if <code>Uri</code> is not pointing to a valid image.
* @param callback Optional callback to observe when printing is finished.
*/
- public void printBitmap(String jobName, Uri imageFile, OnPrintFinishCallback callback)
+ public void printBitmap(@NonNull String jobName, @NonNull Uri imageFile,
+ @Nullable OnPrintFinishCallback callback)
throws FileNotFoundException {
mImpl.printBitmap(jobName, imageFile, callback);
}
diff --git a/recommendation/build.gradle b/recommendation/build.gradle
index b401eb8..4f4a737 100644
--- a/recommendation/build.gradle
+++ b/recommendation/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/recyclerview-selection/build.gradle b/recyclerview-selection/build.gradle
index c0915bc..6db338f 100644
--- a/recyclerview-selection/build.gradle
+++ b/recyclerview-selection/build.gradle
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/room/common/build.gradle b/room/common/build.gradle
index 9457308..1bf6652 100644
--- a/room/common/build.gradle
+++ b/room/common/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension;
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension;
plugins {
id("SupportJavaLibraryPlugin")
diff --git a/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java b/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
index 32b5818..8d0b7ce 100644
--- a/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
+++ b/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
@@ -17,6 +17,7 @@
package android.arch.persistence.room;
import android.support.annotation.IntDef;
+import android.support.annotation.RequiresApi;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -146,12 +147,14 @@
*
* @see #collate()
*/
+ @RequiresApi(21)
int LOCALIZED = 5;
/**
* Collation sequence that uses Unicode Collation Algorithm.
*
* @see #collate()
*/
+ @RequiresApi(21)
int UNICODE = 6;
@IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM, LOCALIZED, UNICODE})
diff --git a/room/compiler/build.gradle b/room/compiler/build.gradle
index c3319ac..3599893 100644
--- a/room/compiler/build.gradle
+++ b/room/compiler/build.gradle
@@ -15,14 +15,16 @@
*/
-import android.support.SupportConfig
+import androidx.build.SupportConfig
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
-apply plugin: android.support.SupportKotlinLibraryPlugin
+plugins {
+ id("SupportKotlinLibraryPlugin")
+}
def antlrOut = "$buildDir/generated/antlr/grammar-gen/"
sourceSets {
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 02e299b..3e7dd9c 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
@@ -146,9 +146,10 @@
" queries."
val OBSERVABLE_QUERY_NOTHING_TO_OBSERVE = "Observable query return type (LiveData, Flowable" +
- " etc) can only be used with SELECT queries that directly or indirectly (via" +
- " @Relation, for example) access at least one table. For @RawQuery, you should" +
- " specify the list of tables to be observed via the observedEntities field."
+ ", DataSource, DataSourceFactory etc) can only be used with SELECT queries that" +
+ " directly or indirectly (via @Relation, for example) access at least one table. For" +
+ " @RawQuery, you should specify the list of tables to be observed via the" +
+ " observedEntities field."
val RECURSIVE_REFERENCE_DETECTED = "Recursive referencing through @Embedded and/or @Relation " +
"detected: %s"
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt
index 4d0a280..dc11428 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt
@@ -19,10 +19,11 @@
import android.arch.persistence.room.ext.PagingTypeNames
import android.arch.persistence.room.parser.ParsedQuery
import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.processor.ProcessorErrors
import android.arch.persistence.room.solver.QueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.PositionalDataSourceQueryResultBinder
import android.arch.persistence.room.solver.query.result.ListQueryResultAdapter
import android.arch.persistence.room.solver.query.result.LivePagedListQueryResultBinder
+import android.arch.persistence.room.solver.query.result.PositionalDataSourceQueryResultBinder
import android.arch.persistence.room.solver.query.result.QueryResultBinder
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeMirror
@@ -34,6 +35,9 @@
}
override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+ if (query.tables.isEmpty()) {
+ context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+ }
val typeArg = declared.typeArguments[1]
val listAdapter = context.typeAdapterStore.findRowAdapter(typeArg, query)?.let {
ListQueryResultAdapter(it)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt
index c13354e..0fc9dce 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt
@@ -39,6 +39,9 @@
}
override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+ if (query.tables.isEmpty()) {
+ context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+ }
val typeArg = declared.typeArguments.last()
val listAdapter = context.typeAdapterStore.findRowAdapter(typeArg, query)?.let {
ListQueryResultAdapter(it)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
index ac5a256..4530332 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
@@ -42,8 +42,11 @@
dbField: FieldSpec,
inTransaction: Boolean,
scope: CodeGenScope) {
- val tableNamesList = tableNames.joinToString(",") { "\"$it\"" }
- val spec = TypeSpec.anonymousClassBuilder("$N, $L, $L, $L",
+ // first comma for table names comes from the string since it might be empty in which case
+ // we don't need a comma. If list is empty, this prevents generating bad code (it is still
+ // an error to have empty list but that is already reported while item is processed)
+ val tableNamesList = tableNames.joinToString { ",\"$it\"" }
+ val spec = TypeSpec.anonymousClassBuilder("$N, $L, $L $L",
dbField, roomSQLiteQueryVar, inTransaction, tableNamesList).apply {
superclass(typeName)
addMethod(createConvertRowsMethod(scope))
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt
index 95130e8..512770a 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt
@@ -44,8 +44,8 @@
}
private fun createSelect(resultFields: Set<String>): String {
- return "SELECT ${resultFields.joinToString(",")}" +
+ return "SELECT ${resultFields.joinToString(",") {"`$it`"}}" +
" FROM `${entity.tableName}`" +
- " WHERE ${entityField.columnName} IN (:args)"
+ " WHERE `${entityField.columnName}` IN (:args)"
}
}
diff --git a/room/compiler/src/test/data/common/input/PositionalDataSource.java b/room/compiler/src/test/data/common/input/PositionalDataSource.java
new file mode 100644
index 0000000..7cff0d7
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/PositionalDataSource.java
@@ -0,0 +1,5 @@
+package android.arch.paging;
+
+public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
+
+}
\ No newline at end of file
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 3eb629a..17e8ad9 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
@@ -24,6 +24,7 @@
import android.arch.persistence.room.Query
import android.arch.persistence.room.RawQuery
import android.arch.persistence.room.ext.CommonTypeNames
+import android.arch.persistence.room.ext.PagingTypeNames
import android.arch.persistence.room.ext.SupportDbTypeNames
import android.arch.persistence.room.ext.hasAnnotation
import android.arch.persistence.room.ext.typeName
@@ -123,6 +124,42 @@
}
@Test
+ fun observableWithoutEntities_dataSourceFactory() {
+ singleQueryMethod(
+ """
+ @RawQuery
+ abstract public ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, User> getOne();
+ """) { _, _ ->
+ // do nothing
+ }.failsToCompile()
+ .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+ }
+
+ @Test
+ fun observableWithoutEntities_positionalDataSource() {
+ singleQueryMethod(
+ """
+ @RawQuery
+ abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne();
+ """) { _, _ ->
+ // do nothing
+ }.failsToCompile()
+ .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+ }
+
+ @Test
+ fun positionalDataSource() {
+ singleQueryMethod(
+ """
+ @RawQuery(observedEntities = {User.class})
+ abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne(
+ SupportSQLiteQuery query);
+ """) { _, _ ->
+ // do nothing
+ }.compilesWithoutError()
+ }
+
+ @Test
fun pojo() {
val pojo: TypeName = ClassName.get("foo.bar.MyClass", "MyPojo")
singleQueryMethod(
@@ -204,10 +241,11 @@
DAO_PREFIX
+ input.joinToString("\n")
+ DAO_SUFFIX
- ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER))
+ ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER,
+ COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE))
.processedWith(TestProcessor.builder()
.forAnnotations(Query::class, Dao::class, ColumnInfo::class,
- Entity::class, PrimaryKey::class, RawQueryMethod::class)
+ Entity::class, PrimaryKey::class, RawQuery::class)
.nextRunHandler { invocation ->
val (owner, methods) = invocation.roundEnv
.getElementsAnnotatedWith(Dao::class.java)
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt
index b657643..c0167b3 100644
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt
@@ -21,6 +21,7 @@
import android.arch.persistence.room.Query
import android.arch.persistence.room.Relation
import android.arch.persistence.room.ext.LifecyclesTypeNames
+import android.arch.persistence.room.ext.PagingTypeNames
import android.arch.persistence.room.ext.ReactiveStreamsTypeNames
import android.arch.persistence.room.ext.RoomRxJava2TypeNames
import android.arch.persistence.room.ext.RxJava2TypeNames
@@ -88,6 +89,11 @@
val DATA_SOURCE_FACTORY by lazy {
loadJavaCode("common/input/DataSource.java", "android.arch.paging.DataSource")
}
+
+ val POSITIONAL_DATA_SOURCE by lazy {
+ loadJavaCode("common/input/PositionalDataSource.java",
+ PagingTypeNames.POSITIONAL_DATA_SOURCE.toString())
+ }
}
fun testCodeGenScope(): CodeGenScope {
return CodeGenScope(Mockito.mock(ClassWriter::class.java))
diff --git a/room/guava/build.gradle b/room/guava/build.gradle
index 8660aae..3837406 100644
--- a/room/guava/build.gradle
+++ b/room/guava/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index 1dc0a79..162f325 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -17,7 +17,7 @@
//./gradlew :r:in:k:clean :r:in:k:cC --no-daemon
// -Dorg.gradle.debug=true
// -Dkotlin.compiler.execution.strategy="in-process"
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidTestAppPlugin")
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 45ec9c6..401cc07 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidTestAppPlugin")
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 7cb8b60..1db5b46 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -18,11 +18,13 @@
import android.arch.lifecycle.LiveData;
import android.arch.paging.DataSource;
+import android.arch.persistence.db.SupportSQLiteQuery;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
+import android.arch.persistence.room.RawQuery;
import android.arch.persistence.room.Transaction;
import android.arch.persistence.room.Update;
import android.arch.persistence.room.integration.testapp.TestDatabase;
@@ -116,6 +118,10 @@
@Query("select * from user where mId = :id")
public abstract LiveData<User> liveUserById(int id);
+ @Transaction
+ @Query("select * from user where mId = :id")
+ public abstract LiveData<User> liveUserByIdInTransaction(int id);
+
@Query("select * from user where mName LIKE '%' || :name || '%' ORDER BY mId DESC")
public abstract LiveData<List<User>> liveUsersListByName(String name);
@@ -194,6 +200,10 @@
@Query("SELECT * FROM user where mAge > :age")
public abstract DataSource.Factory<Integer, User> loadPagedByAge(int age);
+ @RawQuery(observedEntities = User.class)
+ public abstract DataSource.Factory<Integer, User> loadPagedByAgeWithObserver(
+ SupportSQLiteQuery query);
+
// TODO: switch to PositionalDataSource once Room supports it
@Query("SELECT * FROM user ORDER BY mAge DESC")
public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
index 0f68656..587367f 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
@@ -30,6 +30,7 @@
import android.arch.lifecycle.Observer;
import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;
+import android.arch.persistence.db.SimpleSQLiteQuery;
import android.arch.persistence.room.integration.testapp.test.TestDatabaseTest;
import android.arch.persistence.room.integration.testapp.test.TestUtil;
import android.arch.persistence.room.integration.testapp.vo.User;
@@ -41,7 +42,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
@@ -69,7 +69,23 @@
.build());
}
- private void validateUsersAsPagedList(LivePagedListFactory factory)
+ @Test
+ public void getUsersAsPagedList_ViaRawQuery_WithObservable()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ SimpleSQLiteQuery query = new SimpleSQLiteQuery(
+ "SELECT * FROM user where mAge > ?",
+ new Object[]{3});
+ validateUsersAsPagedList(() -> new LivePagedListBuilder<>(
+ mUserDao.loadPagedByAgeWithObserver(query),
+ new PagedList.Config.Builder()
+ .setPageSize(10)
+ .setPrefetchDistance(1)
+ .setInitialLoadSizeHint(10).build())
+ .build());
+ }
+
+ private void validateUsersAsPagedList(
+ LivePagedListFactory factory)
throws InterruptedException, ExecutionException, TimeoutException {
mDatabase.beginTransaction();
try {
@@ -135,13 +151,10 @@
private void observe(final LiveData liveData, final LifecycleOwner provider,
final Observer observer) throws ExecutionException, InterruptedException {
- FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- //noinspection unchecked
- liveData.observe(provider, observer);
- return null;
- }
+ FutureTask<Void> futureTask = new FutureTask<>(() -> {
+ //noinspection unchecked
+ liveData.observe(provider, observer);
+ return null;
});
ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
futureTask.get();
@@ -167,6 +180,7 @@
private static class PagedListObserver<T> implements Observer<PagedList<T>> {
private PagedList<T> mList;
+
void reset() {
mList = null;
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CollationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CollationTest.java
index 7b0e933..f81cb70 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CollationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CollationTest.java
@@ -27,6 +27,7 @@
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -83,6 +84,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 21)
public void localized_asUnicode() {
initDao(Locale.getDefault());
List<CollateEntity> result = mDao.sortedByLocalizedAsUnicode();
@@ -101,6 +103,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 21)
public void unicode() {
initDao(Locale.getDefault());
List<CollateEntity> result = mDao.sortedByUnicode();
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DaoNameConflictTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DaoNameConflictTest.java
index 1b8bff4..07c0369 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DaoNameConflictTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DaoNameConflictTest.java
@@ -36,8 +36,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Objects;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DaoNameConflictTest {
@@ -89,14 +87,17 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item1 item1 = (Item1) o;
- return id == item1.id
- && Objects.equals(name, item1.name);
+
+ //noinspection SimplifiableIfStatement
+ if (id != item1.id) return false;
+ return name != null ? name.equals(item1.name) : item1.name == null;
}
@Override
public int hashCode() {
-
- return Objects.hash(id, name);
+ int result = id;
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ return result;
}
}
@@ -124,14 +125,17 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item2 item2 = (Item2) o;
- return id == item2.id
- && Objects.equals(name, item2.name);
+
+ //noinspection SimplifiableIfStatement
+ if (id != item2.id) return false;
+ return name != null ? name.equals(item2.name) : item2.name == null;
}
@Override
public int hashCode() {
-
- return Objects.hash(id, name);
+ int result = id;
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ return result;
}
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
index f4fca7f..dcefa41 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
@@ -22,9 +22,8 @@
import static org.hamcrest.MatcherAssert.assertThat;
import android.arch.core.executor.testing.CountingTaskExecutorRule;
-import android.arch.lifecycle.Observer;
import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
-import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -71,12 +70,9 @@
@Test
public void observe() throws TimeoutException, InterruptedException {
final FunnyNamedEntity[] item = new FunnyNamedEntity[1];
- mFunnyNamedDao.observableOne(2).observeForever(new Observer<FunnyNamedEntity>() {
- @Override
- public void onChanged(@Nullable FunnyNamedEntity funnyNamedEntity) {
- item[0] = funnyNamedEntity;
- }
- });
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mFunnyNamedDao.observableOne(2).observeForever(
+ funnyNamedEntity -> item[0] = funnyNamedEntity));
FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
mFunnyNamedDao.insert(entity);
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/GenericEntityTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/GenericEntityTest.java
index 6db28d4..8efb7c7 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/GenericEntityTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/GenericEntityTest.java
@@ -37,8 +37,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Objects;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class GenericEntityTest {
@@ -98,14 +96,16 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item<?, ?> item = (Item<?, ?>) o;
- return Objects.equals(id, item.id)
- && Objects.equals(mField, item.mField);
+ //noinspection SimplifiableIfStatement
+ if (!id.equals(item.id)) return false;
+ return mField != null ? mField.equals(item.mField) : item.mField == null;
}
@Override
public int hashCode() {
-
- return Objects.hash(id, mField);
+ int result = id.hashCode();
+ result = 31 * result + (mField != null ? mField.hashCode() : 0);
+ return result;
}
}
@@ -128,14 +128,16 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- EntityItem item = (EntityItem) o;
- return Objects.equals(name, item.name);
+ if (!super.equals(o)) return false;
+ EntityItem that = (EntityItem) o;
+ return name != null ? name.equals(that.name) : that.name == null;
}
@Override
public int hashCode() {
-
- return Objects.hash(name);
+ int result = super.hashCode();
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ return result;
}
}
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 4aae4ea..d2cf37b 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
@@ -29,6 +29,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;
@@ -71,9 +72,10 @@
@Test
public void entity_liveData() throws TimeoutException, InterruptedException {
- LiveData<User> liveData = mRawDao.getUserLiveData("SELECT * FROM User WHERE mId = 3");
- liveData.observeForever(user -> {
- });
+ final LiveData<User> liveData = mRawDao.getUserLiveData("SELECT * FROM User WHERE mId = 3");
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> liveData.observeForever(user -> { }));
+
drain();
assertThat(liveData.getValue(), is(nullValue()));
User user = TestUtil.createUser(3);
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RelationWithReservedKeywordTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RelationWithReservedKeywordTest.java
new file mode 100644
index 0000000..b01ed45
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RelationWithReservedKeywordTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018 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.MatcherAssert.assertThat;
+
+import static java.util.Collections.singletonList;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.arch.persistence.room.Index;
+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.Transaction;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RelationWithReservedKeywordTest {
+ private MyDatabase mDb;
+
+ @Before
+ public void initDb() {
+ mDb = Room.inMemoryDatabaseBuilder(
+ InstrumentationRegistry.getTargetContext(),
+ MyDatabase.class).build();
+ }
+
+ @Test
+ public void loadRelation() {
+ Category category = new Category(1, "cat1");
+ mDb.getDao().insert(category);
+ Topic topic = new Topic(2, 1, "foo");
+ mDb.getDao().insert(topic);
+ List<CategoryWithTopics> categoryWithTopics = mDb.getDao().loadAll();
+ assertThat(categoryWithTopics.size(), is(1));
+ assertThat(categoryWithTopics.get(0).category, is(category));
+ assertThat(categoryWithTopics.get(0).topics, is(singletonList(topic)));
+ }
+
+ @Entity(tableName = "categories")
+ static class Category {
+
+ @PrimaryKey(autoGenerate = true)
+ public final long id;
+
+ public final String name;
+
+ Category(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Category category = (Category) o;
+ //noinspection SimplifiableIfStatement
+ if (id != category.id) return false;
+ return name != null ? name.equals(category.name) : category.name == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (id ^ (id >>> 32));
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ return result;
+ }
+ }
+
+ @Dao
+ interface MyDao {
+ @Transaction
+ @Query("SELECT * FROM categories")
+ List<CategoryWithTopics> loadAll();
+
+ @Insert
+ void insert(Category... categories);
+
+ @Insert
+ void insert(Topic... topics);
+ }
+
+ @Database(
+ entities = {Category.class, Topic.class},
+ version = 1,
+ exportSchema = false)
+ abstract static class MyDatabase extends RoomDatabase {
+ abstract MyDao getDao();
+ }
+
+
+ @SuppressWarnings("WeakerAccess")
+ static class CategoryWithTopics {
+ @Embedded
+ public Category category;
+
+ @Relation(
+ parentColumn = "id",
+ entityColumn = "category_id",
+ entity = Topic.class)
+ public List<Topic> topics;
+ }
+
+ @Entity(
+ tableName = "topics",
+ foreignKeys = @ForeignKey(
+ entity = Category.class,
+ parentColumns = "id",
+ childColumns = "category_id",
+ onDelete = ForeignKey.CASCADE),
+ indices = @Index("category_id"))
+ static class Topic {
+
+ @PrimaryKey(autoGenerate = true)
+ public final long id;
+
+ @ColumnInfo(name = "category_id")
+ public final long categoryId;
+
+ public final String to;
+
+ Topic(long id, long categoryId, String to) {
+ this.id = id;
+ this.categoryId = categoryId;
+ this.to = to;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Topic topic = (Topic) o;
+ if (id != topic.id) return false;
+ //noinspection SimplifiableIfStatement
+ if (categoryId != topic.categoryId) return false;
+ return to != null ? to.equals(topic.to) : topic.to == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (id ^ (id >>> 32));
+ result = 31 * result + (int) (categoryId ^ (categoryId >>> 32));
+ result = 31 * result + (to != null ? to.hashCode() : 0);
+ return result;
+ }
+ }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WriteAheadLoggingTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WriteAheadLoggingTest.java
index 8d8e0e8..5bc01ce 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WriteAheadLoggingTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WriteAheadLoggingTest.java
@@ -17,10 +17,12 @@
package android.arch.persistence.room.integration.testapp.test;
import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;
+import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
@@ -39,6 +41,7 @@
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SdkSuppress;
import android.support.test.runner.AndroidJUnit4;
@@ -125,7 +128,18 @@
LiveData<User> user1 = dao.liveUserById(1);
Observer<User> observer = startObserver(user1);
dao.insert(TestUtil.createUser(1));
- verify(observer, timeout(30000).atLeastOnce())
+ verify(observer, timeout(3000).atLeastOnce())
+ .onChanged(argThat(user -> user != null && user.getId() == 1));
+ stopObserver(user1, observer);
+ }
+
+ @Test
+ public void observeLiveDataWithTransaction() {
+ UserDao dao = mDatabase.getUserDao();
+ LiveData<User> user1 = dao.liveUserByIdInTransaction(1);
+ Observer<User> observer = startObserver(user1);
+ dao.insert(TestUtil.createUser(1));
+ verify(observer, timeout(3000).atLeastOnce())
.onChanged(argThat(user -> user != null && user.getId() == 1));
stopObserver(user1, observer);
}
@@ -159,15 +173,12 @@
final UserDao dao = mDatabase.getUserDao();
final User user1 = TestUtil.createUser(1);
dao.insert(user1);
- Future<Boolean> future;
try {
mDatabase.beginTransaction();
dao.delete(user1);
ExecutorService executor = Executors.newSingleThreadExecutor();
- future = executor.submit(() -> {
- assertThat(dao.load(1), is(equalTo(user1)));
- return true;
- });
+ Future<?> future = executor.submit(() ->
+ assertThat(dao.load(1), is(equalTo(user1))));
future.get();
mDatabase.setTransactionSuccessful();
} finally {
@@ -177,21 +188,52 @@
}
@Test
+ @LargeTest
+ public void observeInvalidationInBackground() throws InterruptedException, ExecutionException {
+ final UserDao dao = mDatabase.getUserDao();
+ final User user1 = TestUtil.createUser(1);
+ final CountDownLatch observerRegistered = new CountDownLatch(1);
+ final CountDownLatch onInvalidatedCalled = new CountDownLatch(1);
+ dao.insert(user1);
+ Future future;
+ try {
+ mDatabase.beginTransaction();
+ dao.delete(user1);
+ future = Executors.newSingleThreadExecutor().submit(() -> {
+ // Adding this observer will be blocked by the surrounding transaction.
+ mDatabase.getInvalidationTracker().addObserver(
+ new InvalidationTracker.Observer("User") {
+ @Override
+ public void onInvalidated(@NonNull Set<String> tables) {
+ onInvalidatedCalled.countDown(); // This should not happen
+ }
+ });
+ observerRegistered.countDown();
+ });
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ assertThat(observerRegistered.getCount(), is(1L));
+ mDatabase.endTransaction();
+ }
+ assertThat(dao.count(), is(0));
+ assertThat(observerRegistered.await(3000, TimeUnit.MILLISECONDS), is(true));
+ future.get();
+ assertThat(onInvalidatedCalled.await(500, TimeUnit.MILLISECONDS), is(false));
+ }
+
+ @Test
public void invalidation() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
mDatabase.getInvalidationTracker().addObserver(new InvalidationTracker.Observer("User") {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
+ assertThat(tables, hasSize(1));
+ assertThat(tables, hasItem("User"));
latch.countDown();
}
});
mDatabase.getUserDao().insert(TestUtil.createUser(1));
- latch.await(3000, TimeUnit.MILLISECONDS);
- for (int i = 0; i < 10; i++) {
- // This can (occasionally) detect if there is an recursive loop in InvalidationTracker
- // invalidating itself by running its refresh query in a transaction.
- assertThat(mDatabase.inTransaction(), is(false));
- }
+ assertThat(latch.await(3000, TimeUnit.MILLISECONDS), is(true));
}
private static <T> Observer<T> startObserver(LiveData<T> liveData) {
diff --git a/room/migration/build.gradle b/room/migration/build.gradle
index acadb71..2b683e8 100644
--- a/room/migration/build.gradle
+++ b/room/migration/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension;
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension;
plugins {
id("SupportJavaLibraryPlugin")
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index 1022616..739725c 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java b/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java
index 86054ce..7bb4fb2 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java
@@ -159,6 +159,7 @@
} finally {
database.endTransaction();
}
+ syncTriggers(database);
mCleanupStatement = database.compileStatement(CLEANUP_SQL);
mInitialized = true;
}
@@ -219,6 +220,7 @@
*
* @param observer The observer which listens the database for changes.
*/
+ @WorkerThread
public void addObserver(@NonNull Observer observer) {
final String[] tableNames = observer.mTables;
int[] tableIds = new int[tableNames.length];
@@ -240,7 +242,7 @@
currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
}
if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
- ArchTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+ syncTriggers();
}
}
@@ -265,65 +267,17 @@
* @param observer The observer to remove.
*/
@SuppressWarnings("WeakerAccess")
+ @WorkerThread
public void removeObserver(@NonNull final Observer observer) {
ObserverWrapper wrapper;
synchronized (mObserverMap) {
wrapper = mObserverMap.remove(observer);
}
if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
- ArchTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+ syncTriggers();
}
}
- private Runnable mSyncTriggers = new Runnable() {
- @Override
- public void run() {
- if (mDatabase.inTransaction()) {
- // we won't run this inside another transaction.
- return;
- }
- if (!ensureInitialization()) {
- return;
- }
- try {
- // This method runs in a while loop because while changes are synced to db, another
- // runnable may be skipped. If we cause it to skip, we need to do its work.
- while (true) {
- // there is a potential race condition where another mSyncTriggers runnable
- // can start running right after we get the tables list to sync.
- final int[] tablesToSync = mObservedTableTracker.getTablesToSync();
- if (tablesToSync == null) {
- return;
- }
- final int limit = tablesToSync.length;
- final SupportSQLiteDatabase writableDatabase = mDatabase.getOpenHelper()
- .getWritableDatabase();
- try {
- writableDatabase.beginTransaction();
- for (int tableId = 0; tableId < limit; tableId++) {
- switch (tablesToSync[tableId]) {
- case ObservedTableTracker.ADD:
- startTrackingTable(writableDatabase, tableId);
- break;
- case ObservedTableTracker.REMOVE:
- stopTrackingTable(writableDatabase, tableId);
- break;
- }
- }
- writableDatabase.setTransactionSuccessful();
- } finally {
- writableDatabase.endTransaction();
- }
- mObservedTableTracker.onSyncCompleted();
- }
- } catch (IllegalStateException | SQLiteException exception) {
- // may happen if db is closed. just log.
- Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
- exception);
- }
- }
- };
-
private boolean ensureInitialization() {
if (!mDatabase.isOpen()) {
return false;
@@ -444,6 +398,53 @@
mRefreshRunnable.run();
}
+ void syncTriggers(SupportSQLiteDatabase database) {
+ if (database.inTransaction()) {
+ // we won't run this inside another transaction.
+ return;
+ }
+ try {
+ // This method runs in a while loop because while changes are synced to db, another
+ // runnable may be skipped. If we cause it to skip, we need to do its work.
+ while (true) {
+ Lock closeLock = mDatabase.getCloseLock();
+ closeLock.lock();
+ try {
+ // there is a potential race condition where another mSyncTriggers runnable
+ // can start running right after we get the tables list to sync.
+ final int[] tablesToSync = mObservedTableTracker.getTablesToSync();
+ if (tablesToSync == null) {
+ return;
+ }
+ final int limit = tablesToSync.length;
+ try {
+ database.beginTransaction();
+ for (int tableId = 0; tableId < limit; tableId++) {
+ switch (tablesToSync[tableId]) {
+ case ObservedTableTracker.ADD:
+ startTrackingTable(database, tableId);
+ break;
+ case ObservedTableTracker.REMOVE:
+ stopTrackingTable(database, tableId);
+ break;
+ }
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ mObservedTableTracker.onSyncCompleted();
+ } finally {
+ closeLock.unlock();
+ }
+ }
+ } catch (IllegalStateException | SQLiteException exception) {
+ // may happen if db is closed. just log.
+ Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
+ exception);
+ }
+ }
+
/**
* Called by RoomDatabase before each beginTransaction call.
* <p>
@@ -453,7 +454,10 @@
* This api should eventually be public.
*/
void syncTriggers() {
- mSyncTriggers.run();
+ if (!mDatabase.isOpen()) {
+ return;
+ }
+ syncTriggers(mDatabase.getOpenHelper().getWritableDatabase());
}
/**
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
index b2a9f0e..90131ed 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
@@ -243,8 +243,9 @@
*/
public void beginTransaction() {
assertNotMainThread();
- mInvalidationTracker.syncTriggers();
- mOpenHelper.getWritableDatabase().beginTransaction();
+ SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
+ mInvalidationTracker.syncTriggers(database);
+ database.beginTransaction();
}
/**
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java
index a10cc52..c4ff4bd 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java
@@ -79,6 +79,55 @@
static final TreeMap<Integer, RoomSQLiteQuery> sQueryPool = new TreeMap<>();
/**
+ * Copies the given SupportSQLiteQuery and converts it into RoomSQLiteQuery.
+ *
+ * @param supportSQLiteQuery The query to copy from
+ * @return A new query copied from the provided one.
+ */
+ public static RoomSQLiteQuery copyFrom(SupportSQLiteQuery supportSQLiteQuery) {
+ final RoomSQLiteQuery query = RoomSQLiteQuery.acquire(
+ supportSQLiteQuery.getSql(),
+ supportSQLiteQuery.getArgCount());
+ supportSQLiteQuery.bindTo(new SupportSQLiteProgram() {
+ @Override
+ public void bindNull(int index) {
+ query.bindNull(index);
+ }
+
+ @Override
+ public void bindLong(int index, long value) {
+ query.bindLong(index, value);
+ }
+
+ @Override
+ public void bindDouble(int index, double value) {
+ query.bindDouble(index, value);
+ }
+
+ @Override
+ public void bindString(int index, String value) {
+ query.bindString(index, value);
+ }
+
+ @Override
+ public void bindBlob(int index, byte[] value) {
+ query.bindBlob(index, value);
+ }
+
+ @Override
+ public void clearBindings() {
+ query.clearBindings();
+ }
+
+ @Override
+ public void close() {
+ // ignored.
+ }
+ });
+ return query;
+ }
+
+ /**
* Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the
* given query.
*
@@ -152,6 +201,7 @@
return mQuery;
}
+ @Override
public int getArgCount() {
return mArgCount;
}
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 baa5b43..73777c4 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
@@ -17,6 +17,7 @@
package android.arch.persistence.room.paging;
import android.arch.paging.PositionalDataSource;
+import android.arch.persistence.db.SupportSQLiteQuery;
import android.arch.persistence.room.InvalidationTracker;
import android.arch.persistence.room.RoomDatabase;
import android.arch.persistence.room.RoomSQLiteQuery;
@@ -52,6 +53,11 @@
private final InvalidationTracker.Observer mObserver;
private final boolean mInTransaction;
+ protected LimitOffsetDataSource(RoomDatabase db, SupportSQLiteQuery query,
+ boolean inTransaction, String... tables) {
+ this(db, RoomSQLiteQuery.copyFrom(query), inTransaction, tables);
+ }
+
protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
boolean inTransaction, String... tables) {
mDb = db;
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java b/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java
index d7474fd..ca091d5 100644
--- a/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java
+++ b/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java
@@ -125,13 +125,10 @@
public void addRemoveObserver() throws Exception {
InvalidationTracker.Observer observer = new LatchObserver(1, "a");
mTracker.addObserver(observer);
- drainTasks();
assertThat(mTracker.mObserverMap.size(), is(1));
mTracker.removeObserver(new LatchObserver(1, "a"));
- drainTasks();
assertThat(mTracker.mObserverMap.size(), is(1));
mTracker.removeObserver(observer);
- drainTasks();
assertThat(mTracker.mObserverMap.size(), is(0));
}
@@ -241,9 +238,9 @@
@Test
public void closedDb() {
+ doReturn(false).when(mRoomDatabase).isOpen();
doThrow(new IllegalStateException("foo")).when(mOpenHelper).getWritableDatabase();
mTracker.addObserver(new LatchObserver(1, "a", "b"));
- mTracker.syncTriggers();
mTracker.mRefreshRunnable.run();
}
diff --git a/room/rxjava2/build.gradle b/room/rxjava2/build.gradle
index d3d7bc9..8dcaa6b 100644
--- a/room/rxjava2/build.gradle
+++ b/room/rxjava2/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/room/testing/build.gradle b/room/testing/build.gradle
index d584b54..48ac8a7 100644
--- a/room/testing/build.gradle
+++ b/room/testing/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-import android.support.SupportLibraryExtension
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/samples/SupportSliceDemos/build.gradle b/samples/SupportSliceDemos/build.gradle
index a0b236d..4eb590e 100644
--- a/samples/SupportSliceDemos/build.gradle
+++ b/samples/SupportSliceDemos/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidTestAppPlugin")
diff --git a/samples/ViewPager2Demos/OWNERS b/samples/ViewPager2Demos/OWNERS
new file mode 100644
index 0000000..e24ee8b
--- /dev/null
+++ b/samples/ViewPager2Demos/OWNERS
@@ -0,0 +1 @@
+jgielzak@google.com
\ No newline at end of file
diff --git a/samples/ViewPager2Demos/src/main/AndroidManifest.xml b/samples/ViewPager2Demos/src/main/AndroidManifest.xml
index 57e52d3..71d115e 100644
--- a/samples/ViewPager2Demos/src/main/AndroidManifest.xml
+++ b/samples/ViewPager2Demos/src/main/AndroidManifest.xml
@@ -16,7 +16,7 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.androidx.widget.viewpager2">
+ package="com.example.androidx.viewpager2">
<application
android:icon="@drawable/app_sample_code"
@@ -31,6 +31,13 @@
</intent-filter>
</activity>
+ <activity android:name=".CardFragmentActivity" android:label="CardsFragmentDemo">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.SAMPLE_CODE"/>
+ </intent-filter>
+ </activity>
+
<activity android:name=".BrowseActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/BrowseActivity.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/BrowseActivity.java
similarity index 97%
rename from samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/BrowseActivity.java
rename to samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/BrowseActivity.java
index 6c36994..41e4f99 100644
--- a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/BrowseActivity.java
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/BrowseActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.example.androidx.widget.viewpager2;
+package com.example.androidx.viewpager2;
import android.app.ListActivity;
import android.content.Intent;
@@ -45,7 +45,7 @@
super.onCreate(savedInstanceState);
Intent intent = getIntent();
- String path = intent.getStringExtra("com.example.androidx.widget.viewpager2");
+ String path = intent.getStringExtra("com.example.androidx.viewpager2");
if (path == null) {
path = "";
@@ -131,7 +131,7 @@
protected Intent browseIntent(String path) {
Intent result = new Intent();
result.setClass(this, BrowseActivity.class);
- result.putExtra("com.example.androidx.widget.viewpager2", path);
+ result.putExtra("com.example.androidx.viewpager2", path);
return result;
}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardActivity.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardActivity.java
new file mode 100644
index 0000000..7c43127
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.example.androidx.viewpager2;
+
+import static java.util.Collections.unmodifiableList;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.ViewGroup;
+
+import com.example.androidx.viewpager2.cards.Card;
+import com.example.androidx.viewpager2.cards.CardView;
+
+import java.util.List;
+
+import androidx.viewpager2.widget.ViewPager2;
+
+/**
+ * Shows how to use {@link ViewPager2#setAdapter(RecyclerView.Adapter)}
+ *
+ * @see CardFragmentActivity
+ */
+public class CardActivity extends Activity {
+ private static final List<Card> sCards = unmodifiableList(Card.createDeck52());
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.activity_card_layout);
+
+ this.<ViewPager2>findViewById(R.id.view_pager).setAdapter(
+ new RecyclerView.Adapter<CardViewHolder>() {
+ @NonNull
+ public CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+ int viewType) {
+ return new CardViewHolder(new CardView(getLayoutInflater(), parent));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull CardViewHolder holder, int position) {
+ holder.bind(sCards.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return sCards.size();
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ public static class CardViewHolder extends RecyclerView.ViewHolder {
+ private final CardView mCardView;
+
+ /** {@inheritDoc} */
+ public CardViewHolder(CardView cardView) {
+ super(cardView.getView());
+ mCardView = cardView;
+ }
+
+ /** @see CardView#bind(Card) */
+ public void bind(Card card) {
+ mCardView.bind(card);
+ }
+ }
+}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.java
new file mode 100644
index 0000000..fe4e538
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018 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 com.example.androidx.viewpager2;
+
+import static java.util.Collections.unmodifiableList;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.example.androidx.viewpager2.cards.Card;
+import com.example.androidx.viewpager2.cards.CardView;
+
+import java.util.List;
+
+import androidx.viewpager2.widget.ViewPager2;
+import androidx.viewpager2.widget.ViewPager2.FragmentProvider;
+
+/**
+ * Shows how to use {@link ViewPager2#setAdapter(FragmentManager, FragmentProvider, int)}
+ *
+ * @see CardActivity
+ */
+public class CardFragmentActivity extends FragmentActivity {
+ private static final List<Card> sCards = unmodifiableList(Card.createDeck52());
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.activity_card_layout);
+
+ this.<ViewPager2>findViewById(R.id.view_pager).setAdapter(getSupportFragmentManager(),
+ new FragmentProvider() {
+ @Override
+ public Fragment getItem(int position) {
+ return CardFragment.create(sCards.get(position));
+ }
+
+ @Override
+ public int getCount() {
+ return sCards.size();
+ }
+ },
+ ViewPager2.FragmentRetentionPolicy.SAVE_STATE);
+ }
+
+ /** {@inheritDoc} */
+ public static class CardFragment extends Fragment {
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ CardView cardView = new CardView(getLayoutInflater(), container);
+ cardView.bind(Card.fromBundle(getArguments()));
+ return cardView.getView();
+ }
+
+ /** Creates a Fragment for a given {@link Card} */
+ public static CardFragment create(Card card) {
+ CardFragment fragment = new CardFragment();
+ fragment.setArguments(card.toBundle());
+ return fragment;
+ }
+ }
+}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/Card.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/Card.java
new file mode 100644
index 0000000..28b8fd1
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/Card.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.example.androidx.viewpager2.cards;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableSet;
+
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Playing card
+ */
+public class Card {
+ private static final String ARGS_BUNDLE = Card.class.getName() + ":Bundle";
+
+ private static final Set<Character> SUITS = unmodifiableSet(new LinkedHashSet<>(
+ asList('♣' /* clubs*/, '♦' /* diamonds*/, '♥' /* hearts*/, '♠' /*spades*/)));
+ private static final Set<Character> VALUES = unmodifiableSet(new LinkedHashSet<>(
+ asList('2', '3', '4', '5', '6', '7', '8', '9', 'â’‘', 'J', 'Q', 'K', 'A')));
+
+ private final char mSuit;
+ private final char mValue;
+
+ public Card(char suit, char value) {
+ this.mSuit = checkValidValue(suit, SUITS);
+ this.mValue = checkValidValue(value, VALUES);
+ }
+
+ char getSuit() {
+ return mSuit;
+ }
+
+ String getCornerLabel() {
+ return mValue + "\n" + mSuit;
+ }
+
+ /** Use in conjunction with {@link Card#fromBundle(Bundle)} */
+ public Bundle toBundle() {
+ Bundle args = new Bundle(1);
+ args.putCharArray(ARGS_BUNDLE, new char[]{mSuit, mValue});
+ return args;
+ }
+
+ /** Use in conjunction with {@link Card#toBundle()} */
+ public static Card fromBundle(Bundle bundle) {
+ char[] spec = bundle.getCharArray(ARGS_BUNDLE);
+ return new Card(spec[0], spec[1]);
+ }
+
+ private static char checkValidValue(char value, Set<Character> allowed) {
+ if (allowed.contains(value)) {
+ return value;
+ }
+ throw new IllegalArgumentException("Illegal argument: " + value);
+ }
+
+ /**
+ * Creates a deck of all allowed cards
+ */
+ public static List<Card> createDeck52() {
+ List<Card> result = new ArrayList<>(52);
+ for (Character suit : SUITS) {
+ for (Character value : VALUES) {
+ result.add(new Card(suit, value));
+ }
+ }
+ if (result.size() != 52) {
+ throw new IllegalStateException();
+ }
+ return result;
+ }
+}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/CardView.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/CardView.java
new file mode 100644
index 0000000..02b52f8
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/CardView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 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 com.example.androidx.viewpager2.cards;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.androidx.viewpager2.R;
+
+/** Inflates and populates a {@link View} representing a {@link Card} */
+public class CardView {
+ private final View mView;
+ private final TextView mTextSuite;
+ private final TextView mTextCorner1;
+ private final TextView mTextCorner2;
+
+ public CardView(LayoutInflater layoutInflater, ViewGroup container) {
+ mView = layoutInflater.inflate(R.layout.item_card_layout, container, false);
+ mTextSuite = mView.findViewById(R.id.label_center);
+ mTextCorner1 = mView.findViewById(R.id.label_top);
+ mTextCorner2 = mView.findViewById(R.id.label_bottom);
+ }
+
+ /**
+ * Updates the view to represent the passed in card
+ */
+ public void bind(Card card) {
+ mTextSuite.setText(Character.toString(card.getSuit()));
+
+ String cornerLabel = card.getCornerLabel();
+ mTextCorner1.setText(cornerLabel);
+ mTextCorner2.setText(cornerLabel);
+ mTextCorner2.setRotation(180);
+ }
+
+ public View getView() {
+ return mView;
+ }
+}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/CardActivity.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/CardActivity.java
deleted file mode 100644
index 7484389..0000000
--- a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/CardActivity.java
+++ /dev/null
@@ -1,49 +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 com.example.androidx.widget.viewpager2;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.unmodifiableList;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import com.example.androidx.widget.viewpager2.cards.Card;
-import com.example.androidx.widget.viewpager2.cards.CardDataAdapter;
-
-import java.util.List;
-
-import androidx.widget.ViewPager2;
-
-/** @inheritDoc */
-public class CardActivity extends Activity {
- private static final List<Card> sCards = unmodifiableList(asList(
- new Card('♦', 'A'),
- new Card('♣', 'K'),
- new Card('♥', 'J'),
- new Card('♠', '9'),
- new Card('♦', '2')));
-
- @Override
- public void onCreate(Bundle bundle) {
- super.onCreate(bundle);
- setContentView(R.layout.activity_card_layout);
-
- this.<ViewPager2>findViewById(R.id.view_pager).setAdapter(
- new CardDataAdapter(getLayoutInflater(), sCards));
- }
-}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/Card.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/Card.java
deleted file mode 100644
index 02f3271..0000000
--- a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/Card.java
+++ /dev/null
@@ -1,56 +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 com.example.androidx.widget.viewpager2.cards;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.unmodifiableSet;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-/**
- * Playing card
- */
-public class Card {
- private static final Set<Character> SUITS = unmodifiableSet(new LinkedHashSet<>(
- asList('♦', /* diamonds*/ '♣', /*, clubs*/ '♥', /* hearts*/ '♠' /*spades*/)));
- private static final Set<Character> VALUES = unmodifiableSet(new LinkedHashSet<>(
- asList('2', '3', '4', '5', '6', '7', '8', '9', 'â’‘', 'J', 'Q', 'K', 'A')));
-
- private final char mSuit;
- private final char mValue;
-
- public Card(char suit, char value) {
- this.mSuit = checkValidValue(suit, SUITS);
- this.mValue = checkValidValue(value, VALUES);
- }
-
- public char getSuit() {
- return mSuit;
- }
-
- public String getCornerLabel() {
- return mValue + "\n" + mSuit;
- }
-
- private static char checkValidValue(char value, Set<Character> allowed) {
- if (allowed.contains(value)) {
- return value;
- }
- throw new IllegalArgumentException("Illegal argument: " + value);
- }
-}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardDataAdapter.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardDataAdapter.java
deleted file mode 100644
index 7f69e1b..0000000
--- a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardDataAdapter.java
+++ /dev/null
@@ -1,56 +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 com.example.androidx.widget.viewpager2.cards;
-
-import android.support.annotation.NonNull;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import com.example.androidx.widget.viewpager2.R;
-
-import java.util.List;
-
-/** @inheritDoc */
-public class CardDataAdapter extends RecyclerView.Adapter<CardViewHolder> {
- private final List<Card> mCards;
- private final LayoutInflater mLayoutInflater;
-
- public CardDataAdapter(LayoutInflater layoutInflater, List<Card> cards) {
- mLayoutInflater = layoutInflater;
- mCards = cards;
- }
-
- /**
- * @inheritDoc
- */
- @NonNull
- public CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- return new CardViewHolder(
- mLayoutInflater.inflate(R.layout.item_card_layout, parent, false));
- }
-
- @Override
- public void onBindViewHolder(@NonNull CardViewHolder holder, int position) {
- holder.apply(mCards.get(position));
- }
-
- @Override
- public int getItemCount() {
- return mCards.size();
- }
-}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardViewHolder.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardViewHolder.java
deleted file mode 100644
index 8fd0477..0000000
--- a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardViewHolder.java
+++ /dev/null
@@ -1,49 +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 com.example.androidx.widget.viewpager2.cards;
-
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.widget.TextView;
-
-import com.example.androidx.widget.viewpager2.R;
-
-/** @inheritDoc */
-public class CardViewHolder extends RecyclerView.ViewHolder {
- private final TextView mTextSuite;
- private final TextView mTextCorner1;
- private final TextView mTextCorner2;
-
- public CardViewHolder(View itemView) {
- super(itemView);
- mTextSuite = itemView.findViewById(R.id.label_center);
- mTextCorner1 = itemView.findViewById(R.id.label_top);
- mTextCorner2 = itemView.findViewById(R.id.label_bottom);
- }
-
- /**
- * Updates the view to represent the passed in card
- */
- public void apply(Card card) {
- mTextSuite.setText(Character.toString(card.getSuit()));
-
- String cornerLabel = card.getCornerLabel();
- mTextCorner1.setText(cornerLabel);
- mTextCorner2.setText(cornerLabel);
- mTextCorner2.setRotation(180);
- }
-}
diff --git a/samples/ViewPager2Demos/src/main/res/layout/activity_card_layout.xml b/samples/ViewPager2Demos/src/main/res/layout/activity_card_layout.xml
index 3037029..9d996ab 100644
--- a/samples/ViewPager2Demos/src/main/res/layout/activity_card_layout.xml
+++ b/samples/ViewPager2Demos/src/main/res/layout/activity_card_layout.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<androidx.widget.ViewPager2
+<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view_pager"
android:layout_width="match_parent"
diff --git a/samples/ViewPager2Demos/src/main/res/layout/item_card_layout.xml b/samples/ViewPager2Demos/src/main/res/layout/item_card_layout.xml
index 90a0404..1ae9295 100644
--- a/samples/ViewPager2Demos/src/main/res/layout/item_card_layout.xml
+++ b/samples/ViewPager2Demos/src/main/res/layout/item_card_layout.xml
@@ -26,6 +26,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
+ android:gravity="center"
android:textAppearance="@android:style/TextAppearance.Medium"/>
<TextView
@@ -40,5 +41,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
+ android:gravity="center"
android:textAppearance="@android:style/TextAppearance.Medium"/>
</FrameLayout>
diff --git a/settings.gradle b/settings.gradle
index 06b1c32..5e1b188 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -127,7 +127,7 @@
//
/////////////////////////////
-includeProject(":support-testutils", "testutils")
+includeProject(":internal-testutils", "testutils")
/////////////////////////////
//
diff --git a/slices/builders/build.gradle b/slices/builders/build.gradle
index 491617a..4b9a250 100644
--- a/slices/builders/build.gradle
+++ b/slices/builders/build.gradle
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryVersions
-import android.support.LibraryGroups
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryVersions
+import androidx.build.LibraryGroups
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/slices/core/build.gradle b/slices/core/build.gradle
index 8087ab4..f1eed8d 100644
--- a/slices/core/build.gradle
+++ b/slices/core/build.gradle
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/slices/view/build.gradle b/slices/view/build.gradle
index 0d9d277..9b7069e 100644
--- a/slices/view/build.gradle
+++ b/slices/view/build.gradle
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryVersions
-import android.support.LibraryGroups
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryVersions
+import androidx.build.LibraryGroups
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/slidingpanelayout/build.gradle b/slidingpanelayout/build.gradle
index d084e63..4e24b3f 100644
--- a/slidingpanelayout/build.gradle
+++ b/slidingpanelayout/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/swiperefreshlayout/build.gradle b/swiperefreshlayout/build.gradle
index 0479bbe..6654e94 100644
--- a/swiperefreshlayout/build.gradle
+++ b/swiperefreshlayout/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -18,7 +18,7 @@
androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation project(':support-testutils'), {
+ androidTestImplementation project(':internal-testutils'), {
exclude group: 'com.android.support', module: 'swiperefreshlayout'
}
}
diff --git a/swiperefreshlayout/src/androidTest/java/android/support/v4/widget/SwipeRefreshLayoutTest.java b/swiperefreshlayout/src/androidTest/java/android/support/v4/widget/SwipeRefreshLayoutTest.java
index fe53293..96c2dac 100644
--- a/swiperefreshlayout/src/androidTest/java/android/support/v4/widget/SwipeRefreshLayoutTest.java
+++ b/swiperefreshlayout/src/androidTest/java/android/support/v4/widget/SwipeRefreshLayoutTest.java
@@ -39,7 +39,6 @@
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import android.view.View;
import org.junit.Before;
@@ -50,6 +49,8 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import androidx.testutils.PollingCheck;
+
/**
* Tests SwipeRefreshLayout widget.
*/
diff --git a/testutils/build.gradle b/testutils/build.gradle
index d75b6b7..d32244f 100644
--- a/testutils/build.gradle
+++ b/testutils/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/testutils/src/main/AndroidManifest.xml b/testutils/src/main/AndroidManifest.xml
index 4d2fd19..2feb440 100644
--- a/testutils/src/main/AndroidManifest.xml
+++ b/testutils/src/main/AndroidManifest.xml
@@ -14,4 +14,4 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<manifest package="android.support.testutils"/>
+<manifest package="androidx.testutils"/>
diff --git a/testutils/src/main/java/android/support/testutils/AppCompatActivityUtils.java b/testutils/src/main/java/androidx/testutils/AppCompatActivityUtils.java
similarity index 98%
rename from testutils/src/main/java/android/support/testutils/AppCompatActivityUtils.java
rename to testutils/src/main/java/androidx/testutils/AppCompatActivityUtils.java
index 49ccc1b..e36a2ec 100644
--- a/testutils/src/main/java/android/support/testutils/AppCompatActivityUtils.java
+++ b/testutils/src/main/java/androidx/testutils/AppCompatActivityUtils.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.support.testutils;
+package androidx.testutils;
import static org.junit.Assert.assertTrue;
diff --git a/testutils/src/main/java/android/support/testutils/FragmentActivityUtils.java b/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
similarity index 98%
rename from testutils/src/main/java/android/support/testutils/FragmentActivityUtils.java
rename to testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
index 7d12deb..a4b67e7 100644
--- a/testutils/src/main/java/android/support/testutils/FragmentActivityUtils.java
+++ b/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.support.testutils;
+package androidx.testutils;
import static org.junit.Assert.assertTrue;
diff --git a/testutils/src/main/java/android/support/testutils/PollingCheck.java b/testutils/src/main/java/androidx/testutils/PollingCheck.java
similarity index 98%
rename from testutils/src/main/java/android/support/testutils/PollingCheck.java
rename to testutils/src/main/java/androidx/testutils/PollingCheck.java
index 8e85896..8fd4852 100644
--- a/testutils/src/main/java/android/support/testutils/PollingCheck.java
+++ b/testutils/src/main/java/androidx/testutils/PollingCheck.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.testutils;
+package androidx.testutils;
import org.junit.Assert;
diff --git a/testutils/src/main/java/android/support/testutils/RecreatedActivity.java b/testutils/src/main/java/androidx/testutils/RecreatedActivity.java
similarity index 97%
rename from testutils/src/main/java/android/support/testutils/RecreatedActivity.java
rename to testutils/src/main/java/androidx/testutils/RecreatedActivity.java
index aaea3a9..8430dca 100644
--- a/testutils/src/main/java/android/support/testutils/RecreatedActivity.java
+++ b/testutils/src/main/java/androidx/testutils/RecreatedActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.testutils;
+package androidx.testutils;
import android.os.Bundle;
import android.support.annotation.Nullable;
diff --git a/testutils/src/main/java/android/support/testutils/RecreatedAppCompatActivity.java b/testutils/src/main/java/androidx/testutils/RecreatedAppCompatActivity.java
similarity index 97%
rename from testutils/src/main/java/android/support/testutils/RecreatedAppCompatActivity.java
rename to testutils/src/main/java/androidx/testutils/RecreatedAppCompatActivity.java
index d5645a3..1a48cf8 100644
--- a/testutils/src/main/java/android/support/testutils/RecreatedAppCompatActivity.java
+++ b/testutils/src/main/java/androidx/testutils/RecreatedAppCompatActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.support.testutils;
+package androidx.testutils;
import android.os.Bundle;
import android.support.annotation.Nullable;
diff --git a/textclassifier/build.gradle b/textclassifier/build.gradle
index b01cbbf..a6e57de 100644
--- a/textclassifier/build.gradle
+++ b/textclassifier/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/transition/build.gradle b/transition/build.gradle
index d8257ab..c413a73 100644
--- a/transition/build.gradle
+++ b/transition/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/transition/proguard-rules.pro b/transition/proguard-rules.pro
index 6cae5e6..dda2c4e 100644
--- a/transition/proguard-rules.pro
+++ b/transition/proguard-rules.pro
@@ -12,10 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# FragmentTransitionSupport is instantiated in support-fragment via reflection.
--keep public class android.support.transition.FragmentTransitionSupport {
-}
-
# Keep a field in transition that is used to keep a reference to weakly-referenced object
-keepclassmembers class android.support.transition.ChangeBounds$* extends android.animation.AnimatorListenerAdapter {
android.support.transition.ChangeBounds$ViewBounds mViewBounds;
diff --git a/tv-provider/api/current.txt b/tv-provider/api/current.txt
index c486190..ee11d8f 100644
--- a/tv-provider/api/current.txt
+++ b/tv-provider/api/current.txt
@@ -77,10 +77,68 @@
method public static boolean storeChannelLogo(android.content.Context, long, android.graphics.Bitmap);
}
+ public class PreviewChannel {
+ method public static android.support.media.tv.PreviewChannel fromCursor(android.database.Cursor);
+ method public android.content.Intent getAppLinkIntent() throws java.net.URISyntaxException;
+ method public android.net.Uri getAppLinkIntentUri();
+ method public java.lang.CharSequence getDescription();
+ method public java.lang.CharSequence getDisplayName();
+ method public long getId();
+ method public byte[] getInternalProviderDataByteArray();
+ method public java.lang.Long getInternalProviderFlag1();
+ method public java.lang.Long getInternalProviderFlag2();
+ method public java.lang.Long getInternalProviderFlag3();
+ method public java.lang.Long getInternalProviderFlag4();
+ method public java.lang.String getInternalProviderId();
+ method public android.graphics.Bitmap getLogo(android.content.Context);
+ method public java.lang.String getPackageName();
+ method public java.lang.String getType();
+ method public boolean hasAnyUpdatedValues(android.support.media.tv.PreviewChannel);
+ method public boolean isBrowsable();
+ }
+
+ public static final class PreviewChannel.Builder {
+ ctor public PreviewChannel.Builder();
+ ctor public PreviewChannel.Builder(android.support.media.tv.PreviewChannel);
+ method public android.support.media.tv.PreviewChannel build();
+ method public android.support.media.tv.PreviewChannel.Builder setAppLinkIntent(android.content.Intent);
+ method public android.support.media.tv.PreviewChannel.Builder setAppLinkIntentUri(android.net.Uri);
+ method public android.support.media.tv.PreviewChannel.Builder setDescription(java.lang.CharSequence);
+ method public android.support.media.tv.PreviewChannel.Builder setDisplayName(java.lang.CharSequence);
+ method public android.support.media.tv.PreviewChannel.Builder setInternalProviderData(byte[]);
+ method public android.support.media.tv.PreviewChannel.Builder setInternalProviderFlag1(long);
+ method public android.support.media.tv.PreviewChannel.Builder setInternalProviderFlag2(long);
+ method public android.support.media.tv.PreviewChannel.Builder setInternalProviderFlag3(long);
+ method public android.support.media.tv.PreviewChannel.Builder setInternalProviderFlag4(long);
+ method public android.support.media.tv.PreviewChannel.Builder setInternalProviderId(java.lang.String);
+ method public android.support.media.tv.PreviewChannel.Builder setLogo(android.graphics.Bitmap);
+ method public android.support.media.tv.PreviewChannel.Builder setLogo(android.net.Uri);
+ }
+
+ public class PreviewChannelHelper {
+ ctor public PreviewChannelHelper(android.content.Context);
+ ctor public PreviewChannelHelper(android.content.Context, int, int);
+ method public void deletePreviewChannel(long);
+ method public void deletePreviewProgram(long);
+ method protected android.graphics.Bitmap downloadBitmap(android.net.Uri) throws java.io.IOException;
+ method public java.util.List<android.support.media.tv.PreviewChannel> getAllChannels();
+ method public android.support.media.tv.PreviewChannel getPreviewChannel(long);
+ method public android.support.media.tv.PreviewProgram getPreviewProgram(long);
+ method public android.support.media.tv.WatchNextProgram getWatchNextProgram(long);
+ method public long publishChannel(android.support.media.tv.PreviewChannel) throws java.io.IOException;
+ method public long publishDefaultChannel(android.support.media.tv.PreviewChannel) throws java.io.IOException;
+ method public long publishPreviewProgram(android.support.media.tv.PreviewProgram);
+ method public long publishWatchNextProgram(android.support.media.tv.WatchNextProgram);
+ method public void updatePreviewChannel(long, android.support.media.tv.PreviewChannel) throws java.io.IOException;
+ method public void updatePreviewProgram(long, android.support.media.tv.PreviewProgram);
+ method public void updateWatchNextProgram(android.support.media.tv.WatchNextProgram, long);
+ }
+
public final class PreviewProgram {
method public static android.support.media.tv.PreviewProgram fromCursor(android.database.Cursor);
method public long getChannelId();
method public int getWeight();
+ method public boolean hasAnyUpdatedValues(android.support.media.tv.PreviewProgram);
method public android.content.ContentValues toContentValues();
}
@@ -529,6 +587,7 @@
method public static android.support.media.tv.WatchNextProgram fromCursor(android.database.Cursor);
method public long getLastEngagementTimeUtcMillis();
method public int getWatchNextType();
+ method public boolean hasAnyUpdatedValues(android.support.media.tv.WatchNextProgram);
method public android.content.ContentValues toContentValues();
field public static final int WATCH_NEXT_TYPE_UNKNOWN = -1; // 0xffffffff
}
diff --git a/tv-provider/build.gradle b/tv-provider/build.gradle
index a8057c0..eb8cbd5 100644
--- a/tv-provider/build.gradle
+++ b/tv-provider/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -12,6 +12,7 @@
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(TEST_RULES)
+ androidTestImplementation(MOCKITO_CORE)
}
supportLibrary {
diff --git a/tv-provider/src/androidTest/java/android/support/media/tv/PreviewChannelHelperTest.java b/tv-provider/src/androidTest/java/android/support/media/tv/PreviewChannelHelperTest.java
new file mode 100644
index 0000000..679e0ee
--- /dev/null
+++ b/tv-provider/src/androidTest/java/android/support/media/tv/PreviewChannelHelperTest.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright 2018 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.media.tv;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.tv.TvContentRating;
+import android.net.Uri;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+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.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Test that {@link PreviewChannelHelper} can perform CRUD operations on
+ * {@link PreviewChannel PreviewChannels} and {@link PreviewProgram PreviewPrograms} correctly.
+ * All of the following tests involve the system content provider.
+ */
+@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(JUnit4.class)
+public class PreviewChannelHelperTest {
+
+
+ private Context mContext;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ /**
+ * taken from {@link PreviewProgram}
+ */
+ private static PreviewProgram.Builder createFullyPopulatedPreviewProgram(long channelId) {
+ return new PreviewProgram.Builder()
+ .setTitle("Google")
+ .setInternalProviderId("ID-4321")
+ .setChannelId(channelId)
+ .setWeight(100)
+ .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
+ .setLastPlaybackPositionMillis(0)
+ .setDurationMillis(60 * 1000)
+ .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
+ Intent.URI_INTENT_SCHEME)))
+ .setTransient(false)
+ .setType(TvContractCompat.PreviewPrograms.TYPE_MOVIE)
+ .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_2_3)
+ .setThumbnailAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_16_9)
+ .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
+ .setAvailability(TvContractCompat.PreviewPrograms.AVAILABILITY_AVAILABLE)
+ .setStartingPrice("12.99 USD")
+ .setOfferPrice("4.99 USD")
+ .setReleaseDate("1997")
+ .setItemCount(3)
+ .setLive(false)
+ .setInteractionType(TvContractCompat.PreviewPrograms.INTERACTION_TYPE_LIKES)
+ .setInteractionCount(10200)
+ .setAuthor("author_name")
+ .setReviewRatingStyle(TvContractCompat.PreviewPrograms.REVIEW_RATING_STYLE_STARS)
+ .setReviewRating("4.5")
+ .setSearchable(false)
+ .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
+ .setAudioLanguages(new String[]{"eng", "kor"})
+ .setCanonicalGenres(new String[]{TvContractCompat.Programs.Genres.MOVIES})
+ .setContentRatings(new TvContentRating[]{
+ TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
+ .setDescription("This is a sample program")
+ .setEpisodeNumber("Pilot", 0)
+ .setEpisodeTitle("Hello World")
+ .setLongDescription("This is a longer description than the previous description")
+ .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
+ .setSeasonNumber("The Final Season", 7)
+ .setSeasonTitle("The Final Season")
+ .setVideoHeight(1080)
+ .setVideoWidth(1920)
+ .setInternalProviderFlag1(0x4)
+ .setInternalProviderFlag2(0x3)
+ .setInternalProviderFlag3(0x2)
+ .setInternalProviderFlag4(0x1)
+ .setBrowsable(true)
+ .setContentId("CID-8642");
+ }
+
+ private static void compareProgram(PreviewProgram programA, PreviewProgram programB) {
+ assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
+ assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
+ assertEquals(programA.getChannelId(), programB.getChannelId());
+ assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings()));
+ assertEquals(programA.getDescription(), programB.getDescription());
+ assertEquals(programA.getEpisodeNumber(), programB.getEpisodeNumber());
+ assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
+ assertEquals(programA.getLongDescription(), programB.getLongDescription());
+ assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
+ assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
+ assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
+ assertEquals(programA.getTitle(), programB.getTitle());
+ assertEquals(programA.getVideoHeight(), programB.getVideoHeight());
+ assertEquals(programA.getVideoWidth(), programB.getVideoWidth());
+ assertEquals(programA.isSearchable(), programB.isSearchable());
+ assertEquals(programA.getInternalProviderFlag1(), programB.getInternalProviderFlag1());
+ assertEquals(programA.getInternalProviderFlag2(), programB.getInternalProviderFlag2());
+ assertEquals(programA.getInternalProviderFlag3(), programB.getInternalProviderFlag3());
+ assertEquals(programA.getInternalProviderFlag4(), programB.getInternalProviderFlag4());
+ assertTrue(Objects.equals(programA.getSeasonTitle(), programB.getSeasonTitle()));
+ assertEquals(programA.getInternalProviderId(), programB.getInternalProviderId());
+ assertEquals(programA.getPreviewVideoUri(), programB.getPreviewVideoUri());
+ assertEquals(programA.getLastPlaybackPositionMillis(),
+ programB.getLastPlaybackPositionMillis());
+ assertEquals(programA.getDurationMillis(), programB.getDurationMillis());
+ assertEquals(programA.getIntentUri(), programB.getIntentUri());
+ assertEquals(programA.getWeight(), programB.getWeight());
+ assertEquals(programA.isTransient(), programB.isTransient());
+ assertEquals(programA.getType(), programB.getType());
+ assertEquals(programA.getPosterArtAspectRatio(), programB.getPosterArtAspectRatio());
+ assertEquals(programA.getThumbnailAspectRatio(), programB.getThumbnailAspectRatio());
+ assertEquals(programA.getLogoUri(), programB.getLogoUri());
+ assertEquals(programA.getAvailability(), programB.getAvailability());
+ assertEquals(programA.getStartingPrice(), programB.getStartingPrice());
+ assertEquals(programA.getOfferPrice(), programB.getOfferPrice());
+ assertEquals(programA.getReleaseDate(), programB.getReleaseDate());
+ assertEquals(programA.getItemCount(), programB.getItemCount());
+ assertEquals(programA.isLive(), programB.isLive());
+ assertEquals(programA.getInteractionType(), programB.getInteractionType());
+ assertEquals(programA.getInteractionCount(), programB.getInteractionCount());
+ assertEquals(programA.getAuthor(), programB.getAuthor());
+ assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle());
+ assertEquals(programA.getReviewRating(), programB.getReviewRating());
+ assertEquals(programA.getContentId(), programB.getContentId());
+ }
+
+ private static WatchNextProgram.Builder createFullyPopulatedWatchNextProgram() {
+ return new WatchNextProgram.Builder()
+ .setTitle("Google")
+ .setInternalProviderId("ID-4321")
+ .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
+ .setLastPlaybackPositionMillis(0)
+ .setDurationMillis(60 * 1000)
+ .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
+ Intent.URI_INTENT_SCHEME)))
+ .setTransient(false)
+ .setType(TvContractCompat.WatchNextPrograms.TYPE_MOVIE)
+ .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
+ .setPosterArtAspectRatio(TvContractCompat.WatchNextPrograms.ASPECT_RATIO_2_3)
+ .setThumbnailAspectRatio(TvContractCompat.WatchNextPrograms.ASPECT_RATIO_16_9)
+ .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
+ .setAvailability(TvContractCompat.WatchNextPrograms.AVAILABILITY_AVAILABLE)
+ .setStartingPrice("12.99 USD")
+ .setOfferPrice("4.99 USD")
+ .setReleaseDate("1997")
+ .setItemCount(3)
+ .setLive(false)
+ .setInteractionType(TvContractCompat.WatchNextPrograms.INTERACTION_TYPE_LIKES)
+ .setInteractionCount(10200)
+ .setAuthor("author_name")
+ .setReviewRatingStyle(TvContractCompat.WatchNextPrograms.REVIEW_RATING_STYLE_STARS)
+ .setReviewRating("4.5")
+ .setSearchable(false)
+ .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
+ .setAudioLanguages(new String[]{"eng", "kor"})
+ .setCanonicalGenres(new String[]{TvContractCompat.Programs.Genres.MOVIES})
+ .setContentRatings(new TvContentRating[]{
+ TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
+ .setDescription("This is a sample program")
+ .setEpisodeNumber("Pilot", 0)
+ .setEpisodeTitle("Hello World")
+ .setLongDescription("This is a longer description than the previous description")
+ .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
+ .setSeasonNumber("The Final Season", 7)
+ .setSeasonTitle("The Final Season")
+ .setVideoHeight(1080)
+ .setVideoWidth(1920)
+ .setInternalProviderFlag1(0x4)
+ .setInternalProviderFlag2(0x3)
+ .setInternalProviderFlag3(0x2)
+ .setInternalProviderFlag4(0x1)
+ .setBrowsable(true)
+ .setContentId("CID-8442");
+ }
+
+ private static void compareProgram(WatchNextProgram programA, WatchNextProgram programB) {
+ assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
+ assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
+ assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings()));
+ assertEquals(programA.getDescription(), programB.getDescription());
+ assertEquals(programA.getEpisodeNumber(), programB.getEpisodeNumber());
+ assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
+ assertEquals(programA.getLongDescription(), programB.getLongDescription());
+ assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
+ assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
+ assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
+ assertEquals(programA.getTitle(), programB.getTitle());
+ assertEquals(programA.getVideoHeight(), programB.getVideoHeight());
+ assertEquals(programA.getVideoWidth(), programB.getVideoWidth());
+ assertEquals(programA.isSearchable(), programB.isSearchable());
+ assertEquals(programA.getInternalProviderFlag1(), programB.getInternalProviderFlag1());
+ assertEquals(programA.getInternalProviderFlag2(), programB.getInternalProviderFlag2());
+ assertEquals(programA.getInternalProviderFlag3(), programB.getInternalProviderFlag3());
+ assertEquals(programA.getInternalProviderFlag4(), programB.getInternalProviderFlag4());
+ assertTrue(Objects.equals(programA.getSeasonTitle(), programB.getSeasonTitle()));
+ assertEquals(programA.getInternalProviderId(), programB.getInternalProviderId());
+ assertEquals(programA.getPreviewVideoUri(), programB.getPreviewVideoUri());
+ assertEquals(programA.getLastPlaybackPositionMillis(),
+ programB.getLastPlaybackPositionMillis());
+ assertEquals(programA.getDurationMillis(), programB.getDurationMillis());
+ assertEquals(programA.getIntentUri(), programB.getIntentUri());
+ assertEquals(programA.isTransient(), programB.isTransient());
+ assertEquals(programA.getType(), programB.getType());
+ assertEquals(programA.getWatchNextType(), programB.getWatchNextType());
+ assertEquals(programA.getPosterArtAspectRatio(), programB.getPosterArtAspectRatio());
+ assertEquals(programA.getThumbnailAspectRatio(), programB.getThumbnailAspectRatio());
+ assertEquals(programA.getLogoUri(), programB.getLogoUri());
+ assertEquals(programA.getAvailability(), programB.getAvailability());
+ assertEquals(programA.getStartingPrice(), programB.getStartingPrice());
+ assertEquals(programA.getOfferPrice(), programB.getOfferPrice());
+ assertEquals(programA.getReleaseDate(), programB.getReleaseDate());
+ assertEquals(programA.getItemCount(), programB.getItemCount());
+ assertEquals(programA.isLive(), programB.isLive());
+ assertEquals(programA.getInteractionType(), programB.getInteractionType());
+ assertEquals(programA.getInteractionCount(), programB.getInteractionCount());
+ assertEquals(programA.getAuthor(), programB.getAuthor());
+ assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle());
+ assertEquals(programA.getReviewRating(), programB.getReviewRating());
+ assertEquals(programA.getContentId(), programB.getContentId());
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+
+ }
+
+ @After
+ public void tearDown() {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ mContext.getContentResolver().delete(
+ TvContractCompat.Channels.CONTENT_URI, null, null);
+ mContext = null;
+ }
+
+ /**
+ * Test CR of CRUD
+ * Test that the PreviewChannelHelper can correctly create and read preview channels.
+ */
+ @Test
+ public void testPreviewChannelCreation() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ PreviewChannel.Builder builder = createFullyPopulatedPreviewChannel();
+ long channelId = helper.publishDefaultChannel(builder.build());
+ PreviewChannel channelFromTvProvider = getPreviewChannel(helper, channelId);
+ assertTrue(channelsEqual(builder.build(), channelFromTvProvider));
+ }
+
+ @Test
+ public void testLogoRequiredForChannelCreation() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ PreviewChannel.Builder builder = createFullyPopulatedPreviewChannel();
+ builder.setLogo(Uri.parse("bogus"));
+ thrown.expect(IOException.class);
+ helper.publishDefaultChannel(builder.build());
+ List<PreviewChannel> channels = helper.getAllChannels();
+ assertEquals(0, channels.size());
+ }
+
+ /**
+ * Test CR of CRUD
+ * Test that the PreviewChannelHelper can correctly create and read preview channels, when
+ * internalProviderId is null.
+ */
+ @Test
+ public void testPreviewChannelCreationWithNullProviderId() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannel.Builder builder = createFullyPopulatedPreviewChannel();
+ builder.setInternalProviderId(null);
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ long channelId = helper.publishChannel(builder.build());
+ PreviewChannel channelFromTvProvider = getPreviewChannel(helper, channelId);
+ assertTrue(channelsEqual(builder.build(), channelFromTvProvider));
+ }
+
+ /**
+ * All this method is actually doing is
+ * <pre>
+ *
+ * PreviewChannel channelFromTvProvider = helper.getPreviewChannel(channelId);
+ * </pre>
+ * However, due to a known issue, when logo is persisted, the file status is not consistent
+ * between openInputStream and openOutputStream. So as a workaround, a wait period is applied
+ * to make sure that the logo file is written into the disk.
+ */
+ private PreviewChannel getPreviewChannel(PreviewChannelHelper helper,
+ long channelId) {
+ boolean logoReady = false;
+ PreviewChannel channel = null;
+ while (!logoReady) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ }
+ channel = helper.getPreviewChannel(channelId);
+ logoReady = null != channel.getLogo(mContext);
+ }
+ return channel;
+ }
+
+ /**
+ * Test CR of CRUD
+ * Test that all published preview channels can be read at once.
+ */
+ @Test
+ public void testAllPublishedChannelsRead() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ PreviewChannel.Builder builder = createFullyPopulatedPreviewChannel();
+ builder.setInternalProviderId("1");
+ helper.publishChannel(builder.build());
+ builder.setInternalProviderId("11");
+ helper.publishChannel(builder.build());
+ builder.setInternalProviderId("111");
+ helper.publishChannel(builder.build());
+ builder.setInternalProviderId("1111");
+ helper.publishChannel(builder.build());
+ List<PreviewChannel> allChannels = helper.getAllChannels();
+ assertEquals(4, allChannels.size());
+ }
+
+ /**
+ * Test UR of CRUD
+ * Test that the PreviewChannelHelper can correctly update and read preview channels.
+ */
+ @Test
+ public void testPreviewChannelUpdate() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ PreviewChannel.Builder builder = createFullyPopulatedPreviewChannel();
+ long channelId = helper.publishChannel(builder.build());
+ PreviewChannel channelFromTvProvider = getPreviewChannel(helper, channelId);
+ PreviewChannel channel = builder.build();
+ assertTrue(channelsEqual(channel, channelFromTvProvider));
+
+ PreviewChannel patch = new PreviewChannel.Builder()
+ .setDisplayName(channel.getDisplayName())
+ .setAppLinkIntentUri(channel.getAppLinkIntentUri())
+ .setDescription("Patch description").build();
+ helper.updatePreviewChannel(channelId, patch);
+ channelFromTvProvider = helper.getPreviewChannel(channelId);
+ assertFalse(channelsEqual(channel, channelFromTvProvider));
+ assertEquals(channelFromTvProvider.getDescription(), "Patch description");
+ }
+
+ /**
+ * Tests that data is not being updated unnecessarily
+ */
+ @Test
+ public void testDefensiveUpdatePreviewChannel() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ final int[] channelUpdateCount = {0};
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext) {
+ @Override
+ protected void updatePreviewChannelInternal(long channelId, PreviewChannel channel) {
+ channelUpdateCount[0]++;
+ }
+ };
+ PreviewChannel.Builder builder = createFullyPopulatedPreviewChannel();
+ long channelId = helper.publishChannel(builder.build());
+ PreviewChannel fromProvider = helper.getPreviewChannel(channelId);
+ channelsEqual(builder.build(), fromProvider);
+ helper.updatePreviewChannel(channelId, builder.build());
+ assertEquals(0, channelUpdateCount[0]);
+
+ final Uri uri = Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(Intent.URI_INTENT_SCHEME));
+ PreviewChannel channel = new PreviewChannel.Builder()
+ .setDisplayName("Test Display Name Udpate")
+ .setDescription("Test Preview Channel Description")
+ .setAppLinkIntentUri(uri).build();
+
+ helper.updatePreviewChannel(channelId, channel);
+ assertEquals(1, channelUpdateCount[0]);
+ }
+
+ @Test
+ public void testPreviewResolverChannelDeletion() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ PreviewChannel.Builder builder = createFullyPopulatedPreviewChannel();
+ long channelId = helper.publishChannel(builder.build());
+ PreviewChannel channelFromTvProvider = getPreviewChannel(helper, channelId);
+ assertTrue(channelsEqual(builder.build(), channelFromTvProvider));
+
+ helper.deletePreviewChannel(channelId);
+ channelFromTvProvider = helper.getPreviewChannel(channelId);
+ assertNull(channelFromTvProvider);
+ }
+
+ @Test
+ public void testPreviewProgramCreation() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ PreviewChannel.Builder channelBuilder = createFullyPopulatedPreviewChannel();
+ long channelId = helper.publishChannel(channelBuilder.build());
+ PreviewProgram program = createFullyPopulatedPreviewProgram(channelId).build();
+ long programId = helper.publishPreviewProgram(program);
+ PreviewProgram programFromProvider = helper.getPreviewProgram(programId);
+ compareProgram(program, programFromProvider);
+ }
+
+ @Test
+ public void testPreviewProgramUpdate() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ PreviewChannel.Builder channelBuilder = createFullyPopulatedPreviewChannel();
+ long channelId = helper.publishChannel(channelBuilder.build());
+ PreviewProgram.Builder programBuilder = createFullyPopulatedPreviewProgram(channelId);
+ long programId = helper.publishPreviewProgram(programBuilder.build());
+
+ programBuilder.setReleaseDate("2000");
+
+ helper.updatePreviewProgram(programId, programBuilder.build());
+ PreviewProgram programFromProvider = helper.getPreviewProgram(programId);
+ compareProgram(programBuilder.build(), programFromProvider);
+ }
+
+ /**
+ * Tests that data is not being updated unnecessarily
+ */
+ @Test
+ public void testDefensivePreviewProgramUpdateRequests() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ final int[] programUpdateCount = {0};
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext) {
+
+ @Override
+ public void updatePreviewProgramInternal(long programId, PreviewProgram upgrade) {
+ programUpdateCount[0]++;
+ }
+ };
+ PreviewChannel.Builder channelBuilder = createFullyPopulatedPreviewChannel();
+ long channelId = helper.publishChannel(channelBuilder.build());
+ PreviewProgram.Builder programBuilder = createFullyPopulatedPreviewProgram(channelId);
+ long programId = helper.publishPreviewProgram(programBuilder.build());
+ PreviewProgram programFromProvider = helper.getPreviewProgram(programId);
+ compareProgram(programBuilder.build(), programFromProvider);
+ helper.updatePreviewProgram(programId, programBuilder.build());
+ assertEquals(0, programUpdateCount[0]);
+ programBuilder.setDurationMillis(61 * 1000);
+ helper.updatePreviewProgram(programId, programBuilder.build());
+ assertEquals(1, programUpdateCount[0]);
+ }
+
+ @Test
+ public void testDeletePreviewProgram() throws IOException {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ PreviewChannel.Builder channelBuilder = createFullyPopulatedPreviewChannel();
+ long channelId = helper.publishChannel(channelBuilder.build());
+ PreviewProgram.Builder programBuilder = createFullyPopulatedPreviewProgram(channelId);
+ long programId = helper.publishPreviewProgram(programBuilder.build());
+
+ helper.deletePreviewProgram(programId);
+ PreviewProgram programFromProvider = helper.getPreviewProgram(programId);
+ assertNull(programFromProvider);
+ }
+
+ @Test
+ public void testWatchNextProgramCreation() {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ WatchNextProgram program = createFullyPopulatedWatchNextProgram().build();
+ long programId = helper.publishWatchNextProgram(program);
+ WatchNextProgram programFromProvider = helper.getWatchNextProgram(programId);
+ compareProgram(program, programFromProvider);
+ }
+
+ @Test
+ public void testUpdateWatchNextProgram() {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext);
+ WatchNextProgram.Builder builder = createFullyPopulatedWatchNextProgram();
+ long programId = helper.publishWatchNextProgram(builder.build());
+ builder.setOfferPrice("10.99 USD");
+ helper.updateWatchNextProgram(builder.build(), programId);
+
+ WatchNextProgram fromProvider = helper.getWatchNextProgram(programId);
+ compareProgram(builder.build(), fromProvider);
+ }
+
+ /**
+ * Tests that data is not being updated unnecessarily
+ */
+ @Test
+ public void testDefensiveUpdateWatchNextProgram() {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ final int[] programUpdateCount = {0};
+ PreviewChannelHelper helper = new PreviewChannelHelper(mContext) {
+ @Override
+ protected void updateWatchNextProgram(long programId, WatchNextProgram upgrade) {
+ programUpdateCount[0]++;
+ }
+ };
+ WatchNextProgram.Builder builder = createFullyPopulatedWatchNextProgram();
+ long programId = helper.publishWatchNextProgram(builder.build());
+ WatchNextProgram fromProvider = helper.getWatchNextProgram(programId);
+ compareProgram(builder.build(), fromProvider);
+ helper.updateWatchNextProgram(builder.build(), programId);
+ assertEquals(0, programUpdateCount[0]);
+ builder.setReleaseDate("2000");
+ helper.updateWatchNextProgram(builder.build(), programId);
+ assertEquals(1, programUpdateCount[0]);
+ }
+
+ private boolean channelsEqual(PreviewChannel channelA, PreviewChannel channelB) {
+ boolean result = channelA.getDisplayName().equals(channelB.getDisplayName())
+ && channelA.getType().equals(channelB.getType())
+ && channelA.getAppLinkIntentUri().equals(channelB.getAppLinkIntentUri())
+ && channelA.getDescription().equals(channelB.getDescription())
+ && channelA.getPackageName().equals(channelB.getPackageName())
+ && channelA.getInternalProviderFlag1() == channelB.getInternalProviderFlag1()
+ && channelA.getInternalProviderFlag2() == channelB.getInternalProviderFlag2()
+ && channelA.getInternalProviderFlag3() == channelB.getInternalProviderFlag3()
+ && channelA.getInternalProviderFlag4() == channelB.getInternalProviderFlag4()
+ && (channelA.getInternalProviderId() == null
+ && channelB.getInternalProviderId() == null
+ || channelA.getInternalProviderId().equals(channelB.getInternalProviderId()))
+ && (null != channelA.getLogo(mContext) && null != channelB.getLogo(
+ mContext))
+ && Arrays.equals(channelA.getInternalProviderDataByteArray(),
+ channelB.getInternalProviderDataByteArray());
+ return result;
+ }
+
+ public PreviewChannel.Builder createFullyPopulatedPreviewChannel() {
+ Bitmap logo = BitmapFactory.decodeResource(mContext.getResources(),
+ android.support.media.tv.test.R.drawable.test_icon);
+ assertNotNull(logo);
+ return new PreviewChannel.Builder()
+ .setAppLinkIntent(new Intent())
+ .setDescription("Test Preview Channel Description")
+ .setDisplayName("Test Display Name")
+ .setPackageName("android.support.media.tv.test")
+ .setInternalProviderFlag1(0x1)
+ .setInternalProviderFlag2(0x2)
+ .setInternalProviderFlag3(0x3)
+ .setInternalProviderFlag4(0x4)
+ .setInternalProviderId("Test Internal provider id")
+ .setInternalProviderData("Test byte array".getBytes())
+ .setLogo(logo);
+ }
+}
diff --git a/tv-provider/src/androidTest/java/android/support/media/tv/PreviewChannelTest.java b/tv-provider/src/androidTest/java/android/support/media/tv/PreviewChannelTest.java
new file mode 100644
index 0000000..ef5e22e
--- /dev/null
+++ b/tv-provider/src/androidTest/java/android/support/media/tv/PreviewChannelTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2018 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.media.tv;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Build;
+import android.support.media.tv.TvContractCompat.Channels;
+import android.support.media.tv.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+/**
+ * Tests that PreviewChannels can be created correctly. Additional test, the ones involving the
+ * System Content Provider, are inside {@link PreviewChannelHelperTest}
+ */
+@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(JUnit4.class)
+public class PreviewChannelTest extends TestCase {
+
+ private static final String TAG = "PreviewChannelTest";
+ private Context mContext;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ @After
+ public void tearDown() {
+ if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+ return;
+ }
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.delete(Channels.CONTENT_URI, null, null);
+ mContext = null;
+ }
+
+ @Test
+ public void testEmptyPreviewChannel() throws Exception {
+ PreviewChannel.Builder builder = new PreviewChannel.Builder();
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Need channel name. "
+ + "Use method setDisplayName(String) to set it.");
+ PreviewChannel emptyChannel = builder.build();
+ }
+
+ @Test
+ public void testPartiallyPopulatedPreviewChannel() {
+ final String displayName = "Google";
+ final String description = "This is a test preview channel";
+ final Uri uri = Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(Intent.URI_INTENT_SCHEME));
+
+ PreviewChannel channel = new PreviewChannel.Builder()
+ .setDisplayName(displayName)
+ .setDescription(description)
+ .setAppLinkIntentUri(uri)
+ .setLogo(createLogo()).build();
+
+ assertEquals(displayName, channel.getDisplayName());
+ assertEquals(description, channel.getDescription());
+ assertEquals(uri, channel.getAppLinkIntentUri());
+ assertNotNull(channel.getLogo(mContext));
+ assertNull(channel.getPackageName());
+ assertNull(channel.getInternalProviderDataByteArray());
+ assertNull(channel.getInternalProviderFlag1());
+ assertNull(channel.getInternalProviderFlag2());
+ assertNull(channel.getInternalProviderFlag3());
+ assertNull(channel.getInternalProviderFlag4());
+ assertNull(channel.getInternalProviderId());
+ assertFalse(channel.isBrowsable());
+ }
+
+ @Test
+ public void testFullyPopulatedPreviewChannel() {
+ //test cloning and database I/O
+ PreviewChannel channel = createFullyPopulatedPreviewChannel();
+ PreviewChannel clonedChannelFromCursor = PreviewChannel.fromCursor(
+ getPreviewChannelCursor(channel.toContentValues()));
+ assertTrue(channelsEqual(channel, clonedChannelFromCursor));
+
+ PreviewChannel clonedChannelFromBuilder = new PreviewChannel.Builder(channel).build();
+ assertTrue(channelsEqual(channel, clonedChannelFromBuilder));
+ }
+
+ @Test
+ public void testChannelEquals() {
+ assertEquals(createFullyPopulatedPreviewChannel(), createFullyPopulatedPreviewChannel());
+ }
+
+ private boolean channelsEqual(PreviewChannel channelA, PreviewChannel channelB) {
+ boolean result = channelA.getDisplayName().equals(channelB.getDisplayName())
+ && channelA.getType().equals(channelB.getType())
+ && channelA.getAppLinkIntentUri().equals(channelB.getAppLinkIntentUri())
+ && channelA.getDescription().equals(channelB.getDescription())
+ && channelA.getPackageName().equals(channelB.getPackageName())
+ && channelA.getInternalProviderFlag1() == channelB.getInternalProviderFlag1()
+ && channelA.getInternalProviderFlag2() == channelB.getInternalProviderFlag2()
+ && channelA.getInternalProviderFlag3() == channelB.getInternalProviderFlag3()
+ && channelA.getInternalProviderFlag4() == channelB.getInternalProviderFlag4()
+ && channelA.getInternalProviderId().equals(channelB.getInternalProviderId())
+ && Arrays.equals(channelA.getInternalProviderDataByteArray(),
+ channelB.getInternalProviderDataByteArray());
+ return result;
+ }
+
+ private PreviewChannel createFullyPopulatedPreviewChannel() {
+ return new PreviewChannel.Builder()
+ .setAppLinkIntent(new Intent())
+ .setDescription("Test Preview Channel Description")
+ .setDisplayName("Test Display Name")
+ .setPackageName("android.support.media.tv.test")
+ .setInternalProviderFlag1(0x1)
+ .setInternalProviderFlag2(0x2)
+ .setInternalProviderFlag3(0x3)
+ .setInternalProviderFlag4(0x4)
+ .setInternalProviderId("Test Internal provider id")
+ .setLogo(createLogo()).build();
+ }
+
+ private Bitmap createLogo() {
+ Bitmap logo = BitmapFactory.decodeResource(mContext.getResources(),
+ R.drawable.test_icon);
+ assertNotNull(logo);
+ return logo;
+ }
+
+ private MatrixCursor getPreviewChannelCursor(ContentValues contentValues) {
+ MatrixCursor cursor = new MatrixCursor(PreviewChannel.Columns.PROJECTION);
+ MatrixCursor.RowBuilder rowBuilder = cursor.newRow();
+ for (String col : PreviewChannel.Columns.PROJECTION) {
+ rowBuilder.add(col, contentValues.get(col));
+ }
+ cursor.moveToFirst();
+ return cursor;
+ }
+}
diff --git a/tv-provider/src/main/java/android/support/media/tv/PreviewChannel.java b/tv-provider/src/main/java/android/support/media/tv/PreviewChannel.java
new file mode 100644
index 0000000..79a65f9
--- /dev/null
+++ b/tv-provider/src/main/java/android/support/media/tv/PreviewChannel.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2018 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.media.tv;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.annotation.TargetApi;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.WorkerThread;
+import android.support.media.tv.TvContractCompat.Channels;
+import android.support.media.tv.TvContractCompat.Channels.Type;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.net.URISyntaxException;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Since API 26, all TV apps may create preview channels and publish them to the home screen.
+ * We call these App Channels (as distinct from the Live Channels row on the home screen). To help
+ * you create App Channels, the support library provides a number of classes prefixed by the word
+ * Preview-.
+ *
+ * This is a convenience class for mapping your app's content into a
+ * {@link TvContractCompat TvProvider Channel} for publication. Use the provided {@link Builder}
+ * for creating your preview channel object. Once you create a preview channel, you can
+ * use {@link PreviewChannelHelper} to publish it and add {@link PreviewProgram programs} to it.
+ */
+@TargetApi(26)
+public class PreviewChannel {
+
+ private static final String TAG = "PreviewChannel";
+ private static final long INVALID_CHANNEL_ID = -1;
+ private static final int IS_BROWSABLE = 1;
+
+ private ContentValues mValues;
+ private volatile Bitmap mLogoImage;
+
+ private Uri mLogoUri;
+ private boolean mLogoChanged;
+
+ /**
+ * Logo is fetched when it is explicitly asked for. mLogoFetched prevents repeated calls in
+ * case there is no logo in fact.
+ */
+ private volatile boolean mLogoFetched;
+
+ private PreviewChannel(Builder builder) {
+ mValues = builder.mValues;
+ mLogoImage = builder.mLogoBitmap;
+ mLogoUri = builder.mLogoUri;
+ mLogoChanged = (mLogoImage != null || mLogoUri != null);
+ }
+
+ /**
+ * Used by {@link PreviewChannelHelper} to transduce a TvProvider channel row into a
+ * PreviewChannel Java object. You never need to use this method unless you want to convert
+ * database rows to PreviewChannel objects yourself.
+ * <p/>
+ * This method assumes the cursor was obtained using {@link android.support.media.tv
+ * .PreviewChannel.Columns#PROJECTION}. This way, all indices are known
+ * beforehand.
+ *
+ * @param cursor a cursor row from the TvProvider
+ * @return a PreviewChannel whose values come from the cursor row
+ */
+ public static PreviewChannel fromCursor(Cursor cursor) {
+ Builder builder = new Builder();
+ builder.setId(cursor.getInt(Columns.COL_ID));
+ builder.setPackageName(cursor.getString(Columns.COL_PACKAGE_NAME));
+ builder.setType(cursor.getString(Columns.COL_TYPE));
+ builder.setDisplayName(cursor.getString(Columns.COL_DISPLAY_NAME));
+ builder.setDescription(cursor.getString(Columns.COL_DESCRIPTION));
+ builder.setAppLinkIntentUri(Uri.parse(cursor.getString(Columns.COL_APP_LINK_INTENT_URI)));
+ builder.setInternalProviderId(cursor.getString(Columns.COL_INTERNAL_PROVIDER_ID));
+ builder.setInternalProviderData(cursor.getBlob(Columns.COL_INTERNAL_PROVIDER_DATA));
+ builder.setInternalProviderFlag1(cursor.getLong(Columns.COL_INTERNAL_PROVIDER_FLAG1));
+ builder.setInternalProviderFlag2(cursor.getLong(Columns.COL_INTERNAL_PROVIDER_FLAG2));
+ builder.setInternalProviderFlag3(cursor.getLong(Columns.COL_INTERNAL_PROVIDER_FLAG3));
+ builder.setInternalProviderFlag4(cursor.getLong(Columns.COL_INTERNAL_PROVIDER_FLAG4));
+ return builder.build();
+ }
+
+ /**
+ * @return the ID the system assigns to this preview channel upon publication.
+ */
+ public long getId() {
+ Long l = mValues.getAsLong(Channels._ID);
+ return l == null ? INVALID_CHANNEL_ID : l;
+ }
+
+ /**
+ * @return package name of the app that created this channel
+ */
+ public String getPackageName() {
+ return mValues.getAsString(Channels.COLUMN_PACKAGE_NAME);
+ }
+
+ /**
+ * @return what type of channel this is. For preview channels, the type is always
+ * TvContractCompat.Channels.TYPE_PREVIEW
+ */
+ @Type
+ public String getType() {
+ return mValues.getAsString(Channels.COLUMN_TYPE);
+ }
+
+ /**
+ * @return The name users see when this channel appears on the home screen
+ */
+ public CharSequence getDisplayName() {
+ return mValues.getAsString(Channels.COLUMN_DISPLAY_NAME);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_DESCRIPTION} for the channel. A short text
+ * explaining what this channel contains.
+ */
+ public CharSequence getDescription() {
+ return mValues.getAsString(Channels.COLUMN_DESCRIPTION);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_APP_LINK_INTENT_URI} for the channel.
+ */
+ public Uri getAppLinkIntentUri() {
+ String uri = mValues.getAsString(Channels.COLUMN_APP_LINK_INTENT_URI);
+ return uri == null ? null : Uri.parse(uri);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_APP_LINK_INTENT_URI} for the program.
+ */
+ public Intent getAppLinkIntent() throws URISyntaxException {
+ String uri = mValues.getAsString(Channels.COLUMN_APP_LINK_INTENT_URI);
+ return uri == null ? null : Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME);
+ }
+
+ /**
+ * This method should be called on a worker thread since decoding Bitmap is an expensive
+ * operation and therefore should not be performed on the main thread.
+ *
+ * @return The logo associated with this preview channel
+ */
+ @WorkerThread
+ public Bitmap getLogo(Context context) {
+ if (!mLogoFetched && mLogoImage == null) {
+ try {
+ mLogoImage = BitmapFactory.decodeStream(
+ context.getContentResolver().openInputStream(
+ TvContract.buildChannelLogoUri(getId())
+ ));
+ } catch (FileNotFoundException | SQLiteException e) {
+ Log.e(TAG, "Logo for preview channel (ID:" + getId() + ") not found.", e);
+ }
+ mLogoFetched = true;
+ }
+ return mLogoImage;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ boolean isLogoChanged() {
+ return mLogoChanged;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ Uri getLogoUri() {
+ return mLogoUri;
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_INTERNAL_PROVIDER_DATA} for the channel.
+ */
+ public byte[] getInternalProviderDataByteArray() {
+ return mValues.getAsByteArray(Channels.COLUMN_INTERNAL_PROVIDER_DATA);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_INTERNAL_PROVIDER_FLAG1} for the channel.
+ */
+ public Long getInternalProviderFlag1() {
+ return mValues.getAsLong(Channels.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_INTERNAL_PROVIDER_FLAG2} for the channel.
+ */
+ public Long getInternalProviderFlag2() {
+ return mValues.getAsLong(Channels.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_INTERNAL_PROVIDER_FLAG3} for the channel.
+ */
+ public Long getInternalProviderFlag3() {
+ return mValues.getAsLong(Channels.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_INTERNAL_PROVIDER_FLAG4} for the channel.
+ */
+ public Long getInternalProviderFlag4() {
+ return mValues.getAsLong(Channels.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_INTERNAL_PROVIDER_ID} for the channel.
+ */
+ public String getInternalProviderId() {
+ return mValues.getAsString(Channels.COLUMN_INTERNAL_PROVIDER_ID);
+ }
+
+ /**
+ * @return The value of {@link Channels#COLUMN_BROWSABLE} for the channel. A preview channel
+ * is BROWABLE when it is visible on the TV home screen.
+ */
+ public boolean isBrowsable() {
+ Integer i = mValues.getAsInteger(Channels.COLUMN_BROWSABLE);
+ return i != null && i == IS_BROWSABLE;
+ }
+
+ @Override
+ public int hashCode() {
+ return mValues.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof PreviewChannel)) {
+ return false;
+ }
+ return mValues.equals(((PreviewChannel) other).mValues);
+ }
+
+ /**
+ * Indicates whether some other PreviewChannel has any set attribute that is different from
+ * this PreviewChannel's respective attributes. An attribute is considered "set" if its key
+ * is present in the ContentValues vector.
+ */
+ public boolean hasAnyUpdatedValues(PreviewChannel update) {
+ Set<String> updateKeys = update.mValues.keySet();
+ for (String key : updateKeys) {
+ Object updateValue = update.mValues.get(key);
+ Object currValue = mValues.get(key);
+ if (!Objects.deepEquals(updateValue, currValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Channel{" + mValues.toString() + "}";
+ }
+
+ /**
+ * Used by {@link PreviewChannelHelper} to communicate PreviewChannel CRUD operations
+ * to the TvProvider. You never need to use this method unless you want to communicate to the
+ * TvProvider directly.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public ContentValues toContentValues() {
+ ContentValues values = new ContentValues(mValues);
+ return values;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public static class Columns {
+ public static final String[] PROJECTION = {
+ Channels._ID,
+ Channels.COLUMN_PACKAGE_NAME,
+ Channels.COLUMN_TYPE,
+ Channels.COLUMN_DISPLAY_NAME,
+ Channels.COLUMN_DESCRIPTION,
+ Channels.COLUMN_APP_LINK_INTENT_URI,
+ Channels.COLUMN_INTERNAL_PROVIDER_ID,
+ Channels.COLUMN_INTERNAL_PROVIDER_DATA,
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG4
+ };
+
+ public static final int COL_ID = 0;
+ public static final int COL_PACKAGE_NAME = 1;
+ public static final int COL_TYPE = 2;
+ public static final int COL_DISPLAY_NAME = 3;
+ public static final int COL_DESCRIPTION = 4;
+ public static final int COL_APP_LINK_INTENT_URI = 5;
+ public static final int COL_INTERNAL_PROVIDER_ID = 6;
+ public static final int COL_INTERNAL_PROVIDER_DATA = 7;
+ public static final int COL_INTERNAL_PROVIDER_FLAG1 = 8;
+ public static final int COL_INTERNAL_PROVIDER_FLAG2 = 9;
+ public static final int COL_INTERNAL_PROVIDER_FLAG3 = 10;
+ public static final int COL_INTERNAL_PROVIDER_FLAG4 = 11;
+ }
+
+ /**
+ * This builder makes it easy to create a PreviewChannel object by allowing you to chain
+ * setters. Even though this builder provides a no-arg constructor, certain fields are
+ * required or the {@link #build()} method will throw an exception. The required fields are
+ * displayName and appLinkIntentUri; use the respective methods to set them.
+ */
+ public static final class Builder {
+ private ContentValues mValues;
+ private Bitmap mLogoBitmap;
+ private Uri mLogoUri;
+
+ public Builder() {
+ mValues = new ContentValues();
+ }
+
+ public Builder(PreviewChannel other) {
+ mValues = new ContentValues(other.mValues);
+ }
+
+ private Builder setId(long id) {
+ mValues.put(Channels._ID, id);
+ return this;
+ }
+
+ /**
+ * Sets the package name of the Channel.
+ *
+ * @param packageName The value of {@link Channels#COLUMN_PACKAGE_NAME} for the channel.
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ Builder setPackageName(String packageName) {
+ mValues.put(Channels.COLUMN_PACKAGE_NAME, packageName);
+ return this;
+ }
+
+ // Private because this is always the same: setType(TvContractCompat.Channels.TYPE_PREVIEW)
+ private Builder setType(@Type String type) {
+ mValues.put(Channels.COLUMN_TYPE, type);
+ return this;
+ }
+
+ /**
+ * This is the name user sees when your channel appears on their TV home screen. For
+ * example "New Arrivals." This field is required.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_DISPLAY_NAME
+ */
+ public Builder setDisplayName(CharSequence displayName) {
+ mValues.put(Channels.COLUMN_DISPLAY_NAME, displayName.toString());
+ return this;
+ }
+
+ /**
+ * It's good practice to include a general description of the programs in this channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_DESCRIPTION
+ */
+ public Builder setDescription(CharSequence description) {
+ mValues.put(Channels.COLUMN_DESCRIPTION, description.toString());
+ return this;
+ }
+
+ /**
+ * When user clicks on this channel's logo, the system will send an Intent for your app to
+ * open an Activity with contents relevant to this channel. Hence, the Intent data you
+ * provide here must point to content relevant to this channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ */
+ public Builder setAppLinkIntent(Intent appLinkIntent) {
+ return setAppLinkIntentUri(Uri.parse(appLinkIntent.toUri(Intent.URI_INTENT_SCHEME)));
+ }
+
+ /**
+ * When user clicks on this channel's logo, the system will send an Intent for your app to
+ * open an Activity with contents relevant to this channel. Hence, the Uri you provide here
+ * must point to content relevant to this channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_APP_LINK_INTENT_URI
+ */
+ public Builder setAppLinkIntentUri(Uri appLinkIntentUri) {
+ mValues.put(Channels.COLUMN_APP_LINK_INTENT_URI,
+ null == appLinkIntentUri ? null : appLinkIntentUri.toString());
+ return this;
+ }
+
+ /**
+ * It is expected that your app or your server has its own internal representation
+ * (i.e. data structure) of channels. It is highly recommended that you store your
+ * app/server's channel ID here; so that you may easily relate this published preview
+ * channel with the corresponding channel from your server.
+ *
+ * The {@link PreviewChannelHelper#publishChannel(PreviewChannel) publish} method check this
+ * field to verify whether a preview channel being published would result in a duplicate.
+ * :
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_INTERNAL_PROVIDER_ID
+ */
+ public Builder setInternalProviderId(String internalProviderId) {
+ mValues.put(Channels.COLUMN_INTERNAL_PROVIDER_ID, internalProviderId);
+ return this;
+ }
+
+ /**
+ * This is one of the optional fields that your app may set. Use these fields at your
+ * discretion to help you remember important information about this channel.
+ *
+ * For example, if this channel needs a byte array that is expensive for your app to
+ * construct, you may choose to save it here.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_INTERNAL_PROVIDER_DATA
+ */
+ public Builder setInternalProviderData(byte[] internalProviderData) {
+ mValues.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, internalProviderData);
+ return this;
+ }
+
+ /**
+ * This is one of the optional fields that your app may set. Use these fields at your
+ * discretion to help you remember important information about this channel.
+ *
+ * For example, you may use this flag to track additional data about this particular
+ * channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_INTERNAL_PROVIDER_FLAG1
+ */
+ public Builder setInternalProviderFlag1(long flag) {
+ mValues.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, flag);
+ return this;
+ }
+
+ /**
+ * This is one of the optional fields that your app may set. Use these fields at your
+ * discretion to help you remember important information about this channel.
+ *
+ * For example, you may use this flag to track additional data about this particular
+ * channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_INTERNAL_PROVIDER_FLAG2
+ */
+ public Builder setInternalProviderFlag2(long flag) {
+ mValues.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, flag);
+ return this;
+ }
+
+ /**
+ * This is one of the optional fields that your app may set. Use these fields at your
+ * discretion to help you remember important information about this channel.
+ *
+ * For example, you may use this flag to track additional data about this particular
+ * channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_INTERNAL_PROVIDER_FLAG3
+ */
+ public Builder setInternalProviderFlag3(long flag) {
+ mValues.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG3, flag);
+ return this;
+ }
+
+ /**
+ * This is one of the optional fields that your app may set. Use these fields at your
+ * discretion to help you remember important information about this channel.
+ *
+ * For example, you may use this flag to track additional data about this particular
+ * channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @see TvContractCompat.Channels#COLUMN_INTERNAL_PROVIDER_FLAG4
+ */
+ public Builder setInternalProviderFlag4(long flag) {
+ mValues.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG4, flag);
+ return this;
+ }
+
+ /**
+ * A logo visually identifies your channel. Hence, you should consider adding a unique logo
+ * to every channel you create, so user can quickly identify your channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ */
+ public Builder setLogo(@NonNull Bitmap logoImage) {
+ mLogoBitmap = logoImage;
+ mLogoUri = null;
+ return this;
+ }
+
+ /**
+ * A logo visually identifies your channel. Hence, you should consider adding a unique logo
+ * to every channel you create, so user can quickly identify your channel.
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ */
+ public Builder setLogo(@NonNull Uri logoUri) {
+ mLogoUri = logoUri;
+ mLogoBitmap = null;
+ return this;
+ }
+
+ /**
+ * Takes the values of the Builder object and creates a PreviewChannel object.
+ *
+ * @return PreviewChannel object with values from the Builder.
+ */
+ public PreviewChannel build() {
+ setType(Channels.TYPE_PREVIEW);
+
+ if (TextUtils.isEmpty(mValues.getAsString(Channels.COLUMN_DISPLAY_NAME))) {
+ throw new IllegalStateException("Need channel name."
+ + " Use method setDisplayName(String) to set it.");
+ }
+
+ if (TextUtils.isEmpty(mValues.getAsString(Channels.COLUMN_APP_LINK_INTENT_URI))) {
+ throw new IllegalStateException("Need app link intent uri for channel."
+ + " Use method setAppLinkIntent or setAppLinkIntentUri to set it.");
+ }
+
+ PreviewChannel previewChannel = new PreviewChannel(this);
+ return previewChannel;
+ }
+ }
+}
diff --git a/tv-provider/src/main/java/android/support/media/tv/PreviewChannelHelper.java b/tv-provider/src/main/java/android/support/media/tv/PreviewChannelHelper.java
new file mode 100644
index 0000000..05fa9f5
--- /dev/null
+++ b/tv-provider/src/main/java/android/support/media/tv/PreviewChannelHelper.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright 2018 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.media.tv;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.WorkerThread;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * From a user's perspective, the TV home screen has two types of channels: the single Live
+ * Channels row versus the App preview Channels. This class is concerned with App Channels; or more
+ * precisely: <i>your</i> app's preview Channels. In API 26+, all TV apps are allowed to create
+ * multiple channels and publish those Channels to the home screen.
+ * <p>
+ * This class provides convenience methods to help you publish, update and delete channels; add,
+ * update or remove programs in a channel. You do not need to know anything about Content
+ * Providers, Content Resolvers, Cursors or such to publish your channels. This class abstracts
+ * away all database interactions for you.
+ * <p>
+ * To make it easy for you to distinguish classes that help you build App Channels, the support
+ * library uses the prefix Preview- to denote the classes that pertain to app Channels. Hence,
+ * the classes {@link PreviewChannel} and {@link PreviewProgram} help your app add channels to the
+ * TV home page.
+ *
+ * All calls to methods in the class should be made on worker threads.
+ */
+
+@TargetApi(26)
+@WorkerThread
+public class PreviewChannelHelper {
+
+ private static final String TAG = "PreviewChannelHelper";
+ private static final int DEFAULT_URL_CONNNECTION_TIMEOUT_MILLIS =
+ (int) (3 * DateUtils.SECOND_IN_MILLIS);
+ private static final int DEFAULT_READ_TIMEOUT_MILLIS = (int) (10 * DateUtils.SECOND_IN_MILLIS);
+ private static final int INVALID_CONTENT_ID = -1;
+ private final int mUrlConnectionTimeoutMillis;
+ private final int mUrlReadTimeoutMillis;
+ private final Context mContext;
+
+ public PreviewChannelHelper(Context context) {
+ this(context, DEFAULT_URL_CONNNECTION_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * @param urlConnectionTimeoutMillis see {@link URLConnection#setConnectTimeout(int)}
+ * @param urlReadTimeoutMillis see {@link URLConnection#setReadTimeout(int)}
+ */
+ public PreviewChannelHelper(Context context, int urlConnectionTimeoutMillis,
+ int urlReadTimeoutMillis) {
+ mContext = context;
+ mUrlConnectionTimeoutMillis = urlConnectionTimeoutMillis;
+ mUrlReadTimeoutMillis = urlReadTimeoutMillis;
+ }
+
+ /**
+ * Publishing a channel to the TV home screen is a two step process: first, you add the
+ * channel to the TV content provider; second, you make the channel browsable (i.e. visible).
+ * {@link #publishChannel(PreviewChannel) This method} adds the channel to the
+ * TV content provider for you and returns a channelId. Next you must use the channelId
+ * to make the channel browsable.
+ * </br>
+ * There are two ways you can make a channel browsable:
+ * </br>
+ * a) For your first channel, simply ask the system to make the channel browsable:
+ * TvContractCompat.requestChannelBrowsable(context,channelId)
+ * </br>
+ * b) For any additional channel beyond the first channel, you must get permission
+ * from the user. So if this channel is not your first channel, you must request user
+ * permission through the following intent. So take the channelId returned by
+ * {@link #publishChannel(PreviewChannel) this method} and do the following
+ * inside an Activity or Fragment:
+ * </br>
+ * <pre>
+ * intent = new Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE);
+ * intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId);
+ * startActivityForResult(intent, REQUEST_CHANNEL_BROWSABLE);
+ * </pre>
+ *
+ * <p>
+ * Creating a PreviewChannel, you may pass to the builder a
+ * {@link PreviewChannel.Builder#setLogo(Uri) url as your logo}. In such case,
+ * {@link #updatePreviewChannel(long, PreviewChannel)} will load the logo over the network. To
+ * use your own networking code, override {@link #downloadBitmap(Uri)}.
+ *
+ * @return channelId or -1 if insertion fails. This is the id the system assigns to your
+ * published channel. You can use it later to get a reference to this published PreviewChannel.
+ */
+ public long publishChannel(@NonNull PreviewChannel channel) throws IOException {
+ try {
+ Uri channelUri = mContext.getContentResolver().insert(
+ TvContractCompat.Channels.CONTENT_URI,
+ channel.toContentValues());
+ if (null == channelUri || channelUri.equals(Uri.EMPTY)) {
+ throw new NullPointerException("Channel insertion failed");
+ }
+ long channelId = ContentUris.parseId(channelUri);
+ boolean logoAdded = addChannelLogo(channelId, channel);
+ // Rollback channel insertion if logo could not be added.
+ if (!logoAdded) {
+ deletePreviewChannel(channelId);
+ throw new IOException("Failed to add logo, so channel (ID="
+ + channelId + ") was not created");
+ }
+ return channelId;
+ } catch (SecurityException e) {
+ Log.e(TAG, "Your app's ability to insert data into the TvProvider"
+ + " may have been revoked.", e);
+ }
+ return INVALID_CONTENT_ID;
+ }
+
+ /**
+ * This is a convenience method that simply publishes your first channel for you. After calling
+ * {@link #publishChannel(PreviewChannel)} to add the channel to the TvProvider, it
+ * calls {@link TvContractCompat#requestChannelBrowsable(Context, long)} to make the channel
+ * visible.
+ * <p>
+ * Only use this method to publish your first channel as you do not need user permission to
+ * make your first channel browsable (i.e. visible on home screen). For additional channels,
+ * see the documentations for {@link #publishChannel(PreviewChannel)}.
+ *
+ * <p>
+ * Creating a PreviewChannel, you may pass to the builder a
+ * {@link PreviewChannel.Builder#setLogo(Uri) url as your logo}. In such case,
+ * {@link #updatePreviewChannel(long, PreviewChannel)} will load the logo over the network. To
+ * use your own networking code, override {@link #downloadBitmap(Uri)}.
+ *
+ * @return channelId: This is the id the system assigns to your published channel. You can
+ * use it later to get a reference to this published PreviewChannel.
+ */
+ public long publishDefaultChannel(@NonNull PreviewChannel channel)
+ throws IOException {
+ long channelId = publishChannel(channel);
+ TvContractCompat.requestChannelBrowsable(mContext, channelId);
+ return channelId;
+ }
+
+ /**
+ * The TvProvider does not allow select queries. Hence, unless you are querying for a
+ * {@link #getPreviewChannel(long) single PreviewChannel by id}, you must get all of
+ * your channels at once and then use the returned list as necessary.
+ */
+ public List<PreviewChannel> getAllChannels() {
+ Cursor cursor = mContext.getContentResolver()
+ .query(
+ TvContractCompat.Channels.CONTENT_URI,
+ PreviewChannel.Columns.PROJECTION,
+ null,
+ null,
+ null);
+
+ List<PreviewChannel> channels = new ArrayList<>();
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ channels.add(PreviewChannel.fromCursor(cursor));
+ } while (cursor.moveToNext());
+ }
+ return channels;
+ }
+
+ /**
+ * Retrieves a single preview channel from the TvProvider. When you publish a preview channel,
+ * the TvProvider assigns an ID to it. That's the channelId to use here.
+ *
+ * @param channelId ID of preview channel in TvProvider
+ * @return PreviewChannel or null if not found
+ */
+ public PreviewChannel getPreviewChannel(long channelId) {
+ PreviewChannel channel = null;
+ Uri channelUri = TvContractCompat.buildChannelUri(channelId);
+ Cursor cursor = mContext.getContentResolver()
+ .query(channelUri, PreviewChannel.Columns.PROJECTION, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ channel = PreviewChannel.fromCursor(cursor);
+ }
+ return channel;
+ }
+
+ /**
+ * To update a preview channel, you need to use the {@link PreviewChannel.Builder} to set the
+ * attributes you wish to change. Then simply pass in the built channel and the channelId of the
+ * preview channel. (The channelId is the ID you received when you originally
+ * {@link #publishChannel(PreviewChannel) published} the preview channel.)
+ * <p>
+ * Creating a PreviewChannel, you may pass to the builder a
+ * {@link PreviewChannel.Builder#setLogo(Uri) url as your logo}. In such case,
+ * {@link #updatePreviewChannel(long, PreviewChannel)} will load the logo over the network. To
+ * use your own networking code, override {@link #downloadBitmap(Uri)}.
+ */
+ public void updatePreviewChannel(long channelId,
+ @NonNull PreviewChannel update) throws IOException {
+ // To avoid possibly expensive no-op updates, first check that the current content that's
+ // in the database is different from the new content to be added.
+ PreviewChannel curr = getPreviewChannel(channelId);
+ if (curr != null && curr.hasAnyUpdatedValues(update)) {
+ updatePreviewChannelInternal(channelId, update);
+ }
+ if (update.isLogoChanged()) {
+ boolean logoAdded = addChannelLogo(channelId, update);
+ if (!logoAdded) {
+ throw new IOException("Fail to update channel (ID=" + channelId + ") logo.");
+ }
+ }
+ }
+
+ /**
+ * Inner methods that does the actual work of updating a Preview Channel. The method is
+ * extracted to make {@link #updatePreviewChannel(long, PreviewChannel)} testable.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ protected void updatePreviewChannelInternal(long channelId, @NonNull PreviewChannel upgrade) {
+ mContext.getContentResolver().update(
+ TvContractCompat.buildChannelUri(channelId),
+ upgrade.toContentValues(),
+ null,
+ null);
+ }
+
+ /**
+ * Internally, a logo is added to a channel after the channel has been added to the TvProvider.
+ * This private method is called by one of the publish methods, to add a logo to the TvProvider
+ * and associate the logo to the given channel identified by channelId. Because each channel
+ * must have a logo, a NullPointerException is thrown if the channel being published has no
+ * associated logo to publish with it.
+ */
+ private boolean addChannelLogo(long channelId, @NonNull PreviewChannel channel) {
+ boolean result = false;
+ if (!channel.isLogoChanged()) {
+ return result;
+ }
+ Bitmap logo = channel.getLogo(mContext);
+ if (logo == null) {
+ logo = getLogoFromUri(channel.getLogoUri());
+ }
+ Uri logoUri = TvContractCompat.buildChannelLogoUri(channelId);
+ try (OutputStream outputStream = mContext.getContentResolver().openOutputStream(
+ logoUri)) {
+ result = logo.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
+ outputStream.flush();
+ } catch (SQLiteException | IOException | NullPointerException e) {
+ Log.i(TAG, "Failed to add logo to the published channel (ID= " + channelId + ")", e);
+ }
+ return result;
+ }
+
+ /**
+ * Handles the case where the Bitmap must be fetched from a known uri. First the
+ * method checks if the Uri is local. If not, the method makes a connection to fetch the Bitmap
+ * data from its remote location. To use your own networking implementation, simply override
+ * {@link #downloadBitmap(Uri)}
+ */
+ private Bitmap getLogoFromUri(@NonNull Uri logoUri) {
+ String scheme = logoUri.normalizeScheme().getScheme();
+ InputStream inputStream = null;
+ Bitmap logoImage = null;
+
+ try {
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme)
+ || ContentResolver.SCHEME_CONTENT.equals(scheme)) {
+ // for local resource
+ inputStream = mContext.getContentResolver().openInputStream(logoUri);
+ logoImage = BitmapFactory.decodeStream(inputStream);
+ } else {
+ logoImage = downloadBitmap(logoUri);
+ }
+
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to get logo from the URI: " + logoUri, e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // Do nothing
+ }
+ }
+ }
+ return logoImage;
+ }
+
+ /**
+ * Downloads a Bitmap from a remote server. It is declared protected to allow you
+ * to override it to use your own networking implementation if you so wish.
+ */
+ protected Bitmap downloadBitmap(@NonNull Uri logoUri) throws IOException {
+ URLConnection urlConnection = null;
+ InputStream inputStream = null;
+ Bitmap logoImage = null;
+ try {
+ // for remote resource
+ urlConnection = new URL(logoUri.toString()).openConnection();
+ urlConnection.setConnectTimeout(mUrlConnectionTimeoutMillis);
+ urlConnection.setReadTimeout(mUrlReadTimeoutMillis);
+ inputStream = urlConnection.getInputStream();
+ logoImage = BitmapFactory.decodeStream(inputStream);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // Do nothing
+ }
+ }
+ if (urlConnection instanceof HttpURLConnection) {
+ ((HttpURLConnection) urlConnection).disconnect();
+ }
+ }
+ return logoImage;
+ }
+
+ /**
+ * Removes a preview channel from the system's content provider (aka TvProvider).
+ */
+ public void deletePreviewChannel(long channelId) {
+ mContext.getContentResolver().delete(
+ TvContractCompat.buildChannelUri(channelId),
+ null,
+ null);
+ }
+
+ /**
+ * Adds programs to a preview channel.
+ */
+ public long publishPreviewProgram(@NonNull PreviewProgram program) {
+ try {
+ Uri programUri = mContext.getContentResolver().insert(
+ TvContractCompat.PreviewPrograms.CONTENT_URI,
+ program.toContentValues());
+ long programId = ContentUris.parseId(programUri);
+ return programId;
+ } catch (SecurityException e) {
+ Log.e(TAG, "Your app's ability to insert data into the TvProvider"
+ + " may have been revoked.", e);
+ }
+ return INVALID_CONTENT_ID;
+ }
+
+ /**
+ * Retrieves a single preview program from the system content provider (aka TvProvider).
+ */
+ public PreviewProgram getPreviewProgram(long programId) {
+ PreviewProgram program = null;
+ Uri programUri = TvContractCompat.buildPreviewProgramUri(programId);
+ Cursor cursor = mContext.getContentResolver().query(programUri, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ program = PreviewProgram.fromCursor(cursor);
+ }
+ return program;
+ }
+
+ /**
+ * Updates programs in a preview channel.
+ */
+ public void updatePreviewProgram(long programId, @NonNull PreviewProgram update) {
+ // To avoid possibly expensive no-op updates, first check that the current content that's
+ // in the database is different from the new content to be added.
+ PreviewProgram curr = getPreviewProgram(programId);
+ if (curr != null && curr.hasAnyUpdatedValues(update)) {
+ updatePreviewProgramInternal(programId, update);
+ }
+ }
+
+ /**
+ * Inner methods that does the actual work of updating a Preview Program. The method is
+ * extracted to make {@link #updatePreviewProgram(long, PreviewProgram)} testable.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ void updatePreviewProgramInternal(long programId, @NonNull PreviewProgram upgrade) {
+ mContext.getContentResolver().update(
+ TvContractCompat.buildPreviewProgramUri(programId),
+ upgrade.toContentValues(), null, null);
+ }
+
+ /**
+ * Removes programs from a preview channel.
+ */
+ public void deletePreviewProgram(long programId) {
+ mContext.getContentResolver().delete(
+ TvContractCompat.buildPreviewProgramUri(programId), null, null);
+ }
+
+ /**
+ * Adds a program to the Watch Next channel
+ */
+ public long publishWatchNextProgram(@NonNull WatchNextProgram program) {
+ try {
+ Uri programUri = mContext.getContentResolver().insert(
+ TvContractCompat.WatchNextPrograms.CONTENT_URI, program.toContentValues());
+ return ContentUris.parseId(programUri);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Your app's ability to insert data into the TvProvider"
+ + " may have been revoked.", e);
+ }
+ return INVALID_CONTENT_ID;
+ }
+
+ /**
+ * Retrieves a single WatchNext program from the system content provider (aka TvProvider).
+ */
+ public WatchNextProgram getWatchNextProgram(long programId) {
+ WatchNextProgram program = null;
+ Uri programUri = TvContractCompat.buildWatchNextProgramUri(programId);
+ Cursor cursor = mContext.getContentResolver().query(programUri, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ program = WatchNextProgram.fromCursor(cursor);
+ }
+ return program;
+ }
+
+ /**
+ * Updates a WatchNext program.
+ */
+ public void updateWatchNextProgram(@NonNull WatchNextProgram upgrade, long programId) {
+ // To avoid possibly expensive no-op updates, first check that the current content that's in
+ // the database is different from the new content to be added.
+ WatchNextProgram curr = getWatchNextProgram(programId);
+ if (curr != null && curr.hasAnyUpdatedValues(upgrade)) {
+ updateWatchNextProgram(programId, upgrade);
+ }
+ }
+
+ /**
+ * Inner methods that does the actual work of updating a Watch Next Program. The method is
+ * extracted to make {@link #updateWatchNextProgram(WatchNextProgram, long)} testable.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ void updateWatchNextProgram(long programId, @NonNull WatchNextProgram upgrade) {
+ mContext.getContentResolver().update(
+ TvContractCompat.buildWatchNextProgramUri(programId),
+ upgrade.toContentValues(), null, null);
+ }
+}
diff --git a/tv-provider/src/main/java/android/support/media/tv/PreviewProgram.java b/tv-provider/src/main/java/android/support/media/tv/PreviewProgram.java
index 2536f75..f44e18f 100644
--- a/tv-provider/src/main/java/android/support/media/tv/PreviewProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/PreviewProgram.java
@@ -23,6 +23,9 @@
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.PreviewPrograms;
+import java.util.Objects;
+import java.util.Set;
+
/**
* A convenience class to access {@link PreviewPrograms} entries in the system content
* provider.
@@ -108,6 +111,23 @@
return mValues.equals(((PreviewProgram) other).mValues);
}
+ /**
+ * Indicates whether some other PreviewProgram has any set attribute that is different from
+ * this PreviewProgram's respective attributes. An attribute is considered "set" if its key
+ * is present in the ContentValues vector.
+ */
+ public boolean hasAnyUpdatedValues(PreviewProgram update) {
+ Set<String> updateKeys = update.mValues.keySet();
+ for (String key : updateKeys) {
+ Object updateValue = update.mValues.get(key);
+ Object currValue = mValues.get(key);
+ if (!Objects.deepEquals(updateValue, currValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public String toString() {
return "PreviewProgram{" + mValues.toString() + "}";
@@ -164,7 +184,7 @@
}
private static String[] getProjection() {
- String[] oColumns = new String[] {
+ String[] oColumns = new String[]{
PreviewPrograms.COLUMN_CHANNEL_ID,
PreviewPrograms.COLUMN_WEIGHT,
};
@@ -184,6 +204,7 @@
/**
* Creates a new Builder object with values copied from another Program.
+ *
* @param other The Program you're copying from.
*/
public Builder(PreviewProgram other) {
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 e5d12b2..cb317b7 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
@@ -26,6 +26,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
/**
* A convenience class to access {@link WatchNextPrograms} entries in the system content
@@ -94,7 +96,8 @@
})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(LIBRARY_GROUP)
- public @interface WatchNextType {}
+ public @interface WatchNextType {
+ }
/**
* The unknown watch next type. Use this type when the actual type is not known.
@@ -131,6 +134,23 @@
return mValues.equals(((WatchNextProgram) other).mValues);
}
+ /**
+ * Indicates whether some other WatchNextProgram has any set attribute that is different from
+ * this WatchNextProgram's respective attributes. An attribute is considered "set" if its key
+ * is present in the ContentValues vector.
+ */
+ public boolean hasAnyUpdatedValues(WatchNextProgram update) {
+ Set<String> updateKeys = update.mValues.keySet();
+ for (String key : updateKeys) {
+ Object updateValue = update.mValues.get(key);
+ Object currValue = mValues.get(key);
+ if (!Objects.deepEquals(updateValue, currValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public String toString() {
return "WatchNextProgram{" + mValues.toString() + "}";
@@ -188,7 +208,7 @@
}
private static String[] getProjection() {
- String[] oColumns = new String[] {
+ String[] oColumns = new String[]{
WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE,
WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS,
};
@@ -208,6 +228,7 @@
/**
* Creates a new Builder object with values copied from another Program.
+ *
* @param other The Program you're copying from.
*/
public Builder(WatchNextProgram other) {
@@ -235,7 +256,8 @@
* Sets the time when the program is going to begin in milliseconds since the epoch.
*
* @param lastEngagementTimeUtcMillis The value of
- * {@link WatchNextPrograms#COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS} for the program.
+ * {@link WatchNextPrograms#COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS}
+ * for the program.
* @return This Builder object to allow for chaining of calls to builder methods.
*/
public Builder setLastEngagementTimeUtcMillis(long lastEngagementTimeUtcMillis) {
diff --git a/v13/build.gradle b/v13/build.gradle
index 82a48bd..cb9cdbf 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/v14/preference/build.gradle b/v14/preference/build.gradle
index 86036bd..43f6e36 100644
--- a/v14/preference/build.gradle
+++ b/v14/preference/build.gradle
@@ -14,8 +14,8 @@
* limitations under the License
*/
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/v4/build.gradle b/v4/build.gradle
index 8725a46..640c5fe 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index 799b7b3..2f50fff 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -17,7 +17,7 @@
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
- androidTestImplementation project(':support-testutils'), {
+ androidTestImplementation project(':internal-testutils'), {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
}
@@ -36,6 +36,10 @@
additionalParameters "--no-version-vectors"
noCompress 'ttf'
}
+
+ buildTypes.all {
+ consumerProguardFiles("proguard-rules.pro")
+ }
}
supportLibrary {
diff --git a/v7/appcompat/proguard-rules.pro b/v7/appcompat/proguard-rules.pro
new file mode 100644
index 0000000..98c23e5
--- /dev/null
+++ b/v7/appcompat/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Copyright (C) 2018 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.
+
+# Ensure that reflectively-loaded inflater is not obfuscated. This can be
+# removed when we stop supporting AAPT1 builds.
+-keepnames class android.support.v7.app.AppCompatViewInflater
diff --git a/v7/appcompat/src/androidTest/java/android/support/v7/app/NightModeTestCase.java b/v7/appcompat/src/androidTest/java/android/support/v7/app/NightModeTestCase.java
index d42174f..7f636bd 100644
--- a/v7/appcompat/src/androidTest/java/android/support/v7/app/NightModeTestCase.java
+++ b/v7/appcompat/src/androidTest/java/android/support/v7/app/NightModeTestCase.java
@@ -30,8 +30,6 @@
import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.AppCompatActivityUtils;
-import android.support.testutils.RecreatedAppCompatActivity;
import android.support.v4.content.ContextCompat;
import android.support.v7.appcompat.test.R;
@@ -43,6 +41,9 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import androidx.testutils.AppCompatActivityUtils;
+import androidx.testutils.RecreatedAppCompatActivity;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
public class NightModeTestCase {
diff --git a/v7/appcompat/src/androidTest/java/android/support/v7/testutils/BaseTestActivity.java b/v7/appcompat/src/androidTest/java/android/support/v7/testutils/BaseTestActivity.java
index e4dbf26..c880768 100644
--- a/v7/appcompat/src/androidTest/java/android/support/v7/testutils/BaseTestActivity.java
+++ b/v7/appcompat/src/androidTest/java/android/support/v7/testutils/BaseTestActivity.java
@@ -19,7 +19,6 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.testutils.RecreatedAppCompatActivity;
import android.support.v7.app.AppCompatCallback;
import android.support.v7.appcompat.test.R;
import android.support.v7.view.ActionMode;
@@ -28,6 +27,8 @@
import android.view.MenuItem;
import android.view.WindowManager;
+import androidx.testutils.RecreatedAppCompatActivity;
+
public abstract class BaseTestActivity extends RecreatedAppCompatActivity {
private Menu mMenu;
diff --git a/v7/appcompat/src/androidTest/java/android/support/v7/widget/PopupMenuTest.java b/v7/appcompat/src/androidTest/java/android/support/v7/widget/PopupMenuTest.java
index cae216d..c1326ac 100644
--- a/v7/appcompat/src/androidTest/java/android/support/v7/widget/PopupMenuTest.java
+++ b/v7/appcompat/src/androidTest/java/android/support/v7/widget/PopupMenuTest.java
@@ -56,7 +56,6 @@
import android.support.test.filters.SdkSuppress;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.appcompat.test.R;
import android.text.TextUtils;
@@ -80,6 +79,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.testutils.PollingCheck;
+
@RunWith(AndroidJUnit4.class)
public class PopupMenuTest {
@Rule
diff --git a/v7/appcompat/src/androidTest/java/android/support/v7/widget/SearchView_CursorTest.java b/v7/appcompat/src/androidTest/java/android/support/v7/widget/SearchView_CursorTest.java
index ea919e8..7d61d54 100644
--- a/v7/appcompat/src/androidTest/java/android/support/v7/widget/SearchView_CursorTest.java
+++ b/v7/appcompat/src/androidTest/java/android/support/v7/widget/SearchView_CursorTest.java
@@ -36,7 +36,6 @@
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.appcompat.test.R;
@@ -50,6 +49,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.testutils.PollingCheck;
+
/**
* Test {@link SearchView} with {@link Cursor}-backed suggestions adapter.
*/
diff --git a/v7/appcompat/src/main/java/android/support/v7/app/WindowDecorActionBar.java b/v7/appcompat/src/main/java/android/support/v7/app/WindowDecorActionBar.java
index 1c17922..db8c1a2 100644
--- a/v7/appcompat/src/main/java/android/support/v7/app/WindowDecorActionBar.java
+++ b/v7/appcompat/src/main/java/android/support/v7/app/WindowDecorActionBar.java
@@ -239,7 +239,7 @@
return ((Toolbar) view).getWrapper();
} else {
throw new IllegalStateException("Can't make a decor toolbar out of " +
- view != null ? view.getClass().getSimpleName() : "null");
+ (view != null ? view.getClass().getSimpleName() : "null"));
}
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/view/menu/ListMenuItemView.java b/v7/appcompat/src/main/java/android/support/v7/view/menu/ListMenuItemView.java
index 0b5cb48..8e58754 100644
--- a/v7/appcompat/src/main/java/android/support/v7/view/menu/ListMenuItemView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/view/menu/ListMenuItemView.java
@@ -196,9 +196,8 @@
if (checkable) {
compoundButton.setChecked(mItemData.isChecked());
- final int newVisibility = checkable ? VISIBLE : GONE;
- if (compoundButton.getVisibility() != newVisibility) {
- compoundButton.setVisibility(newVisibility);
+ if (compoundButton.getVisibility() != VISIBLE) {
+ compoundButton.setVisibility(VISIBLE);
}
// Make sure the other compound button isn't visible
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/ActivityChooserView.java b/v7/appcompat/src/main/java/android/support/v7/widget/ActivityChooserView.java
index 9979363..1121966 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/ActivityChooserView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/ActivityChooserView.java
@@ -90,7 +90,7 @@
/**
* The content of this view.
*/
- private final LinearLayoutCompat mActivityChooserContent;
+ private final View mActivityChooserContent;
/**
* Stores the background drawable to allow hiding and latter showing.
diff --git a/v7/cardview/build.gradle b/v7/cardview/build.gradle
index 88ce452..cb597ad 100644
--- a/v7/cardview/build.gradle
+++ b/v7/cardview/build.gradle
@@ -1,5 +1,5 @@
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index 329cb00..85021b1 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index f28938b..3d19227 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/v7/mediarouter/src/main/res/values/strings.xml b/v7/mediarouter/src/main/res/values/strings.xml
index 630a482..4dd0f8a 100644
--- a/v7/mediarouter/src/main/res/values/strings.xml
+++ b/v7/mediarouter/src/main/res/values/strings.xml
@@ -46,7 +46,7 @@
<!-- Button to disconnect from a media route. [CHAR LIMIT=30] -->
<string name="mr_controller_disconnect">Disconnect</string>
- <!-- Button to stop playback and disconnect from a media route. [CHAR LIMIT=30] -->
+ <!-- Button to stop playback and disconnect from a media route. [CHAR LIMIT=32] -->
<string name="mr_controller_stop_casting">Stop casting</string>
<!-- Content description for accessibility (not shown on the screen): dialog close button. [CHAR LIMIT=NONE] -->
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
index 8829dcb..9c91601 100644
--- a/v7/palette/build.gradle
+++ b/v7/palette/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index 7707883..a0c9bd9 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -14,9 +14,9 @@
* limitations under the License
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/v7/preference/res/values/attrs.xml b/v7/preference/res/values/attrs.xml
index bb52593..332a3df 100644
--- a/v7/preference/res/values/attrs.xml
+++ b/v7/preference/res/values/attrs.xml
@@ -176,6 +176,9 @@
<attr name="iconSpaceReserved" format="boolean" />
<attr name="android:iconSpaceReserved" />
+ <!-- Whether the Preference is visible. By default, this is set to true. -->
+ <attr name="isPreferenceVisible" format="boolean" />
+
</declare-styleable>
<!-- Base attributes available to CheckBoxPreference. -->
diff --git a/v7/preference/src/androidTest/java/android/support/v7/preference/tests/PreferenceVisibilityTest.java b/v7/preference/src/androidTest/java/android/support/v7/preference/tests/PreferenceVisibilityTest.java
new file mode 100644
index 0000000..9000f15
--- /dev/null
+++ b/v7/preference/src/androidTest/java/android/support/v7/preference/tests/PreferenceVisibilityTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.v7.preference.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.preference.test.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link android.support.v7.preference.Preference} visibility set with XML.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreferenceVisibilityTest {
+
+ @Test
+ @UiThreadTest
+ public void testPreferencesAreCreatedWithTheVisibilitySetInXml() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final PreferenceManager manager = new PreferenceManager(context);
+ final PreferenceScreen screen = manager.inflateFromResource(context,
+ R.layout.test_visibility,
+ null);
+
+ // Preference without visibility set should be visible
+ assertTrue(screen.getPreference(0).isVisible());
+ // Preference with visibility set to true should be visible
+ assertTrue(screen.getPreference(1).isVisible());
+ // Preference with visibility set to false should not be invisible
+ assertFalse(screen.getPreference(2).isVisible());
+ }
+}
diff --git a/v7/preference/src/androidTest/res/layout/test_visibility.xml b/v7/preference/src/androidTest/res/layout/test_visibility.xml
new file mode 100644
index 0000000..5623cd1
--- /dev/null
+++ b/v7/preference/src/androidTest/res/layout/test_visibility.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2018 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.
+-->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <Preference android:summary="This preference is visible by default" />
+
+ <Preference
+ android:summary="This preference is forced to be visible"
+ app:isPreferenceVisible="true" />
+
+ <Preference
+ android:summary="This preference is invisible"
+ app:isPreferenceVisible="false" />
+</PreferenceScreen>
diff --git a/v7/preference/src/main/java/android/support/v7/preference/Preference.java b/v7/preference/src/main/java/android/support/v7/preference/Preference.java
index 88262cd..79fbceb 100644
--- a/v7/preference/src/main/java/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/main/java/android/support/v7/preference/Preference.java
@@ -323,6 +323,9 @@
mIconSpaceReserved = TypedArrayUtils.getBoolean(a, R.styleable.Preference_iconSpaceReserved,
R.styleable.Preference_android_iconSpaceReserved, false);
+ mVisible = TypedArrayUtils.getBoolean(a, R.styleable.Preference_isPreferenceVisible,
+ R.styleable.Preference_isPreferenceVisible, true);
+
a.recycle();
}
@@ -871,6 +874,8 @@
* {@link PreferenceFragmentCompat#findPreference(CharSequence)}.
*
* @param visible Set false if this preference should be hidden from the list.
+ *
+ * @attr ref R.styleable#Preference_isPreferenceVisible
*/
public final void setVisible(boolean visible) {
if (mVisible != visible) {
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index e58e5b1..5f7630c 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
@@ -18,7 +18,7 @@
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(JUNIT)
androidTestImplementation(KOTLIN_STDLIB)
- androidTestImplementation(project(":support-testutils"))
+ androidTestImplementation(project(":internal-testutils"))
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
diff --git a/v7/recyclerview/src/androidTest/java/android/support/v7/recyclerview/extensions/AsyncListDifferTest.kt b/v7/recyclerview/src/androidTest/java/android/support/v7/recyclerview/extensions/AsyncListDifferTest.kt
index 2518065..7d2ec60 100644
--- a/v7/recyclerview/src/androidTest/java/android/support/v7/recyclerview/extensions/AsyncListDifferTest.kt
+++ b/v7/recyclerview/src/androidTest/java/android/support/v7/recyclerview/extensions/AsyncListDifferTest.kt
@@ -170,6 +170,43 @@
verifyNoMoreInteractions(callback)
}
+ @Test
+ fun singleChangePayload() {
+ val callback = mock(ListUpdateCallback::class.java)
+ val helper = createHelper(callback, STRING_DIFF_CALLBACK)
+
+ helper.submitList(listOf("a", "b"))
+ verify(callback).onInserted(0, 2)
+ verifyNoMoreInteractions(callback)
+ drain()
+ verifyNoMoreInteractions(callback)
+
+ helper.submitList(listOf("a", "beta"))
+ verifyNoMoreInteractions(callback)
+ drain()
+ verify(callback).onChanged(1, 1, "eta")
+ verifyNoMoreInteractions(callback)
+ }
+
+ @Test
+ fun multiChangePayload() {
+ val callback = mock(ListUpdateCallback::class.java)
+ val helper = createHelper(callback, STRING_DIFF_CALLBACK)
+
+ helper.submitList(listOf("a", "b"))
+ verify(callback).onInserted(0, 2)
+ verifyNoMoreInteractions(callback)
+ drain()
+ verifyNoMoreInteractions(callback)
+
+ helper.submitList(listOf("alpha", "beta"))
+ verifyNoMoreInteractions(callback)
+ drain()
+ verify(callback).onChanged(1, 1, "eta")
+ verify(callback).onChanged(0, 1, "lpha")
+ verifyNoMoreInteractions(callback)
+ }
+
private fun drain() {
var executed: Boolean
do {
@@ -181,12 +218,21 @@
companion object {
private val STRING_DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
- return oldItem == newItem
+ // items are the same if first char is the same
+ return oldItem[0] == newItem[0]
}
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem == newItem
}
+
+ override fun getChangePayload(oldItem: String, newItem: String): Any? {
+ if (newItem.startsWith(oldItem)) {
+ // new string is appended, return added portion on the end
+ return newItem.subSequence(oldItem.length, newItem.length)
+ }
+ return null
+ }
}
private val IGNORE_CALLBACK = object : ListUpdateCallback {
diff --git a/v7/recyclerview/src/androidTest/java/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/src/androidTest/java/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index e47a480..cf6e228 100644
--- a/v7/recyclerview/src/androidTest/java/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/src/androidTest/java/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -36,7 +36,6 @@
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
-import android.support.testutils.PollingCheck;
import android.support.v4.view.ViewCompat;
import android.support.v7.recyclerview.test.R;
import android.util.Log;
@@ -63,6 +62,8 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import androidx.testutils.PollingCheck;
+
abstract public class BaseRecyclerViewInstrumentationTest {
private static final String TAG = "RecyclerViewTest";
@@ -80,7 +81,8 @@
Thread mInstrumentationThread;
@Rule
- public ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule(TestActivity.class);
+ public ActivityTestRule<TestActivity> mActivityRule =
+ new ActivityTestRule<>(TestActivity.class);
public BaseRecyclerViewInstrumentationTest() {
this(false);
diff --git a/v7/recyclerview/src/androidTest/java/android/support/v7/widget/helper/ItemTouchHelperTest.java b/v7/recyclerview/src/androidTest/java/android/support/v7/widget/helper/ItemTouchHelperTest.java
index 99d1066..6d73fbc 100644
--- a/v7/recyclerview/src/androidTest/java/android/support/v7/widget/helper/ItemTouchHelperTest.java
+++ b/v7/recyclerview/src/androidTest/java/android/support/v7/widget/helper/ItemTouchHelperTest.java
@@ -32,7 +32,6 @@
import android.support.test.filters.SdkSuppress;
import android.support.test.filters.Suppress;
import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
import android.support.v4.util.Pair;
import android.support.v7.util.TouchUtils;
import android.support.v7.widget.BaseRecyclerViewInstrumentationTest;
@@ -47,6 +46,8 @@
import java.util.ArrayList;
import java.util.List;
+import androidx.testutils.PollingCheck;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
public class ItemTouchHelperTest extends BaseRecyclerViewInstrumentationTest {
diff --git a/v7/recyclerview/src/main/java/android/support/v7/recyclerview/extensions/AsyncListDiffer.java b/v7/recyclerview/src/main/java/android/support/v7/recyclerview/extensions/AsyncListDiffer.java
index 5259b4f..fa463d9 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/recyclerview/extensions/AsyncListDiffer.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/recyclerview/extensions/AsyncListDiffer.java
@@ -240,6 +240,13 @@
return mConfig.getDiffCallback().areContentsTheSame(
oldList.get(oldItemPosition), newList.get(newItemPosition));
}
+
+ @Nullable
+ @Override
+ public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+ return mConfig.getDiffCallback().getChangePayload(
+ oldList.get(oldItemPosition), newList.get(newItemPosition));
+ }
});
mConfig.getMainThreadExecutor().execute(new Runnable() {
diff --git a/viewpager/build.gradle b/viewpager/build.gradle
index fdf3b29..5da06de 100644
--- a/viewpager/build.gradle
+++ b/viewpager/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/viewpager2/build.gradle b/viewpager2/build.gradle
index 7f75d09..a3106f0 100644
--- a/viewpager2/build.gradle
+++ b/viewpager2/build.gradle
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
}
dependencies {
- api(project(":support-core-utils"))
+ api(project(":support-fragment"))
api(project(":recyclerview-v7"))
androidTestImplementation(TEST_RUNNER)
diff --git a/viewpager2/src/androidTest/AndroidManifest.xml b/viewpager2/src/androidTest/AndroidManifest.xml
index ac723e6..22028ad 100755
--- a/viewpager2/src/androidTest/AndroidManifest.xml
+++ b/viewpager2/src/androidTest/AndroidManifest.xml
@@ -15,10 +15,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.widget.viewpager2.test">
+ package="androidx.viewpager2.test">
<uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
<application android:supportsRtl="true">
- <activity android:name="androidx.widget.viewpager2.tests.TestActivity"/>
+ <activity android:name="androidx.viewpager2.widget.tests.TestActivity"/>
</application>
</manifest>
\ No newline at end of file
diff --git a/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/TestActivity.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/tests/TestActivity.java
similarity index 81%
rename from viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/TestActivity.java
rename to viewpager2/src/androidTest/java/androidx/viewpager2/widget/tests/TestActivity.java
index 351ad9a..f94d9d6 100644
--- a/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/TestActivity.java
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/tests/TestActivity.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package androidx.widget.viewpager2.tests;
+package androidx.viewpager2.widget.tests;
-import android.app.Activity;
import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
-import androidx.widget.viewpager2.test.R;
+import androidx.viewpager2.test.R;
-public class TestActivity extends Activity {
+public class TestActivity extends FragmentActivity {
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/tests/ViewPager2Tests.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/tests/ViewPager2Tests.java
new file mode 100644
index 0000000..2355df1
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/tests/ViewPager2Tests.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.viewpager2.widget.tests;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
+import static android.view.View.OVER_SCROLL_NEVER;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.ViewActions;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import androidx.viewpager2.test.R;
+import androidx.viewpager2.widget.ViewPager2;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewPager2Tests {
+ private static final Random RANDOM = new Random();
+ private static final int[] sColors = {
+ Color.parseColor("#BBA9FF00"),
+ Color.parseColor("#BB00E87E"),
+ Color.parseColor("#BB00C7FF"),
+ Color.parseColor("#BBB30CE8"),
+ Color.parseColor("#BBFF00D0")};
+
+ @Rule
+ public final ActivityTestRule<TestActivity> mActivityTestRule;
+ @Rule
+ public ExpectedException mExpectedException = ExpectedException.none();
+
+ private ViewPager2 mViewPager;
+
+ // allows to wait until swipe operation is finished (Smooth Scroller done)
+ private CountDownLatch mStableAfterSwipe;
+
+ public ViewPager2Tests() {
+ mActivityTestRule = new ActivityTestRule<>(TestActivity.class);
+ }
+
+ @Before
+ public void setUp() {
+ mViewPager = mActivityTestRule.getActivity().findViewById(R.id.view_pager);
+
+ mViewPager.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ // coming to idle from another state (dragging or setting) means we're stable now
+ if (newState == SCROLL_STATE_IDLE) {
+ mStableAfterSwipe.countDown();
+ }
+ }
+ });
+
+ final long seed = RANDOM.nextLong();
+ RANDOM.setSeed(seed);
+ Log.i(getClass().getName(), "Random seed: " + seed);
+ }
+
+ public static class PageFragment extends Fragment {
+ private static final String KEY_VALUE = "value";
+
+ public interface EventListener {
+ void onEvent(PageFragment fragment);
+
+ EventListener NO_OP = new EventListener() {
+ @Override
+ public void onEvent(PageFragment fragment) {
+ // do nothing
+ }
+ };
+ }
+
+ private EventListener mOnAttachListener = EventListener.NO_OP;
+ private EventListener mOnDestroyListener = EventListener.NO_OP;
+
+ private int mPosition;
+ private int mValue;
+
+ public static PageFragment create(int position, int value) {
+ PageFragment result = new PageFragment();
+ Bundle args = new Bundle(1);
+ args.putInt(KEY_VALUE, value);
+ result.setArguments(args);
+ result.mPosition = position;
+ return result;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mOnAttachListener.onEvent(this);
+ }
+
+ @NonNull
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.item_test_layout, container, false);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ Bundle data = savedInstanceState != null ? savedInstanceState : getArguments();
+ setValue(data.getInt(KEY_VALUE));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mOnDestroyListener.onEvent(this);
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ outState.putInt(KEY_VALUE, mValue);
+ }
+
+ public void setValue(int value) {
+ mValue = value;
+ TextView textView = getView().findViewById(R.id.text_view);
+ applyViewValue(textView, mValue);
+ }
+ }
+
+ private static void applyViewValue(TextView textView, int value) {
+ textView.setText(String.valueOf(value));
+ textView.setBackgroundColor(getColor(value));
+ }
+
+ private static int getColor(int value) {
+ return sColors[value % sColors.length];
+ }
+
+ @Test
+ public void fragmentAdapter_fullPass() throws Throwable {
+ testFragmentLifecycle(8, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0));
+ }
+
+ @Test
+ public void fragmentAdapter_random() throws Throwable {
+ final int totalPages = 10;
+ final int sequenceLength = 50;
+ testFragmentLifecycle_random(totalPages, sequenceLength, PageMutator.NO_OP);
+ }
+
+ @Test
+ public void fragmentAdapter_random_withMutations() throws Throwable {
+ final int totalPages = 10;
+ final int sequenceLength = 50;
+ testFragmentLifecycle_random(totalPages, sequenceLength, PageMutator.RANDOM);
+ }
+
+ private void testFragmentLifecycle_random(int totalPages, int sequenceLength,
+ PageMutator pageMutator) throws Throwable {
+ List<Integer> pageSequence = generateRandomPageSequence(totalPages, sequenceLength);
+
+ Log.i(getClass().getName(),
+ String.format("Testing with a sequence [%s]", TextUtils.join(", ", pageSequence)));
+
+ testFragmentLifecycle(totalPages, pageSequence, pageMutator);
+ }
+
+ @NonNull
+ private List<Integer> generateRandomPageSequence(int totalPages, int sequenceLength) {
+ List<Integer> pageSequence = new ArrayList<>(sequenceLength);
+
+ int pageIx = 0;
+ Double goRightProbability = null;
+ while (pageSequence.size() != sequenceLength) {
+ boolean goRight;
+ if (pageIx == 0) {
+ goRight = true;
+ goRightProbability = 0.7;
+ } else if (pageIx == totalPages - 1) { // last page
+ goRight = false;
+ goRightProbability = 0.3;
+ } else {
+ goRight = RANDOM.nextDouble() < goRightProbability;
+ }
+
+ pageSequence.add(goRight ? ++pageIx : --pageIx);
+ }
+
+ return pageSequence;
+ }
+
+ /**
+ * Test added when caught a bug: after the last swipe: actual=6, expected=4
+ * <p>
+ * Bug was caused by an invalid test assumption (new Fragment value can be inferred from number
+ * of instances created) - invalid in a case when we sometimes create Fragments off-screen and
+ * end up scrapping them.
+ **/
+ @Test
+ public void fragmentAdapter_regression1() throws Throwable {
+ testFragmentLifecycle(10, Arrays.asList(1, 2, 3, 2, 1, 2, 3, 4));
+ }
+
+ /**
+ * Test added when caught a bug: after the last swipe: actual=4, expected=5
+ * <p>
+ * Bug was caused by mSavedStates.add(position, ...) instead of mSavedStates.set(position, ...)
+ **/
+ @Test
+ public void fragmentAdapter_regression2() throws Throwable {
+ testFragmentLifecycle(10, Arrays.asList(1, 2, 3, 4, 3, 2, 1, 2, 3, 4, 5));
+ }
+
+ /**
+ * Test added when caught a bug: after the last swipe: ArrayIndexOutOfBoundsException: length=5;
+ * index=-1 at androidx.viewpager2.widget.tests.ViewPager2Tests$PageFragment.onCreateView
+ * <p>
+ * Bug was caused by always saving states of unattached fragments as null (even if there was a
+ * valid previously saved state)
+ */
+ @Test
+ public void fragmentAdapter_regression3() throws Throwable {
+ testFragmentLifecycle(10, Arrays.asList(1, 2, 3, 2, 1, 2, 3, 2, 1, 0));
+ }
+
+ /** Goes left on left edge / right on right edge */
+ @Test
+ public void fragmentAdapter_edges() throws Throwable {
+ testFragmentLifecycle(4, Arrays.asList(0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0));
+ }
+
+ private interface PageMutator {
+ void mutate(PageFragment fragment);
+
+ PageMutator NO_OP = new PageMutator() {
+ @Override
+ public void mutate(PageFragment fragment) {
+ // do nothing
+ }
+ };
+
+ /** At random modifies the page under Fragment */
+ PageMutator RANDOM = new PageMutator() {
+ @Override
+ public void mutate(PageFragment fragment) {
+ Random random = ViewPager2Tests.RANDOM;
+ if (random.nextDouble() < 0.125) {
+ int delta = (1 + random.nextInt(5)) * sColors.length;
+ fragment.setValue(fragment.mValue + delta);
+ }
+ }
+ };
+ }
+
+ /** @see this#testFragmentLifecycle(int, List, PageMutator) */
+ private void testFragmentLifecycle(final int totalPages, List<Integer> pageSequence)
+ throws Throwable {
+ testFragmentLifecycle(totalPages, pageSequence, PageMutator.NO_OP);
+ }
+
+ /**
+ * Verifies:
+ * <ul>
+ * <li>page content / background
+ * <li>maximum number of Fragments held in memory
+ * <li>Fragment state saving / restoring
+ * </ul>
+ */
+ private void testFragmentLifecycle(final int totalPages, List<Integer> pageSequence,
+ final PageMutator pageMutator) throws Throwable {
+ final AtomicInteger attachCount = new AtomicInteger(0);
+ final AtomicInteger destroyCount = new AtomicInteger(0);
+ final boolean[] wasEverAttached = new boolean[totalPages];
+ final PageFragment[] fragments = new PageFragment[totalPages];
+
+ final int[] expectedValues = new int[totalPages];
+ for (int i = 0; i < totalPages; i++) {
+ expectedValues[i] = i;
+ }
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mViewPager.setAdapter(mActivityTestRule.getActivity().getSupportFragmentManager(),
+ new ViewPager2.FragmentProvider() {
+ @Override
+ public Fragment getItem(final int position) {
+ // if the fragment was attached in the past, it means we have
+ // provided it with the correct value already; give a dummy one
+ // to prove state save / restore functionality works
+ int value = wasEverAttached[position] ? -1 : position;
+ PageFragment fragment = PageFragment.create(position, value);
+
+ fragment.mOnAttachListener = new PageFragment.EventListener() {
+ @Override
+ public void onEvent(PageFragment fragment) {
+ attachCount.incrementAndGet();
+ wasEverAttached[fragment.mPosition] = true;
+ }
+ };
+
+ fragment.mOnDestroyListener = new PageFragment.EventListener() {
+ @Override
+ public void onEvent(PageFragment fragment) {
+ destroyCount.incrementAndGet();
+ }
+ };
+
+ fragments[position] = fragment;
+ return fragment;
+ }
+
+ @Override
+ public int getCount() {
+ return totalPages;
+ }
+ }, ViewPager2.FragmentRetentionPolicy.SAVE_STATE);
+ }
+ });
+
+ final AtomicInteger currentPage = new AtomicInteger(0);
+ verifyView(expectedValues[currentPage.get()]);
+ for (int nextPage : pageSequence) {
+ swipe(currentPage.get(), nextPage, totalPages);
+ currentPage.set(nextPage);
+ verifyView(expectedValues[currentPage.get()]);
+
+ // TODO: validate Fragments that are instantiated, but not attached. No destruction
+ // steps are done to them - they're just left to the Garbage Collector. Maybe
+ // WeakReferences could help, but the GC behaviour is not predictable. Alternatively,
+ // we could only create Fragments onAttach, but there is a potential performance
+ // trade-off.
+ assertThat(attachCount.get() - destroyCount.get(), isBetween(1, 4));
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final int page = currentPage.get();
+ PageFragment fragment = fragments[page];
+ pageMutator.mutate(fragment);
+ expectedValues[page] = fragment.mValue;
+ }
+ });
+ }
+ }
+
+ private void swipe(int currentPageIx, int nextPageIx, int totalPages)
+ throws InterruptedException {
+ if (nextPageIx >= totalPages) {
+ throw new IllegalArgumentException("Invalid nextPageIx: >= totalPages.");
+ }
+
+ if (currentPageIx == nextPageIx) { // dedicated for testing edge behaviour
+ if (nextPageIx == 0) {
+ swipeRight(); // bounce off the left edge
+ return;
+ }
+ if (nextPageIx == totalPages - 1) { // bounce off the right edge
+ swipeLeft();
+ return;
+ }
+ throw new IllegalArgumentException(
+ "Invalid sequence. Not on an edge, and currentPageIx/nextPageIx pages same.");
+ }
+
+ if (Math.abs(nextPageIx - currentPageIx) > 1) {
+ throw new IllegalArgumentException(
+ "Specified nextPageIx not adjacent to the current page.");
+ }
+
+ if (nextPageIx > currentPageIx) {
+ swipeLeft();
+ } else {
+ swipeRight();
+ }
+ }
+
+ private Matcher<Integer> isBetween(int min, int max) {
+ return allOf(greaterThanOrEqualTo(min), lessThanOrEqualTo(max));
+ }
+
+ @Test
+ public void viewAdapter_rendersAndHandlesSwiping() throws Throwable {
+ final int totalPages = 8;
+
+ if (Build.VERSION.SDK_INT < 16) { // TODO(b/71500143): remove temporary workaround
+ RecyclerView mRecyclerView = (RecyclerView) mViewPager.getChildAt(0);
+ mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER);
+ }
+
+ onView(withId(mViewPager.getId())).check(matches(isDisplayed()));
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mViewPager.setAdapter(
+ new Adapter<ViewHolder>() {
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+ int viewType) {
+ return new ViewHolder(
+ mActivityTestRule.getActivity().getLayoutInflater().inflate(
+ R.layout.item_test_layout, parent, false)) {
+ };
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ TextView view = (TextView) holder.itemView;
+ applyViewValue(view, position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return totalPages;
+ }
+ });
+ }
+ });
+
+ List<Integer> pageSequence = Arrays.asList(0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 6, 5, 4, 3, 2,
+ 1, 0, 0, 0);
+ verifyView(0);
+ int currentPage = 0;
+ for (int nextPage : pageSequence) {
+ swipe(currentPage, nextPage, totalPages);
+ currentPage = nextPage;
+ verifyView(currentPage);
+ }
+ }
+
+ private void verifyView(int pageNumber) {
+ onView(allOf(withId(R.id.text_view), isDisplayed())).check(
+ matches(allOf(withText(String.valueOf(pageNumber)),
+ new BackgroundColorMatcher(pageNumber))));
+ }
+
+ private static class BackgroundColorMatcher extends BaseMatcher<View> {
+ private final int mPageNumber;
+
+ BackgroundColorMatcher(int pageNumber) {
+ mPageNumber = pageNumber;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("should have background color: ").appendValue(
+ getColor(mPageNumber));
+ }
+
+ @Override
+ public boolean matches(Object item) {
+ ColorDrawable background = (ColorDrawable) ((View) item).getBackground();
+ return background.getColor() == getColor(mPageNumber);
+ }
+ }
+
+ private void swipeLeft() throws InterruptedException {
+ performSwipe(ViewActions.swipeLeft());
+ }
+
+ private void swipeRight() throws InterruptedException {
+ performSwipe(ViewActions.swipeRight());
+ }
+
+ private void performSwipe(ViewAction swipeAction) throws InterruptedException {
+ mStableAfterSwipe = new CountDownLatch(1);
+ onView(allOf(isDisplayed(), withId(R.id.text_view))).perform(swipeAction);
+ mStableAfterSwipe.await(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void itemViewSizeMatchParentEnforced() {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage(
+ "Item's root view must fill the whole ViewPager2 (use match_parent)");
+
+ ViewPager2 viewPager = new ViewPager2(InstrumentationRegistry.getContext());
+ viewPager.setAdapter(new Adapter<ViewHolder>() {
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = new View(parent.getContext());
+ view.setLayoutParams(new ViewGroup.LayoutParams(50, 50)); // arbitrary fixed size
+ return new ViewHolder(view) {
+ };
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ // do nothing
+ }
+
+ @Override
+ public int getItemCount() {
+ return 1;
+ }
+ });
+
+ viewPager.measure(0, 0); // equivalent of unspecified
+ }
+
+ @Test
+ public void childrenNotAllowed() throws Exception {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("ViewPager2 does not support direct child views");
+
+ Context context = InstrumentationRegistry.getContext();
+ ViewPager2 viewPager = new ViewPager2(context);
+ viewPager.addView(new View(context));
+ }
+
+ // TODO: verify correct padding behavior
+ // TODO: add test for screen orientation change
+ // TODO: port some of the fragment adapter tests as view adapter tests
+}
diff --git a/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java b/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java
deleted file mode 100644
index 45b42aa..0000000
--- a/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java
+++ /dev/null
@@ -1,214 +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 androidx.widget.viewpager2.tests;
-
-import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.assertion.ViewAssertions.matches;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-import static android.support.test.espresso.matcher.ViewMatchers.withText;
-import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
-import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
-import static android.view.View.OVER_SCROLL_NEVER;
-
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.espresso.IdlingRegistry;
-import android.support.test.espresso.ViewAction;
-import android.support.test.espresso.action.ViewActions;
-import android.support.test.espresso.idling.CountingIdlingResource;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.Adapter;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-import androidx.widget.ViewPager2;
-import androidx.widget.viewpager2.test.R;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ViewPager2Tests {
- private static final int[] sColors = {
- Color.parseColor("#BBA9FF00"),
- Color.parseColor("#BB00E87E"),
- Color.parseColor("#BB00C7FF"),
- Color.parseColor("#BBB30CE8"),
- Color.parseColor("#BBFF00D0")};
-
- @Rule
- public final ActivityTestRule<TestActivity> mActivityTestRule;
- @Rule
- public ExpectedException mExpectedException = ExpectedException.none();
-
- private ViewPager2 mViewPager;
- private CountingIdlingResource mIdlingResource;
-
- public ViewPager2Tests() {
- mActivityTestRule = new ActivityTestRule<>(TestActivity.class);
- }
-
- @Before
- public void setUp() {
- mViewPager = mActivityTestRule.getActivity().findViewById(R.id.view_pager);
-
- mIdlingResource = new CountingIdlingResource(getClass().getSimpleName() + "IdlingResource");
- mViewPager.addOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- if (newState == SCROLL_STATE_IDLE && !mIdlingResource.isIdleNow()) {
- mIdlingResource.decrement();
- } else if (newState == SCROLL_STATE_SETTLING && mIdlingResource.isIdleNow()) {
- mIdlingResource.increment();
- }
- }
- });
- IdlingRegistry.getInstance().register(mIdlingResource);
- }
-
- @After
- public void tearDown() {
- IdlingRegistry.getInstance().unregister(mIdlingResource);
- }
-
- @Test
- public void rendersAndHandlesSwiping() throws Throwable {
- final int pageCount = sColors.length;
-
- if (Build.VERSION.SDK_INT < 16) { // TODO(b/71500143): remove temporary workaround
- RecyclerView mRecyclerView = (RecyclerView) mViewPager.getChildAt(0);
- mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER);
- }
-
- onView(withId(mViewPager.getId())).check(matches(isDisplayed()));
- mActivityTestRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mViewPager.setAdapter(
- new Adapter<ViewHolder>() {
- @NonNull
- @Override
- public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
- int viewType) {
- return new ViewHolder(
- mActivityTestRule.getActivity().getLayoutInflater().inflate(
- R.layout.item_test_layout, parent, false)) {
- };
- }
-
- @Override
- public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
- TextView view = (TextView) holder.itemView;
- view.setText(String.valueOf(position));
- view.setBackgroundColor(sColors[position]);
- }
-
- @Override
- public int getItemCount() {
- return pageCount;
- }
- });
- }
- });
-
- final int pageIxFirst = 0;
- final int pageIxLast = pageCount - 1;
- final int swipeCount = pageCount + 1; // two swipes beyond edge to test 'edge behavior'
- int pageNumber = pageIxFirst;
- for (int i = 0; i < swipeCount; i++, pageNumber = Math.min(pageIxLast, ++pageNumber)) {
- verifyView(pageNumber);
- performSwipe(ViewActions.swipeLeft());
- }
- assertThat(pageNumber, equalTo(pageIxLast));
- for (int i = 0; i < swipeCount; i++, pageNumber = Math.max(pageIxFirst, --pageNumber)) {
- verifyView(pageNumber);
- performSwipe(ViewActions.swipeRight());
- }
- assertThat(pageNumber, equalTo(pageIxFirst));
- }
-
- private void verifyView(int pageNumber) {
- onView(allOf(withId(R.id.text_view), isDisplayed())).check(
- matches(withText(String.valueOf(pageNumber))));
- }
-
- private void performSwipe(ViewAction swipeAction) throws InterruptedException {
- onView(allOf(isDisplayed(), withId(R.id.text_view))).perform(swipeAction);
- }
-
- @Test
- public void itemViewSizeMatchParentEnforced() {
- mExpectedException.expect(IllegalStateException.class);
- mExpectedException.expectMessage(
- "Item's root view must fill the whole ViewPager2 (use match_parent)");
-
- ViewPager2 viewPager = new ViewPager2(InstrumentationRegistry.getContext());
- viewPager.setAdapter(new Adapter<ViewHolder>() {
- @NonNull
- @Override
- public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- View view = new View(parent.getContext());
- view.setLayoutParams(new ViewGroup.LayoutParams(50, 50)); // arbitrary fixed size
- return new ViewHolder(view) {
- };
- }
-
- @Override
- public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
- // do nothing
- }
-
- @Override
- public int getItemCount() {
- return 1;
- }
- });
-
- viewPager.measure(0, 0); // equivalent of unspecified
- }
-
- @Test
- public void childrenNotAllowed() throws Exception {
- mExpectedException.expect(IllegalStateException.class);
- mExpectedException.expectMessage("ViewPager2 does not support direct child views");
-
- Context context = InstrumentationRegistry.getContext();
- ViewPager2 viewPager = new ViewPager2(context);
- viewPager.addView(new View(context));
- }
-
- // TODO: verify correct padding behavior
- // TODO: add test for screen orientation change
-}
diff --git a/viewpager2/src/androidTest/res/layout/activity_test_layout.xml b/viewpager2/src/androidTest/res/layout/activity_test_layout.xml
index 3037029..9d996ab 100644
--- a/viewpager2/src/androidTest/res/layout/activity_test_layout.xml
+++ b/viewpager2/src/androidTest/res/layout/activity_test_layout.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<androidx.widget.ViewPager2
+<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view_pager"
android:layout_width="match_parent"
diff --git a/viewpager2/src/main/AndroidManifest.xml b/viewpager2/src/main/AndroidManifest.xml
index ebddd6c..cc03d91 100644
--- a/viewpager2/src/main/AndroidManifest.xml
+++ b/viewpager2/src/main/AndroidManifest.xml
@@ -14,4 +14,4 @@
limitations under the License.
-->
-<manifest package="androidx.widget.viewpager2"/>
\ No newline at end of file
+<manifest package="androidx.viewpager2"/>
\ No newline at end of file
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
new file mode 100644
index 0000000..a5d8759
--- /dev/null
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.viewpager2.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.PagerSnapHelper;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Work in progress: go/viewpager2
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class ViewPager2 extends ViewGroup {
+ // reused in layout(...)
+ private final Rect mTmpContainerRect = new Rect();
+ private final Rect mTmpChildRect = new Rect();
+
+ private RecyclerView mRecyclerView;
+
+ public ViewPager2(Context context) {
+ super(context);
+ initialize(context);
+ }
+
+ public ViewPager2(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ViewPager2(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(context);
+ }
+
+ @RequiresApi(21)
+ public ViewPager2(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ // TODO(b/70663531): handle attrs, defStyleAttr, defStyleRes
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initialize(context);
+ }
+
+ private void initialize(Context context) {
+ mRecyclerView = new RecyclerView(context);
+
+ LinearLayoutManager layoutManager = new LinearLayoutManager(context);
+ // TODO(b/69103581): add support for vertical layout
+ // TODO(b/69398856): add support for RTL
+ layoutManager.setOrientation(RecyclerView.HORIZONTAL);
+ mRecyclerView.setLayoutManager(layoutManager);
+
+ mRecyclerView.setLayoutParams(
+ new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+ // TODO(b/70666992): add automated test for orientation change
+ new PagerSnapHelper().attachToRecyclerView(mRecyclerView);
+
+ attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
+ }
+
+ /**
+ * TODO(b/70663708): decide on an Adapter class. Here supporting RecyclerView.Adapter.
+ *
+ * @see RecyclerView#setAdapter(Adapter)
+ */
+ public <VH extends ViewHolder> void setAdapter(final Adapter<VH> adapter) {
+ mRecyclerView.setAdapter(new Adapter<VH>() {
+ private final Adapter<VH> mAdapter = adapter;
+
+ @NonNull
+ @Override
+ public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ VH viewHolder = mAdapter.onCreateViewHolder(parent, viewType);
+
+ LayoutParams layoutParams = viewHolder.itemView.getLayoutParams();
+ if ((layoutParams.width | layoutParams.height) != LayoutParams.MATCH_PARENT) {
+ // TODO(b/70666614): decide if throw an exception or wrap in FrameLayout
+ // ourselves; consider accepting exact size equal to parent's exact size
+ throw new IllegalStateException(String.format(
+ "Item's root view must fill the whole %s (use match_parent)",
+ ViewPager2.this.getClass().getSimpleName()));
+ }
+
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull VH holder, int position) {
+ mAdapter.onBindViewHolder(holder, position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mAdapter.getItemCount();
+ }
+ });
+ }
+
+ /**
+ * TODO(b/70663708): decide on an Adapter class. Here supporting {@link Fragment}s.
+ *
+ * @param fragmentRetentionPolicy allows for future parameterization of Fragment memory
+ * strategy, similar to what {@link FragmentPagerAdapter} and
+ * {@link FragmentStatePagerAdapter} provide.
+ */
+ public void setAdapter(FragmentManager fragmentManager, FragmentProvider fragmentProvider,
+ @FragmentRetentionPolicy int fragmentRetentionPolicy) {
+ if (fragmentRetentionPolicy != FragmentRetentionPolicy.SAVE_STATE) {
+ throw new IllegalArgumentException("Currently only SAVE_STATE policy is supported");
+ }
+
+ mRecyclerView.setAdapter(new FragmentStateAdapter(fragmentManager, fragmentProvider));
+ }
+
+ /**
+ * Similar in behavior to {@link FragmentStatePagerAdapter}
+ * <p>
+ * Lifecycle within {@link RecyclerView}:
+ * <ul>
+ * <li>{@link RecyclerView.ViewHolder} initially an empty {@link FrameLayout}, serves as a
+ * re-usable container for a {@link Fragment} in later stages.
+ * <li>{@link RecyclerView.Adapter#onBindViewHolder} we ask for a {@link Fragment} for the
+ * position. If we already have the fragment, or have previously saved its state, we use those.
+ * <li>{@link RecyclerView.Adapter#onAttachedToWindow} we attach the {@link Fragment} to a
+ * container.
+ * <li>{@link RecyclerView.Adapter#onViewRecycled} and
+ * {@link RecyclerView.Adapter#onFailedToRecycleView} we remove, save state, destroy the
+ * {@link Fragment}.
+ * </ul>
+ */
+ private static class FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder> {
+ private final List<Fragment.SavedState> mSavedStates = new ArrayList<>();
+ // TODO: handle current item's menuVisibility userVisibleHint as FragmentStatePagerAdapter
+
+ private final FragmentManager mFragmentManager;
+ private final FragmentProvider mFragmentProvider;
+
+ private FragmentStateAdapter(FragmentManager fragmentManager,
+ FragmentProvider fragmentProvider) {
+ this.mFragmentManager = fragmentManager;
+ this.mFragmentProvider = fragmentProvider;
+ }
+
+ @NonNull
+ @Override
+ public FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return FragmentViewHolder.create(parent);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull FragmentViewHolder holder, int position) {
+ if (ViewCompat.isAttachedToWindow(holder.getContainer())) {
+ // this should never happen; if it does, it breaks our assumption that attaching
+ // a Fragment can reliably happen inside onViewAttachedToWindow
+ throw new IllegalStateException(
+ String.format("View %s unexpectedly attached to a window.",
+ holder.getContainer()));
+ }
+
+ holder.mFragment = getFragment(position);
+ }
+
+ private Fragment getFragment(int position) {
+ Fragment fragment = mFragmentProvider.getItem(position);
+ if (mSavedStates.size() > position) {
+ Fragment.SavedState savedState = mSavedStates.get(position);
+ if (savedState != null) {
+ fragment.setInitialSavedState(savedState);
+ }
+ }
+ return fragment;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(@NonNull FragmentViewHolder holder) {
+ if (holder.mFragment.isAdded()) {
+ return;
+ }
+ mFragmentManager.beginTransaction().add(holder.getContainer().getId(),
+ holder.mFragment).commitNowAllowingStateLoss();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mFragmentProvider.getCount();
+ }
+
+ @Override
+ public void onViewRecycled(@NonNull FragmentViewHolder holder) {
+ removeFragment(holder);
+ }
+
+ @Override
+ public boolean onFailedToRecycleView(@NonNull FragmentViewHolder holder) {
+ // This happens when a ViewHolder is in a transient state (e.g. during custom
+ // animation). We don't have sufficient information on how to clear up what lead to
+ // the transient state, so we are throwing away the ViewHolder to stay on the
+ // conservative side.
+ removeFragment(holder);
+ return false; // don't recycle the view
+ }
+
+ private void removeFragment(@NonNull FragmentViewHolder holder) {
+ if (holder.mFragment == null) {
+ return; // fresh ViewHolder, nothing to do
+ }
+
+ int position = holder.getAdapterPosition();
+
+ if (holder.mFragment.isAdded()) {
+ while (mSavedStates.size() <= position) {
+ mSavedStates.add(null);
+ }
+ mSavedStates.set(position,
+ mFragmentManager.saveFragmentInstanceState(holder.mFragment));
+ }
+
+ mFragmentManager.beginTransaction().remove(
+ holder.mFragment).commitNowAllowingStateLoss();
+ holder.mFragment = null;
+ }
+ }
+
+ private static class FragmentViewHolder extends RecyclerView.ViewHolder {
+ private Fragment mFragment;
+
+ private FragmentViewHolder(FrameLayout container) {
+ super(container);
+ }
+
+ static FragmentViewHolder create(ViewGroup parent) {
+ FrameLayout container = new FrameLayout(parent.getContext());
+ container.setLayoutParams(
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ container.setId(ViewCompat.generateViewId());
+ return new FragmentViewHolder(container);
+ }
+
+ FrameLayout getContainer() {
+ return (FrameLayout) itemView;
+ }
+ }
+
+ /**
+ * Provides {@link Fragment}s for pages
+ */
+ public interface FragmentProvider {
+ /**
+ * Return the Fragment associated with a specified position.
+ */
+ Fragment getItem(int position);
+
+ /**
+ * Return the number of pages available.
+ */
+ int getCount();
+ }
+
+ @Retention(CLASS)
+ @IntDef({FragmentRetentionPolicy.SAVE_STATE})
+ public @interface FragmentRetentionPolicy {
+ /** Approach similar to {@link FragmentStatePagerAdapter} */
+ int SAVE_STATE = 0;
+ }
+
+ @Override
+ public void onViewAdded(View child) {
+ // TODO(b/70666620): consider adding a support for Decor views
+ throw new IllegalStateException(
+ getClass().getSimpleName() + " does not support direct child views");
+ }
+
+ /** @see RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener) */
+ public void addOnScrollListener(RecyclerView.OnScrollListener listener) {
+ mRecyclerView.addOnScrollListener(listener);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // TODO(b/70666622): consider margin support
+ // TODO(b/70666626): consider delegating all this to RecyclerView
+ // TODO(b/70666625): write automated tests for this
+
+ measureChild(mRecyclerView, widthMeasureSpec, heightMeasureSpec);
+ int width = mRecyclerView.getMeasuredWidth();
+ int height = mRecyclerView.getMeasuredHeight();
+ int childState = mRecyclerView.getMeasuredState();
+
+ width += getPaddingLeft() + getPaddingRight();
+ height += getPaddingTop() + getPaddingBottom();
+
+ width = Math.max(width, getSuggestedMinimumWidth());
+ height = Math.max(height, getSuggestedMinimumHeight());
+
+ setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+ resolveSizeAndState(height, heightMeasureSpec,
+ childState << MEASURED_HEIGHT_STATE_SHIFT));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int width = mRecyclerView.getMeasuredWidth();
+ int height = mRecyclerView.getMeasuredHeight();
+
+ // TODO(b/70666626): consider delegating padding handling to the RecyclerView to avoid
+ // an unnatural page transition effect: http://shortn/_Vnug3yZpQT
+ mTmpContainerRect.left = getPaddingLeft();
+ mTmpContainerRect.right = r - l - getPaddingRight();
+ mTmpContainerRect.top = getPaddingTop();
+ mTmpContainerRect.bottom = b - t - getPaddingBottom();
+
+ Gravity.apply(Gravity.TOP | Gravity.START, width, height, mTmpContainerRect, mTmpChildRect);
+ mRecyclerView.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right,
+ mTmpChildRect.bottom);
+ }
+}
diff --git a/viewpager2/src/main/java/androidx/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/widget/ViewPager2.java
deleted file mode 100644
index 9ebdea1..0000000
--- a/viewpager2/src/main/java/androidx/widget/ViewPager2.java
+++ /dev/null
@@ -1,176 +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 androidx.widget;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.support.annotation.NonNull;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.PagerSnapHelper;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.Adapter;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Work in progress: go/viewpager2
- *
- * @hide
- */
-@RestrictTo(LIBRARY_GROUP)
-public class ViewPager2 extends ViewGroup {
- // reused in layout(...)
- private final Rect mTmpContainerRect = new Rect();
- private final Rect mTmpChildRect = new Rect();
-
- private RecyclerView mRecyclerView;
-
- public ViewPager2(Context context) {
- super(context);
- initialize(context);
- }
-
- public ViewPager2(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ViewPager2(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initialize(context);
- }
-
- @RequiresApi(21)
- public ViewPager2(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- // TODO(b/70663531): handle attrs, defStyleAttr, defStyleRes
- super(context, attrs, defStyleAttr, defStyleRes);
- initialize(context);
- }
-
- private void initialize(Context context) {
- mRecyclerView = new RecyclerView(context);
-
- LinearLayoutManager layoutManager = new LinearLayoutManager(context);
- // TODO(b/69103581): add support for vertical layout
- // TODO(b/69398856): add support for RTL
- layoutManager.setOrientation(RecyclerView.HORIZONTAL);
- mRecyclerView.setLayoutManager(layoutManager);
-
- mRecyclerView.setLayoutParams(
- new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-
- // TODO(b/70666992): add automated test for orientation change
- new PagerSnapHelper().attachToRecyclerView(mRecyclerView);
-
- attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
- }
-
- /**
- * TODO(b/70663708): decide on an Adapter class (for now reusing RecyclerView.Adapter)
- *
- * @see RecyclerView#setAdapter(Adapter)
- */
- public <VH extends ViewHolder> void setAdapter(final Adapter<VH> adapter) {
- mRecyclerView.setAdapter(new Adapter<VH>() {
- private final Adapter<VH> mAdapter = adapter;
-
- @NonNull
- @Override
- public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- VH viewHolder = mAdapter.onCreateViewHolder(parent, viewType);
-
- LayoutParams layoutParams = viewHolder.itemView.getLayoutParams();
- if ((layoutParams.width | layoutParams.height) != LayoutParams.MATCH_PARENT) {
- // TODO(b/70666614): decide if throw an exception or wrap in FrameLayout
- // ourselves; consider accepting exact size equal to parent's exact size
- throw new IllegalStateException(String.format(
- "Item's root view must fill the whole %s (use match_parent)",
- ViewPager2.this.getClass().getSimpleName()));
- }
-
- return viewHolder;
- }
-
- @Override
- public void onBindViewHolder(@NonNull VH holder, int position) {
- mAdapter.onBindViewHolder(holder, position);
- }
-
- @Override
- public int getItemCount() {
- return mAdapter.getItemCount();
- }
- });
- }
-
- @Override
- public void onViewAdded(View child) {
- // TODO(b/70666620): consider adding a support for Decor views
- throw new IllegalStateException(
- getClass().getSimpleName() + " does not support direct child views");
- }
-
- /** @see RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener) */
- public void addOnScrollListener(RecyclerView.OnScrollListener listener) {
- mRecyclerView.addOnScrollListener(listener);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // TODO(b/70666622): consider margin support
- // TODO(b/70666626): consider delegating all this to RecyclerView
- // TODO(b/70666625): write automated tests for this
-
- measureChild(mRecyclerView, widthMeasureSpec, heightMeasureSpec);
- int width = mRecyclerView.getMeasuredWidth();
- int height = mRecyclerView.getMeasuredHeight();
- int childState = mRecyclerView.getMeasuredState();
-
- width += getPaddingLeft() + getPaddingRight();
- height += getPaddingTop() + getPaddingBottom();
-
- width = Math.max(width, getSuggestedMinimumWidth());
- height = Math.max(height, getSuggestedMinimumHeight());
-
- setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
- resolveSizeAndState(height, heightMeasureSpec,
- childState << MEASURED_HEIGHT_STATE_SHIFT));
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int width = mRecyclerView.getMeasuredWidth();
- int height = mRecyclerView.getMeasuredHeight();
-
- // TODO(b/70666626): consider delegating padding handling to the RecyclerView to avoid
- // an unnatural page transition effect: http://shortn/_Vnug3yZpQT
- mTmpContainerRect.left = getPaddingLeft();
- mTmpContainerRect.right = r - l - getPaddingRight();
- mTmpContainerRect.top = getPaddingTop();
- mTmpContainerRect.bottom = b - t - getPaddingBottom();
-
- Gravity.apply(Gravity.TOP | Gravity.START, width, height, mTmpContainerRect, mTmpChildRect);
- mRecyclerView.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right,
- mTmpChildRect.bottom);
- }
-}
diff --git a/wear/build.gradle b/wear/build.gradle
index 2b832e7..ffc7990 100644
--- a/wear/build.gradle
+++ b/wear/build.gradle
@@ -1,6 +1,6 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/webkit-codegen/build.gradle b/webkit-codegen/build.gradle
index 92090e1..891382b 100644
--- a/webkit-codegen/build.gradle
+++ b/webkit-codegen/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
+import static androidx.build.dependencies.DependenciesKt.*
apply plugin: 'maven'
apply plugin: 'application'
diff --git a/webkit/api/current.txt b/webkit/api/current.txt
index b0d66d6..7a3fe25 100644
--- a/webkit/api/current.txt
+++ b/webkit/api/current.txt
@@ -1,5 +1,14 @@
package androidx.webkit {
+ public class WebSettingsCompat {
+ method public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
+ method public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
+ method public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+ method public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
+ method public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
+ method public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+ }
+
public class WebViewCompat {
method public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
}
diff --git a/webkit/build.gradle b/webkit/build.gradle
index 7975fc8..7d317f1 100644
--- a/webkit/build.gradle
+++ b/webkit/build.gradle
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
plugins {
id("SupportAndroidLibraryPlugin")
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java b/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
new file mode 100644
index 0000000..1e5c152
--- /dev/null
+++ b/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Build;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.webkit.WebSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class WebSettingsCompatTest {
+ WebViewOnUiThread mWebViewOnUiThread;
+
+ @Before
+ public void setUp() {
+ mWebViewOnUiThread = new androidx.webkit.WebViewOnUiThread();
+ }
+
+ @Test
+ public void testOffscreenPreRaster() {
+ // TODO(gsennton) activate this test for pre-M devices when we can pre-install a WebView APK
+ // containing support for the WebView Support Library, see b/73454652.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
+
+ assertFalse(WebSettingsCompat.getOffscreenPreRaster(mWebViewOnUiThread.getSettings()));
+
+ WebSettingsCompat.setOffscreenPreRaster(mWebViewOnUiThread.getSettings(), true);
+ assertTrue(WebSettingsCompat.getOffscreenPreRaster(mWebViewOnUiThread.getSettings()));
+ }
+
+ @Test
+ public void testEnableSafeBrowsing() throws Throwable {
+ // TODO(gsennton) activate this test for old devices when we can pre-install a WebView APK
+ // containing support for the WebView Support Library, see b/73454652.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
+
+ WebSettingsCompat.setSafeBrowsingEnabled(mWebViewOnUiThread.getSettings(), false);
+ assertFalse(WebSettingsCompat.getSafeBrowsingEnabled(mWebViewOnUiThread.getSettings()));
+ }
+
+ @Test
+ public void testDisabledActionModeMenuItems() throws Throwable {
+ // TODO(gsennton) activate this test for old devices when we can pre-install a WebView APK
+ // containing support for the WebView Support Library, see b/73454652.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return;
+
+ assertEquals(WebSettings.MENU_ITEM_NONE,
+ WebSettingsCompat.getDisabledActionModeMenuItems(mWebViewOnUiThread.getSettings()));
+
+ WebSettingsCompat.setDisabledActionModeMenuItems(mWebViewOnUiThread.getSettings(),
+ WebSettings.MENU_ITEM_SHARE);
+ assertEquals(WebSettings.MENU_ITEM_SHARE,
+ WebSettingsCompat.getDisabledActionModeMenuItems(mWebViewOnUiThread.getSettings()));
+
+ WebSettingsCompat.setDisabledActionModeMenuItems(mWebViewOnUiThread.getSettings(),
+ WebSettings.MENU_ITEM_PROCESS_TEXT | WebSettings.MENU_ITEM_WEB_SEARCH);
+ assertEquals(WebSettings.MENU_ITEM_PROCESS_TEXT | WebSettings.MENU_ITEM_WEB_SEARCH,
+ WebSettingsCompat.getDisabledActionModeMenuItems(mWebViewOnUiThread.getSettings()));
+ }
+}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
index 6219bd3..a86775b 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
@@ -17,6 +17,7 @@
package androidx.webkit;
import android.support.test.InstrumentationRegistry;
+import android.webkit.WebSettings;
import android.webkit.WebView;
public class WebViewOnUiThread {
@@ -50,7 +51,36 @@
});
}
+ public WebSettings getSettings() {
+ return getValue(new ValueGetter<WebSettings>() {
+ @Override
+ public WebSettings capture() {
+ return mWebView.getSettings();
+ }
+ });
+ }
+
public WebView getWebViewOnCurrentThread() {
return mWebView;
}
+
+ private <T> T getValue(ValueGetter<T> getter) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(getter);
+ return getter.getValue();
+ }
+
+ private abstract class ValueGetter<T> implements Runnable {
+ private T mValue;
+
+ @Override
+ public void run() {
+ mValue = capture();
+ }
+
+ protected abstract T capture();
+
+ public T getValue() {
+ return mValue;
+ }
+ }
}
diff --git a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
new file mode 100644
index 0000000..c73cda6
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import android.os.Build;
+import android.webkit.WebSettings;
+
+import androidx.webkit.internal.WebSettingsAdapter;
+import androidx.webkit.internal.WebViewGlueCommunicator;
+
+/**
+ * Compatibility version of {@link android.webkit.WebSettings}
+ */
+public class WebSettingsCompat {
+ private WebSettingsCompat() {}
+
+ // TODO(gsennton): add feature detection
+
+ /**
+ * Sets whether this WebView should raster tiles when it is
+ * offscreen but attached to a window. Turning this on can avoid
+ * rendering artifacts when animating an offscreen WebView on-screen.
+ * Offscreen WebViews in this mode use more memory. The default value is
+ * false.<br>
+ * Please follow these guidelines to limit memory usage:
+ * <ul>
+ * <li> WebView size should be not be larger than the device screen size.
+ * <li> Limit use of this mode to a small number of WebViews. Use it for
+ * visible WebViews and WebViews about to be animated to visible.
+ * </ul>
+ */
+ public static void setOffscreenPreRaster(WebSettings webSettings, boolean enabled) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ webSettings.setOffscreenPreRaster(enabled);
+ } else {
+ getAdapter(webSettings).setOffscreenPreRaster(enabled);
+ }
+ }
+
+ /**
+ * Gets whether this WebView should raster tiles when it is
+ * offscreen but attached to a window.
+ * @return {@code true} if this WebView will raster tiles when it is
+ * offscreen but attached to a window.
+ */
+ public static boolean getOffscreenPreRaster(WebSettings webSettings) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return webSettings.getOffscreenPreRaster();
+ } else {
+ return getAdapter(webSettings).getOffscreenPreRaster();
+ }
+ }
+
+ /**
+ * Sets whether Safe Browsing is enabled. Safe Browsing allows WebView to
+ * protect against malware and phishing attacks by verifying the links.
+ *
+ * <p>
+ * Safe Browsing can be disabled for all WebViews using a manifest tag (read <a
+ * href="{@docRoot}reference/android/webkit/WebView.html">general Safe Browsing info</a>). The
+ * manifest tag has a lower precedence than this API.
+ *
+ * <p>
+ * Safe Browsing is enabled by default for devices which support it.
+ *
+ * @param enabled Whether Safe Browsing is enabled.
+ */
+ public static void setSafeBrowsingEnabled(WebSettings webSettings, boolean enabled) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ webSettings.setSafeBrowsingEnabled(enabled);
+ } else {
+ getAdapter(webSettings).setSafeBrowsingEnabled(enabled);
+ }
+ }
+
+ /**
+ * Gets whether Safe Browsing is enabled.
+ * See {@link #setSafeBrowsingEnabled}.
+ *
+ * @return {@code true} if Safe Browsing is enabled and {@code false} otherwise.
+ */
+ public static boolean getSafeBrowsingEnabled(WebSettings webSettings) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ return webSettings.getSafeBrowsingEnabled();
+ } else {
+ return getAdapter(webSettings).getSafeBrowsingEnabled();
+ }
+ }
+
+ /**
+ * Disables the action mode menu items according to {@code menuItems} flag.
+ * @param menuItems an integer field flag for the menu items to be disabled.
+ */
+ public static void setDisabledActionModeMenuItems(WebSettings webSettings, int menuItems) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ webSettings.setDisabledActionModeMenuItems(menuItems);
+ } else {
+ getAdapter(webSettings).setDisabledActionModeMenuItems(menuItems);
+ }
+ }
+
+ /**
+ * Gets the action mode menu items that are disabled, expressed in an integer field flag.
+ * The default value is {@link WebSettings#MENU_ITEM_NONE}
+ *
+ * @return all the disabled menu item flags combined with bitwise OR.
+ */
+ public static int getDisabledActionModeMenuItems(WebSettings webSettings) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return webSettings.getDisabledActionModeMenuItems();
+ } else {
+ return getAdapter(webSettings).getDisabledActionModeMenuItems();
+ }
+ }
+
+ private static WebSettingsAdapter getAdapter(WebSettings webSettings) {
+ return WebViewGlueCommunicator.getCompatConverter().convertSettings(webSettings);
+ }
+}
+
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
new file mode 100644
index 0000000..091879c
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit.internal;
+
+import org.chromium.support_lib_boundary.WebSettingsBoundaryInterface;
+
+/**
+ * Adapter between WebSettingsCompat and
+ * {@link org.chromium.support_lib_boundary.WebSettingsBoundaryInterface} (the
+ * corresponding interface shared with the support library glue in the WebView APK).
+ */
+public class WebSettingsAdapter {
+ private WebSettingsBoundaryInterface mBoundaryInterface;
+
+ public WebSettingsAdapter(WebSettingsBoundaryInterface boundaryInterface) {
+ mBoundaryInterface = boundaryInterface;
+ }
+
+ /**
+ * Adapter method for {@link androidx.webkit.WebSettingsCompat#setOffscreenPreRaster}.
+ */
+ public void setOffscreenPreRaster(boolean enabled) {
+ mBoundaryInterface.setOffscreenPreRaster(enabled);
+ }
+
+ /**
+ * Adapter method for {@link androidx.webkit.WebSettingsCompat#getOffscreenPreRaster}.
+ */
+ public boolean getOffscreenPreRaster() {
+ return mBoundaryInterface.getOffscreenPreRaster();
+ }
+
+ /**
+ * Adapter method for {@link androidx.webkit.WebSettingsCompat#setSafeBrowsingEnabled}.
+ */
+ public void setSafeBrowsingEnabled(boolean enabled) {
+ mBoundaryInterface.setSafeBrowsingEnabled(enabled);
+ }
+
+ /**
+ * Adapter method for {@link androidx.webkit.WebSettingsCompat#getSafeBrowsingEnabled}.
+ */
+ public boolean getSafeBrowsingEnabled() {
+ return mBoundaryInterface.getSafeBrowsingEnabled();
+ }
+
+ /**
+ * Adapter method for {@link androidx.webkit.WebSettingsCompat#setDisabledActionModeMenuItems}.
+ */
+ public void setDisabledActionModeMenuItems(int menuItems) {
+ mBoundaryInterface.setDisabledActionModeMenuItems(menuItems);
+ }
+
+ /**
+ * Adapter method for {@link androidx.webkit.WebSettingsCompat#getDisabledActionModeMenuItems}.
+ */
+ public int getDisabledActionModeMenuItems() {
+ return mBoundaryInterface.getDisabledActionModeMenuItems();
+ }
+
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java b/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
index f97b2d8..24bb2d1 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
@@ -42,10 +42,17 @@
return LAZY_FACTORY_HOLDER.INSTANCE;
}
+ public static WebkitToCompatConverter getCompatConverter() {
+ return LAZY_FACTORY_HOLDER.COMPAT_CONVERTER;
+ }
+
private static class LAZY_FACTORY_HOLDER {
static final WebViewProviderFactoryAdapter INSTANCE =
new WebViewProviderFactoryAdapter(
WebViewGlueCommunicator.createGlueProviderFactory());
+ static final WebkitToCompatConverter COMPAT_CONVERTER =
+ new WebkitToCompatConverter(
+ INSTANCE.getWebkitToCompatConverter());
}
private static InvocationHandler fetchGlueProviderFactoryImpl() {
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
index d961c09..fe98a56 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
@@ -21,6 +21,7 @@
import org.chromium.support_lib_boundary.BoundaryInterfaceReflectionUtil;
import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
import org.chromium.support_lib_boundary.WebViewProviderFactoryBoundaryInterface;
+import org.chromium.support_lib_boundary.WebkitToCompatConverterBoundaryInterface;
/**
* Adapter for WebViewProviderFactoryBoundaryInterface providing static WebView functionality
@@ -42,4 +43,14 @@
return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
WebViewProviderBoundaryInterface.class, mImpl.createWebView(webview));
}
+
+ /**
+ * Adapter method for creating a new support library version of
+ * {@link androidx.webkit.internal.WebkitToCompatConverter}, which converts android.webkit
+ * classes into their corresponding support library classes.
+ */
+ public WebkitToCompatConverterBoundaryInterface getWebkitToCompatConverter() {
+ return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
+ WebkitToCompatConverterBoundaryInterface.class, mImpl.getWebkitToCompatConverter());
+ }
}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebkitToCompatConverter.java b/webkit/src/main/java/androidx/webkit/internal/WebkitToCompatConverter.java
new file mode 100644
index 0000000..cb40530
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/WebkitToCompatConverter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit.internal;
+
+import android.webkit.WebSettings;
+
+import org.chromium.support_lib_boundary.BoundaryInterfaceReflectionUtil;
+import org.chromium.support_lib_boundary.WebSettingsBoundaryInterface;
+import org.chromium.support_lib_boundary.WebkitToCompatConverterBoundaryInterface;
+
+/**
+ * A class providing functionality for converting android.webkit classes into support library
+ * classes.
+ */
+public class WebkitToCompatConverter {
+ private final WebkitToCompatConverterBoundaryInterface mImpl;
+
+ public WebkitToCompatConverter(WebkitToCompatConverterBoundaryInterface impl) {
+ mImpl = impl;
+ }
+
+ /**
+ * Return a WebSettingsAdapter linked to webSettings such that calls on either of those
+ * objects affect the other object. That WebSettingsAdapter can be used to implement
+ * {@link androidx.webkit.WebSettingsCompat}.
+ */
+ public WebSettingsAdapter convertSettings(WebSettings webSettings) {
+ return new WebSettingsAdapter(BoundaryInterfaceReflectionUtil.castToSuppLibClass(
+ WebSettingsBoundaryInterface.class, mImpl.convertSettings(webSettings)));
+ }
+}