Merge "Merge stage-aosp-master into AOSP master."
diff --git a/.gitignore b/.gitignore
index abfef7c..162af55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,8 @@
.classpath
.gradle
-.idea/
-!.idea/codeStyleSettings.xml
+**/.idea/**
+!**/.idea/codeStyleSettings.xml
+!**/.idea/copyright
!.idea/copyright/AndroidCopyright.xml
!.idea/copyright/profiles_settings.xml
!.idea/vcs.xml
diff --git a/annotations/build.gradle b/annotations/build.gradle
index dd4a5bc..df521e1 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportJavaLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportJavaLibraryPlugin")
+}
jar {
from sourceSets.main.output
@@ -45,8 +50,10 @@
}
supportLibrary {
- name 'Android Support Library Annotations'
- publish true
- inceptionYear '2013'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs."
+ name = "Android Support Library Annotations"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2013"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs."
}
\ No newline at end of file
diff --git a/app-toolkit/.idea/codeStyleSettings.xml b/app-toolkit/.idea/codeStyleSettings.xml
new file mode 120000
index 0000000..13e0b76
--- /dev/null
+++ b/app-toolkit/.idea/codeStyleSettings.xml
@@ -0,0 +1 @@
+../../.idea/codeStyleSettings.xml
\ No newline at end of file
diff --git a/app-toolkit/.idea/copyright b/app-toolkit/.idea/copyright
new file mode 120000
index 0000000..51a82b0
--- /dev/null
+++ b/app-toolkit/.idea/copyright
@@ -0,0 +1 @@
+../../.idea/copyright
\ No newline at end of file
diff --git a/app-toolkit/build.gradle b/app-toolkit/build.gradle
index 155a836..9bcb827 100644
--- a/app-toolkit/build.gradle
+++ b/app-toolkit/build.gradle
@@ -18,10 +18,11 @@
ext.supportRootFolder = new File(project.projectDir, "../")
apply from: 'buildSrc/repos.gradle'
apply from: 'init.gradle'
+ apply from: "buildSrc/build_dependencies.gradle"
repos.addMavenRepositories(repositories)
dependencies {
- classpath libs.jacoco
- classpath libs.gradle
- classpath libs.kotlin.gradle_plugin
+ classpath build_libs.jacoco
+ classpath build_libs.gradle
+ classpath build_libs.kotlin.gradle_plugin
}
}
diff --git a/app-toolkit/common/build.gradle b/app-toolkit/common/build.gradle
index 4f51a49..96042108 100644
--- a/app-toolkit/common/build.gradle
+++ b/app-toolkit/common/build.gradle
@@ -14,10 +14,13 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension;
-apply plugin: android.support.SupportJavaLibraryPlugin
+plugins {
+ id("SupportJavaLibraryPlugin")
+}
dependencies {
compile libs.support.annotations
@@ -28,11 +31,12 @@
createAndroidCheckstyle(project)
-version = LibraryVersions.ARCH_CORE.toString()
supportLibrary {
- name 'Android Arch-Common'
- publish true
- inceptionYear '2017'
- description "Android Arch-Common"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Arch-Common"
+ publish = true
+ mavenVersion = LibraryVersions.ARCH_CORE
+ mavenGroup = LibraryGroups.ARCH_CORE
+ inceptionYear = "2017"
+ description = "Android Arch-Common"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/app-toolkit/core-testing/build.gradle b/app-toolkit/core-testing/build.gradle
index 127078d..adb93e6 100644
--- a/app-toolkit/core-testing/build.gradle
+++ b/app-toolkit/core-testing/build.gradle
@@ -14,59 +14,42 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
- compile project(":arch:runtime")
- compile libs.support.annotations
- compile(libs.junit)
- compile libs.mockito_core, { exclude group: 'net.bytebuddy' }
+ api project(":arch:runtime")
+ api libs.support.annotations
+ api libs.junit
+ api libs.mockito_core, { exclude group: 'net.bytebuddy' }
- testCompile libs.junit
- testCompile libs.support.annotations
+ testImplementation libs.junit
+ testImplementation libs.support.annotations
- androidTestCompile libs.junit
+ androidTestImplementation libs.junit
androidTestImplementation libs.test_runner, { exclude module: 'support-annotations' }
androidTestImplementation libs.espresso_core, { exclude module: 'support-annotations' }
}
createAndroidCheckstyle(project)
-android.libraryVariants.all { variant ->
- def name = variant.buildType.name
- def suffix = name.capitalize()
- project.tasks.create(name: "jar${suffix}", type: Jar) {
- dependsOn variant.javaCompile
- from variant.javaCompile.destinationDir
- destinationDir new File(project.buildDir, "libJar")
- }
-}
-
-version = LibraryVersions.ARCH_CORE_TESTING.toString()
supportLibrary {
- name 'Android Core-Testing'
- publish true
- inceptionYear '2017'
- description "Android Core-Testing"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Core-Testing"
+ publish = true
+ mavenVersion = LibraryVersions.ARCH_CORE_TESTING
+ mavenGroup = LibraryGroups.ARCH_CORE
+ inceptionYear = "2017"
+ description = "Android Core-Testing"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/app-toolkit/core-testing/lint-baseline.xml b/app-toolkit/core-testing/lint-baseline.xml
new file mode 100644
index 0000000..3ce85c7
--- /dev/null
+++ b/app-toolkit/core-testing/lint-baseline.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+ <issue
+ id="InvalidPackage"
+ message="Invalid package reference in library; not included in Android: `java.lang.management`. Referenced from `org.junit.internal.runners.statements.FailOnTimeout`.">
+ <location
+ file="../../../../prebuilts/tools/common/m2/repository/junit/junit/4.12/junit-4.12.jar"/>
+ </issue>
+
+ <issue
+ id="InvalidPackage"
+ message="Invalid package reference in library; not included in Android: `java.lang.instrument`. Referenced from `org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker`.">
+ <location
+ file="../../../../prebuilts/tools/common/m2/repository/org/mockito/mockito-core/2.7.6/mockito-core-2.7.6.jar"/>
+ </issue>
+
+</issues>
diff --git a/app-toolkit/dependencies.gradle b/app-toolkit/dependencies.gradle
index cb68089..e760149 100644
--- a/app-toolkit/dependencies.gradle
+++ b/app-toolkit/dependencies.gradle
@@ -22,7 +22,7 @@
ffLibs = libs
}
def ffVersions = [:]
-ffVersions.kotlin = "1.1.3"
+ffVersions.kotlin = "1.1.51"
ffVersions.auto_common = "0.6"
ffVersions.javapoet = "1.8.0"
ffVersions.compile_testing = "0.11"
@@ -42,7 +42,6 @@
ffLibs.kotlin = [
stdlib : "org.jetbrains.kotlin:kotlin-stdlib:$ffVersions.kotlin",
- gradle_plugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$ffVersions.kotlin"
]
ffLibs.auto_common = "com.google.auto:auto-common:$ffVersions.auto_common"
ffLibs.apache = [
diff --git a/app-toolkit/gradle/wrapper/gradle-wrapper.properties b/app-toolkit/gradle/wrapper/gradle-wrapper.properties
index d90766a..9d09896 100644
--- a/app-toolkit/gradle/wrapper/gradle-wrapper.properties
+++ b/app-toolkit/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=../../../../../tools/external/gradle/gradle-4.1-bin.zip
+distributionUrl=../../../../../tools/external/gradle/gradle-4.3-bin.zip
diff --git a/app-toolkit/init.gradle b/app-toolkit/init.gradle
index d317d33..1d6b752 100644
--- a/app-toolkit/init.gradle
+++ b/app-toolkit/init.gradle
@@ -92,14 +92,6 @@
buildServerAnchorTask.dependsOn createDiffArchive
buildServerAnchorTask.dependsOn createArchive
-rootProject.ext.flatfootProjectGroups = [
- "room" : "android.arch.persistence.room",
- "persistence" : "android.arch.persistence",
- "lifecycle" : "android.arch.lifecycle",
- "arch" : "android.arch.core",
- "paging" : "android.arch.paging",
- "navigation" : "android.arch.navigation"]
-
subprojects {
repos.addMavenRepositories(project.repositories)
if (project.name == 'doclava' || project.name == 'jdiff') {
@@ -111,18 +103,6 @@
return
}
- def projectPath = project.getPath().split(":")
- def mavenGroup = projectPath[1]
- def finalGroup = rootProject.flatfootProjectGroups[mavenGroup]
-
- if (finalGroup == null) {
- return
- }
- if (projectPath.size() == 2) {// root project.
- return
- }
- project.group = finalGroup
-
if (project.getPath().contains("integration-tests")) {
// disable upload tasks
project.tasks.whenTaskAdded { task ->
diff --git a/app-toolkit/runtime/build.gradle b/app-toolkit/runtime/build.gradle
index 434d42b..3b46384 100644
--- a/app-toolkit/runtime/build.gradle
+++ b/app-toolkit/runtime/build.gradle
@@ -14,53 +14,33 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.SupportLibraryExtension
import android.support.LibraryVersions
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
- compile libs.support.annotations
- compile project(":arch:common")
+ api libs.support.annotations
+ api project(":arch:common")
}
createAndroidCheckstyle(project)
-android.libraryVariants.all { variant ->
- def name = variant.buildType.name
- def suffix = name.capitalize()
- def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
- dependsOn variant.javaCompile
- from variant.javaCompile.destinationDir
- destinationDir new File(project.buildDir, "libJar")
- }
-}
-
-version = LibraryVersions.ARCH_RUNTIME.toString()
supportLibrary {
- name 'Android Arch-Runtime'
- publish true
- inceptionYear '2017'
- description "Android Arch-Runtime"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Arch-Runtime"
+ publish = true
+ mavenVersion = LibraryVersions.ARCH_RUNTIME
+ mavenGroup = LibraryGroups.ARCH_CORE
+ inceptionYear = "2017"
+ description = "Android Arch-Runtime"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/app-toolkit/runtime/lint-baseline.xml b/app-toolkit/runtime/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/app-toolkit/runtime/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/build.gradle b/build.gradle
index 8c32175b..ec83da5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,14 +17,14 @@
buildscript {
ext.supportRootFolder = project.projectDir
apply from: 'buildSrc/repos.gradle'
-
apply from: 'buildSrc/init.gradle'
+ apply from: 'buildSrc/build_dependencies.gradle'
init.setSdkInLocalPropertiesFile()
repos.addMavenRepositories(repositories)
dependencies {
- classpath libs.gradle
- classpath libs.jacoco
+ classpath build_libs.gradle
+ classpath build_libs.jacoco
}
}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 9f648d9..9c0d8af 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -1,17 +1,37 @@
-apply plugin: 'groovy'
-apply plugin: 'java'
+buildscript {
+ def supportRootFolder = project.projectDir.getParentFile()
+ repositories {
+ maven {
+ url "${supportRootFolder}/../../prebuilts/tools/common/m2/repository"
+ }
+ }
-apply from: "dependencies.gradle"
+ apply from: "build_dependencies.gradle"
+
+ dependencies {
+ classpath build_libs.kotlin.gradle_plugin
+ }
+}
+
+apply from: "build_dependencies.gradle"
ext.supportRootFolder = project.projectDir.getParentFile()
apply from: 'repos.gradle'
repos.addMavenRepositories(repositories)
+apply plugin: 'groovy'
+apply plugin: 'java'
+apply plugin: 'kotlin'
+
+compileGroovy {
+ dependsOn tasks.getByPath('compileKotlin')
+ classpath += files(compileKotlin.destinationDir)
+}
dependencies {
- compile libs.gradle
- compile libs.jacoco
- compile libs.error_prone
- compile libs.jarjar_gradle
+ compile build_libs.gradle
+ compile build_libs.jacoco
+ compile build_libs.error_prone
+ compile build_libs.jarjar_gradle
compile gradleApi()
}
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
new file mode 100644
index 0000000..dc41841
--- /dev/null
+++ b/buildSrc/build_dependencies.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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.
+ */
+
+def build_libs = [:]
+
+def androidPluginVersionOverride = System.getenv("GRADLE_PLUGIN_VERSION")
+
+if (androidPluginVersionOverride != null) {
+ build_libs.gradle = 'com.android.tools.build:gradle:' + androidPluginVersionOverride
+} else {
+ // Keep gradle plugin version in sync with ub_supportlib-master manifest.
+ build_libs.gradle = 'com.android.tools.build:gradle:3.0.0'
+}
+
+// jarjar plugin
+build_libs.jarjar_gradle = 'org.anarres.jarjar:jarjar-gradle:1.0.0'
+build_libs.error_prone = 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10'
+build_libs.jacoco = 'org.jacoco:org.jacoco.core:0.7.8'
+build_libs.kotlin = [gradle_plugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.51"]
+
+rootProject.ext['build_libs'] = build_libs
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index ebeaa7e..b61cfc6 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -24,26 +24,12 @@
libs.test_rules = 'com.android.support.test:rules:1.0.1'
libs.espresso_core = 'com.android.support.test.espresso:espresso-core:3.0.1'
libs.espresso_contrib = 'com.android.support.test.espresso:espresso-contrib:3.0.1'
-libs.jacoco = 'org.jacoco:org.jacoco.core:0.7.8'
-
-def androidPluginVersionOverride = System.getenv("GRADLE_PLUGIN_VERSION")
-
-if (androidPluginVersionOverride != null) {
- libs.gradle = 'com.android.tools.build:gradle:' + androidPluginVersionOverride
-} else {
- // Keep gradle plugin version in sync with ub_supportlib-master manifest.
- libs.gradle = 'com.android.tools.build:gradle:3.0.0'
-}
//arch components
-libs.arch_lifecycle_runtime = "android.arch.lifecycle:runtime:1.0.0@aar"
+libs.arch_lifecycle_runtime = "android.arch.lifecycle:runtime:1.0.3@aar"
// Other dependencies
libs.xml_parser_apis = 'xerces:xmlParserAPIs:2.6.2'
libs.xerces_impl = 'xerces:xercesImpl:2.6.2'
-libs.error_prone = 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10'
-
-// jarjar plugin
-libs.jarjar_gradle = 'org.anarres.jarjar:jarjar-gradle:1.0.0'
rootProject.ext['libs'] = libs
diff --git a/buildSrc/diff_and_docs.gradle b/buildSrc/diff_and_docs.gradle
index 876d1f3..2fcc893 100644
--- a/buildSrc/diff_and_docs.gradle
+++ b/buildSrc/diff_and_docs.gradle
@@ -545,9 +545,11 @@
"ignoring API tasks.")
return
}
- initializeApiChecksForProject(project)
- registerJavaProjectForDocsTask(project.generateApi, project.compileJava)
- registerJavaProjectForDocsTask(project.generateDiffs, project.compileJava)
+ project.afterEvaluate {
+ initializeApiChecksForProject(project)
+ registerJavaProjectForDocsTask(project.generateApi, project.compileJava)
+ registerJavaProjectForDocsTask(project.generateDiffs, project.compileJava)
+ }
}
}
}
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index 44116d6..6b613d0 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -162,10 +162,6 @@
}
project.ext.currentSdk = gradle.ext.currentSdk
- apply plugin: 'maven'
-
- version = LibraryVersions.SUPPORT_LIBRARY.toString();
- group = 'com.android.support'
project.plugins.whenPluginAdded { plugin ->
def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin"
diff --git a/buildSrc/src/main/groovy/android/support/FlatfootAndroidLibraryPlugin.groovy b/buildSrc/src/main/groovy/android/support/FlatfootAndroidLibraryPlugin.groovy
deleted file mode 100644
index 47a72d7..0000000
--- a/buildSrc/src/main/groovy/android/support/FlatfootAndroidLibraryPlugin.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support
-
-import com.android.build.gradle.LibraryExtension
-import com.google.common.collect.ImmutableMap
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-
-/**
- * Flatfoot Android library specific plugin that sets common configurations needed for
- * flatfoot library modules.
- */
-class FlatfootAndroidLibraryPlugin implements Plugin<Project> {
- @Override
- public void apply(Project project) {
- SupportLibraryExtension supportLibraryExtension =
- project.extensions.create("supportLibrary", SupportLibraryExtension, project);
- SupportLibraryMavenUploader.apply(project, supportLibraryExtension);
- VersionFileWriterTask.setUpAndroidLibrary(project);
-
- project.apply(ImmutableMap.of("plugin", "com.android.library"));
-
- LibraryExtension library = project.extensions.findByType(LibraryExtension.class);
- SourceJarTaskHelper.setUpAndroidProject(project, library);
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/android/support/SupportAndroidLibraryPlugin.groovy b/buildSrc/src/main/groovy/android/support/SupportAndroidLibraryPlugin.groovy
deleted file mode 100644
index 86469ee..0000000
--- a/buildSrc/src/main/groovy/android/support/SupportAndroidLibraryPlugin.groovy
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2016 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
-
-import com.android.build.gradle.LibraryExtension
-import com.android.build.gradle.api.LibraryVariant
-import com.android.builder.core.BuilderConstants
-import com.google.common.collect.ImmutableMap
-import net.ltgt.gradle.errorprone.ErrorProneBasePlugin
-import net.ltgt.gradle.errorprone.ErrorProneToolChain
-import org.gradle.api.Action
-import org.gradle.api.JavaVersion
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.tasks.bundling.Jar
-
-/**
- * Support library specific com.android.library plugin that sets common configurations needed for
- * support library modules.
- */
-class SupportAndroidLibraryPlugin implements Plugin<Project> {
- private static final String INSTRUMENTATION_RUNNER =
- "android.support.test.runner.AndroidJUnitRunner";
-
- @Override
- public void apply(Project project) {
- SupportLibraryExtension supportLibraryExtension =
- project.extensions.create("supportLibrary", SupportLibraryExtension, project);
- SupportLibraryMavenUploader.apply(project, supportLibraryExtension);
-
- project.afterEvaluate {
- LibraryExtension library = project.extensions.findByType(LibraryExtension.class);
-
- // Java 8 is only fully supported on API 24+ and not all Java 8 features are binary
- // compatible with API < 24, so use Java 7 for both source AND target.
- final JavaVersion javaVersion;
- if (supportLibraryExtension.java8Library) {
- if (library.defaultConfig.minSdkVersion.apiLevel < 24) {
- throw new IllegalArgumentException("Libraries can only support Java 8 if "
- + "minSdkVersion is 24 or higher");
- }
- javaVersion = JavaVersion.VERSION_1_8
- } else {
- javaVersion = JavaVersion.VERSION_1_7
- }
-
- library.compileOptions {
- sourceCompatibility javaVersion
- targetCompatibility javaVersion
- }
- }
-
- VersionFileWriterTask.setUpAndroidLibrary(project);
-
- project.apply(ImmutableMap.of("plugin", "com.android.library"));
- project.apply(ImmutableMap.of("plugin", ErrorProneBasePlugin.class));
-
- LibraryExtension library = project.extensions.findByType(LibraryExtension.class);
-
- library.compileSdkVersion project.currentSdk
-
- library.defaultConfig {
- // Update the version meta-data in each Manifest.
- addManifestPlaceholders(["target-sdk-version": project.currentSdk])
-
- // Set test related options.
- testInstrumentationRunner INSTRUMENTATION_RUNNER
- }
-
- library.signingConfigs {
- debug {
- // Use a local debug keystore to avoid build server issues.
- storeFile project.rootProject.init.debugKeystore
- }
- }
-
- library.sourceSets {
- main {
- // We use a non-standard manifest path.
- manifest.srcFile 'AndroidManifest.xml'
- }
-
- androidTest {
- // We use a non-standard test directory structure.
- root 'tests'
- java.srcDir 'tests/src'
- res.srcDir 'tests/res'
- manifest.srcFile 'tests/AndroidManifest.xml'
- }
- }
-
- // Always lint check NewApi as fatal.
- library.lintOptions {
- abortOnError true
- ignoreWarnings true
-
- // Write output directly to the console (and nowhere else).
- textOutput 'stderr'
- textReport true
- htmlReport false
- //xmlReport false
-
- // Format output for convenience.
- explainIssues true
- noLines false
- quiet true
-
- // Always fail on NewApi.
- error 'NewApi'
- }
-
- // Set baseline file for all legacy lint warnings.
- if (System.getenv("GRADLE_PLUGIN_VERSION") != null) {
- library.lintOptions.check 'NewApi'
- } else {
- library.lintOptions.baseline new File(project.projectDir, "/lint-baseline.xml")
- }
-
- if (project.rootProject.usingFullSdk) {
- // Library projects don't run lint by default, so set up dependency.
- project.uploadArchives.dependsOn "lintRelease"
- }
-
- SourceJarTaskHelper.setUpAndroidProject(project, library);
-
- final ErrorProneToolChain toolChain = ErrorProneToolChain.create(project);
- library.getBuildTypes().create("errorProne")
- library.getLibraryVariants().all(new Action<LibraryVariant>() {
- @Override
- void execute(LibraryVariant libraryVariant) {
- if (libraryVariant.getBuildType().getName().equals("errorProne")) {
- libraryVariant.getJavaCompile().setToolChain(toolChain);
-
- libraryVariant.getJavaCompile().options.compilerArgs += [
- '-XDcompilePolicy=simple', // Workaround for b/36098770
-
- // Enforce the following checks.
- '-Xep:MissingOverride:ERROR',
- '-Xep:NarrowingCompoundAssignment:ERROR',
- '-Xep:ClassNewInstance:ERROR',
- '-Xep:ClassCanBeStatic:ERROR',
- '-Xep:SynchronizeOnNonFinalField:ERROR',
- '-Xep:OperatorPrecedence:ERROR'
- ]
- }
- }
- })
- }
-}
diff --git a/buildSrc/src/main/groovy/android/support/SupportJavaLibraryPlugin.groovy b/buildSrc/src/main/groovy/android/support/SupportJavaLibraryPlugin.groovy
deleted file mode 100644
index 3113e6c..0000000
--- a/buildSrc/src/main/groovy/android/support/SupportJavaLibraryPlugin.groovy
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support
-
-import com.google.common.collect.ImmutableMap
-import org.gradle.api.JavaVersion
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.plugins.JavaPluginConvention
-
-/**
- * Support java library specific plugin that sets common configurations needed for
- * support library modules.
- */
-class SupportJavaLibraryPlugin implements Plugin<Project> {
- @Override
- public void apply(Project project) {
- SupportLibraryExtension supportLibraryExtension =
- project.extensions.create("supportLibrary", SupportLibraryExtension, project);
- SupportLibraryMavenUploader.apply(project, supportLibraryExtension);
-
- project.apply(ImmutableMap.of("plugin", "java"));
- project.afterEvaluate {
- project.compileJava {
- def version = supportLibraryExtension.java8Library ?
- JavaVersion.VERSION_1_8 : JavaVersion.VERSION_1_7
- sourceCompatibility = version
- targetCompatibility = version
- }
- }
-
- SourceJarTaskHelper.setUpJavaProject(project);
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/android/support/SupportKotlinLibraryPlugin.groovy b/buildSrc/src/main/groovy/android/support/SupportKotlinLibraryPlugin.groovy
index 237aa97..78b47777 100644
--- a/buildSrc/src/main/groovy/android/support/SupportKotlinLibraryPlugin.groovy
+++ b/buildSrc/src/main/groovy/android/support/SupportKotlinLibraryPlugin.groovy
@@ -30,7 +30,7 @@
public void apply(Project project) {
SupportLibraryExtension supportLibraryExtension =
project.extensions.create("supportLibrary", SupportLibraryExtension, project);
- SupportLibraryMavenUploader.apply(project, supportLibraryExtension);
+ MavenUploadHelperKt.apply(project, supportLibraryExtension);
project.apply(ImmutableMap.of("plugin", "kotlin"));
project.apply(ImmutableMap.of("plugin", "kotlin-kapt"));
diff --git a/buildSrc/src/main/groovy/android/support/SupportLibraryExtension.groovy b/buildSrc/src/main/groovy/android/support/SupportLibraryExtension.groovy
deleted file mode 100644
index 93afb9a..0000000
--- a/buildSrc/src/main/groovy/android/support/SupportLibraryExtension.groovy
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support;
-
-import org.gradle.api.Project
-
-/**
- * Extension for {@link SupportAndroidLibraryPlugin} and {@link SupportJavaLibraryPlugin}.
- */
-class SupportLibraryExtension {
- static final String ARCHITECTURE_URL =
- "https://developer.android.com/topic/libraries/architecture/index.html";
- static final String SUPPORT_URL =
- "http://developer.android.com/tools/extras/support-library.html";
-
- Project project
- String name;
- String description;
- String inceptionYear;
- String url = SUPPORT_URL;
- Collection<License> licenses = [];
- boolean java8Library = false;
- boolean publish = false;
-
- SupportLibraryExtension(Project project) {
- this.project = project
- }
-
- License license(Closure closure) {
- def license = project.configure(new License(), closure)
- licenses.add(license)
- return license
- }
-
- class License {
- String name;
- String url;
-
- void url(String p) {
- url = p
- }
-
- void name(String p) {
- name = p
- }
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/android/support/SupportLibraryMavenUploader.groovy b/buildSrc/src/main/groovy/android/support/SupportLibraryMavenUploader.groovy
deleted file mode 100644
index 957cae8..0000000
--- a/buildSrc/src/main/groovy/android/support/SupportLibraryMavenUploader.groovy
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support
-
-import com.android.build.gradle.LibraryPlugin
-import com.google.common.collect.ImmutableMap
-import org.gradle.api.Action
-import org.gradle.api.Project
-import org.gradle.api.artifacts.ProjectDependency
-import org.gradle.api.artifacts.maven.MavenDeployer
-import org.gradle.api.tasks.Upload
-
-class SupportLibraryMavenUploader {
- static void apply(Project project, SupportLibraryExtension supportLibraryExtension) {
- project.apply(ImmutableMap.of("plugin", "maven"));
-
- // Set uploadArchives options.
- Upload uploadTask = (Upload) project.getTasks().getByName("uploadArchives");
- project.afterEvaluate {
- if (supportLibraryExtension.publish) {
- uploadTask.getRepositories().withType(MavenDeployer.class, new Action<MavenDeployer>() {
- @Override
- public void execute(MavenDeployer mavenDeployer) {
- mavenDeployer.getPom().project {
- name supportLibraryExtension.getName()
- description supportLibraryExtension.getDescription()
- url supportLibraryExtension.getUrl()
- inceptionYear supportLibraryExtension.getInceptionYear()
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
-
- supportLibraryExtension.getLicenses().each {
- SupportLibraryExtension.License supportLicense ->
- license {
- name supportLicense.name
- url supportLicense.url
- distribution 'repo'
- }
- }
- }
-
- scm {
- url "http://source.android.com"
- connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
-
- uploadTask.doFirst {
- Set<ProjectDependency> allDeps = new HashSet<>();
- collectDependenciesForConfiguration(allDeps, project, "api");
- collectDependenciesForConfiguration(allDeps, project, "implementation");
- collectDependenciesForConfiguration(allDeps, project, "compile");
-
- mavenDeployer.getPom().whenConfigured {
- it.dependencies.forEach { dep ->
- if (isAndroidProject(dep.groupId, dep.artifactId, allDeps)) {
- dep.type = "aar"
- }
- }
- }
- }
- }
- });
- } else {
- uploadTask.enabled = false;
- }
- }
- }
-
- private static void collectDependenciesForConfiguration(Set<ProjectDependency> dependencies,
- Project project, String name) {
- def config = project.configurations.findByName(name);
- if (config != null) {
- config.dependencies.withType(ProjectDependency.class).forEach {
- dep -> dependencies.add(dep)
- }
- }
- }
-
- private static boolean isAndroidProject(String groupId, String artifactId, Set<ProjectDependency> deps) {
- for (ProjectDependency dep : deps) {
- if (dep.group == groupId && dep.name == artifactId) {
- return dep.getDependencyProject().plugins.hasPlugin(LibraryPlugin.class)
- }
- }
- return false;
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/android/support/checkapi/CheckApiTask.groovy b/buildSrc/src/main/groovy/android/support/checkapi/CheckApiTask.groovy
deleted file mode 100644
index 4f829f6..0000000
--- a/buildSrc/src/main/groovy/android/support/checkapi/CheckApiTask.groovy
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2016 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.checkapi
-
-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.InputFiles
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-
-import java.security.MessageDigest
-
-/**
- * Task used to verify changes between two API files.
- * <p>
- * This task may be configured to ignore, warn, or fail with a message for a specific set of
- * Doclava-defined error codes. See {@link com.google.doclava.Errors} for a complete list of
- * supported error codes.
- * <p>
- * Specific failures may be ignored by specifying a list of SHAs in {@link #whitelistErrors}. Each
- * SHA is unique to a specific API change and is logged to the error output on failure.
- */
-public class CheckApiTask extends DefaultTask {
- /** Character that resets console output color. */
- private static final String ANSI_RESET = "\u001B[0m";
-
- /** Character that sets console output color to red. */
- private static final String ANSI_RED = "\u001B[31m";
-
- /** Character that sets console output color to yellow. */
- private static final String ANSI_YELLOW = "\u001B[33m";
-
- /** API file that represents the existing API surface. */
- @Optional
- @InputFile
- File oldApiFile
-
- /** API file that represents the existing API surface's removals. */
- @Optional
- @InputFile
- File oldRemovedApiFile
-
- /** API file that represents the candidate API surface. */
- @InputFile
- File newApiFile
-
- /** API file that represents the candidate API surface's removals. */
- @Optional
- @InputFile
- File newRemovedApiFile
-
- /** Optional file containing a newline-delimited list of error SHAs to ignore. */
- File whitelistErrorsFile
-
- @Optional
- @InputFile
- File getWhiteListErrorsFileInput() {
- // Gradle requires non-null InputFiles to exist -- even with Optional -- so work around that
- // by returning null for this field if the file doesn't exist.
- if (whitelistErrorsFile && whitelistErrorsFile.exists()) {
- return whitelistErrorsFile;
- }
- return null;
- }
-
- /**
- * Optional list of packages to ignore.
- * <p>
- * Packages names will be matched exactly; sub-packages are not automatically recognized.
- */
- @Optional
- @Input
- Collection ignoredPackages
-
- /**
- * Optional list of classes to ignore.
- * <p>
- * Class names will be matched exactly by their fully-qualified names; inner classes are not
- * automatically recognized.
- */
- @Optional
- @Input
- Collection ignoredClasses
-
- /**
- * Optional set of error SHAs to ignore.
- * <p>
- * Each error SHA is unique to a specific API change.
- */
- @Optional
- @Input
- Set whitelistErrors = []
-
- // Errors that were both detected and whitelisted.
- Set detectedWhitelistErrors = []
-
- @InputFiles
- Collection<File> doclavaClasspath
-
- // A dummy output file meant only to tag when this check was last ran.
- // Without any outputs, Gradle will run this task every time.
- @Optional
- private File mOutputFile
-
- @OutputFile
- public File getOutputFile() {
- return mOutputFile ?: new File(project.buildDir, "checkApi/${name}-completed")
- }
-
- @Optional
- public void setOutputFile(File outputFile) {
- mOutputFile = outputFile
- }
-
- /**
- * List of Doclava error codes to treat as errors.
- * <p>
- * See {@link com.google.doclava.Errors} for a complete list of error codes.
- */
- @Input
- Collection checkApiErrors
-
- /**
- * List of Doclava error codes to treat as warnings.
- * <p>
- * See {@link com.google.doclava.Errors} for a complete list of error codes.
- */
- @Input
- Collection checkApiWarnings
-
- /**
- * List of Doclava error codes to ignore.
- * <p>
- * See {@link com.google.doclava.Errors} for a complete list of error codes.
- */
- @Input
- Collection checkApiHidden
-
- /** Message to display on API check failure. */
- @Input
- String onFailMessage
-
- public CheckApiTask() {
- group = 'Verification'
- description = 'Invoke Doclava\'s ApiCheck tool to make sure current.txt is up to date.'
- }
-
- private Set<File> collectAndVerifyInputs() {
- if (getOldRemovedApiFile() != null && getNewRemovedApiFile() != null) {
- return [getOldApiFile(), getNewApiFile(), getOldRemovedApiFile(),
- getNewRemovedApiFile()] as Set
- } else {
- return [getOldApiFile(), getNewApiFile()] as Set
- }
- }
-
- public void setCheckApiErrors(Collection errors) {
- // Make it serializable.
- checkApiErrors = errors as int[]
- }
-
- public void setCheckApiWarnings(Collection warnings) {
- // Make it serializable.
- checkApiWarnings = warnings as int[]
- }
-
- public void setCheckApiHidden(Collection hidden) {
- // Make it serializable.
- checkApiHidden = hidden as int[]
- }
-
- @TaskAction
- public void exec() {
- if (getOldApiFile() == null) {
- // Nothing to do.
- return
- }
-
- final def apiFiles = collectAndVerifyInputs()
-
- OutputStream errStream = new ByteArrayOutputStream()
-
- // If either of those gets tweaked, then this should be refactored to extend JavaExec.
- project.javaexec {
- // Put Doclava on the classpath so we can get the ApiCheck class.
- classpath(getDoclavaClasspath())
- main = 'com.google.doclava.apicheck.ApiCheck'
-
- minHeapSize = '128m'
- maxHeapSize = '1024m'
-
- // add -error LEVEL for every error level we want to fail the build on.
- getCheckApiErrors().each { args('-error', it) }
- getCheckApiWarnings().each { args('-warning', it) }
- getCheckApiHidden().each { args('-hide', it) }
-
- Collection ignoredPackages = getIgnoredPackages()
- if (ignoredPackages) {
- ignoredPackages.each { args('-ignorePackage', it) }
- }
- Collection ignoredClasses = getIgnoredClasses()
- if (ignoredClasses) {
- ignoredClasses.each { args('-ignoreClass', it) }
- }
-
- args(apiFiles.collect( { it.absolutePath } ))
-
- // Redirect error output so that we can whitelist specific errors.
- errorOutput = errStream
-
- // We will be handling failures ourselves with a custom message.
- ignoreExitValue = true
- }
-
- // Load the whitelist file, if present.
- if (whitelistErrorsFile && whitelistErrorsFile.exists()) {
- whitelistErrors += whitelistErrorsFile.readLines()
- }
-
- // Parse the error output.
- Set unparsedErrors = []
- Set detectedErrors = []
- Set parsedErrors = []
- errStream.toString().split("\n").each {
- if (it) {
- def matcher = it =~ ~/^(.+):(.+): (\w+) (\d+): (.+)$/
- if (!matcher) {
- unparsedErrors += [it]
- } else if (matcher[0][3] == "error") {
- def hash = getShortHash(matcher[0][5]);
- def error = matcher[0][1..-1] + [hash]
- if (hash in whitelistErrors) {
- detectedErrors += [error]
- detectedWhitelistErrors += error[5]
- } else {
- parsedErrors += [error]
- }
- }
- }
- }
-
- unparsedErrors.each { error -> logger.error "$ANSI_RED$error$ANSI_RESET" }
- parsedErrors.each { logger.error "$ANSI_RED${it[5]}$ANSI_RESET ${it[4]}"}
- detectedErrors.each { logger.warn "$ANSI_YELLOW${it[5]}$ANSI_RESET ${it[4]}"}
-
- if (unparsedErrors || parsedErrors) {
- throw new GradleException(onFailMessage)
- }
-
- // Just create a dummy file upon completion. Without any outputs, Gradle will run this task
- // every time.
- File outputFile = getOutputFile()
- outputFile.parentFile.mkdirs()
- outputFile.createNewFile()
- }
-
- def getShortHash(src) {
- return MessageDigest.getInstance("SHA-1")
- .digest(src.toString().bytes)
- .encodeHex()
- .toString()[-7..-1]
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/android/support/doclava/DoclavaJavadocOptionFileOption.java b/buildSrc/src/main/groovy/android/support/doclava/DoclavaJavadocOptionFileOption.java
deleted file mode 100644
index db3f318..0000000
--- a/buildSrc/src/main/groovy/android/support/doclava/DoclavaJavadocOptionFileOption.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.doclava;
-
-import org.gradle.external.javadoc.internal.AbstractJavadocOptionFileOption;
-import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-
-/**
- * This class is used to hold complex argument(s) to doclava
- */
-public class DoclavaJavadocOptionFileOption extends
- AbstractJavadocOptionFileOption<Iterable<String>> {
-
- public DoclavaJavadocOptionFileOption(String option) {
- super(option, null);
- }
-
- public DoclavaJavadocOptionFileOption(String option, Iterable<String> value) {
- super(option, value);
- }
-
- @Override
- public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
- writerContext.writeOptionHeader(getOption());
-
- final Iterable<String> args = getValue();
- if (args != null) {
- final Iterator<String> iter = args.iterator();
- while (true) {
- writerContext.writeValue(iter.next());
- if (!iter.hasNext()) {
- break;
- }
- writerContext.write(" ");
- }
- }
-
- writerContext.newLine();
- }
-
- /**
- * @return a deep copy of the option
- */
- public DoclavaJavadocOptionFileOption duplicate() {
- final Iterable<String> value = getValue();
- final ArrayList<String> valueCopy;
- if (value != null) {
- valueCopy = new ArrayList<>();
- for (String item : value) {
- valueCopy.add(item);
- }
- } else {
- valueCopy = null;
- }
- return new DoclavaJavadocOptionFileOption(getOption(), valueCopy);
- }
-}
diff --git a/buildSrc/src/main/groovy/android/support/doclava/DoclavaTask.groovy b/buildSrc/src/main/groovy/android/support/doclava/DoclavaTask.groovy
deleted file mode 100644
index 651446f..0000000
--- a/buildSrc/src/main/groovy/android/support/doclava/DoclavaTask.groovy
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2016 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.doclava
-
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.javadoc.Javadoc
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.OutputFile
-import org.gradle.external.javadoc.JavadocOptionFileOption
-
-public class DoclavaTask extends Javadoc {
-
- // external/doclava/src/com/google/doclava/Errors.java
- public static final def DEFAULT_DOCLAVA_ERRORS = Collections.unmodifiableSet([
- 101, // unresolved link
- 103, // unknown tag
- 104, // unknown param name
- ] as Set)
-
- public static final def DEFAULT_DOCLAVA_WARNINGS = Collections.unmodifiableSet([
- 121, // hidden type param
- ] as Set)
-
-
- public static final def DEFAULT_DOCLAVA_HIDDEN = Collections.unmodifiableSet([
- 111, // hidden super class
- 113, // @deprecation mismatch
- ] as Set)
-
-
- // All lowercase name to match MinimalJavadocOptions#docletpath
- private Collection<File> mDocletpath
-
- // doclava error types which will cause the build to fail
- @Input
- Collection doclavaErrors = DEFAULT_DOCLAVA_ERRORS
- @Input
- Collection doclavaWarnings = DEFAULT_DOCLAVA_WARNINGS
- // spammy doclava warnings which we want to hide
- @Input
- Collection doclavaHidden = DEFAULT_DOCLAVA_HIDDEN
-
- /**
- * If non-null, the list of packages that will be treated as if they were
- * marked with {@literal @hide}.<br>
- * Packages names will be matched exactly; sub-packages are not automatically recognized.
- */
- @Optional
- @Input
- Collection hiddenPackages
-
- /**
- * If non-null and not-empty, the whitelist of packages that will be present in the generated
- * stubs; if null or empty, then all packages have stubs generated.<br>
- * Wildcards are accepted.
- */
- @Optional
- @Input
- Set<String> stubPackages
-
- @Input
- boolean generateDocs = true
-
- /**
- * If non-null, the location of where to place the generated api file.
- * If this is non-null, then {@link #removedApiFile} must be non-null as well.
- */
- @Optional
- @OutputFile
- File apiFile
-
- /**
- * If non-null, the location of where to place the generated removed api file.
- * If this is non-null, then {@link #apiFile} must be non-null as well.
- */
- @Optional
- @OutputFile
- File removedApiFile
-
- /**
- * If non-null, the location of the generated keep list.
- */
- @Optional
- @OutputFile
- File keepListFile
-
- /**
- * If non-null, the location to put the generated stub sources.
- */
- @Optional
- @OutputDirectory
- File stubsDir
-
- public DoclavaTask() {
- failOnError = true
- options.doclet = "com.google.doclava.Doclava"
- options.encoding("UTF-8")
- options.quiet()
- // doclava doesn't understand '-doctitle'
- title = null
- maxMemory = "1280m"
- // If none of generateDocs, apiFile, keepListFile, or stubJarsDir are true, then there is
- // no work to do.
- onlyIf( { getGenerateDocs() ||
- getApiFile() != null ||
- getKeepListFile() != null ||
- getStubsDir() != null } )
- }
-
- /**
- * The doclet path which has the {@code com.gogole.doclava.Doclava} class.
- * This option will override any doclet path set in this instance's {@link #options JavadocOptions}.
- * @see MinimalJavadocOptions#getDocletpath()
- */
- @InputFiles
- public Collection<File> getDocletpath() {
- return mDocletpath
- }
-
- /**
- * Sets the doclet path which has the {@code com.gogole.doclava.Doclava} class.
- * This option will override any doclet path set in this instance's {@link #options JavadocOptions}.
- * @see MinimalJavadocOptions#setDocletpath(java.util.List)
- */
- public void setDocletpath(Collection<File> docletpath) {
- mDocletpath = docletpath
- // Go ahead and keep the docletpath in our JavadocOptions object in sync.
- options.docletpath = docletpath as List
- }
-
- public void setDoclavaErrors(Collection errors) {
- // Make it serializable.
- doclavaErrors = errors as int[]
- }
-
- public void setDoclavaWarnings(Collection warnings) {
- // Make it serializable.
- doclavaWarnings = warnings as int[]
- }
-
- public void setDoclavaHidden(Collection hidden) {
- // Make it serializable.
- doclavaHidden = hidden as int[]
- }
-
- /**
- * "Configures" this DoclavaTask with parameters that might not be at their final values
- * until this task is run.
- */
- private configureDoclava() {
- options.docletpath = getDocletpath() as List
-
- // configure doclava error/warning/hide levels
- JavadocOptionFileOption hide = options.addMultilineMultiValueOption("hide")
- hide.setValue(getDoclavaHidden().collect({ [it.toString()] }))
-
- JavadocOptionFileOption warning = options.addMultilineMultiValueOption("warning")
- warning.setValue(getDoclavaWarnings().collect({ [it.toString()] }))
-
- JavadocOptionFileOption error = options.addMultilineMultiValueOption("error")
- error.setValue(getDoclavaErrors().collect({ [it.toString()] }))
-
- Collection hiddenPackages = getHiddenPackages()
- if (hiddenPackages) {
- JavadocOptionFileOption hidePackage =
- options.addMultilineMultiValueOption("hidePackage")
- hidePackage.setValue(hiddenPackages.collect({ [it.toString()] }))
- }
-
- if (!getGenerateDocs()) {
- options.addOption(new DoclavaJavadocOptionFileOption('nodocs'))
- }
-
- // If requested, generate the API files.
- File apiFile = getApiFile()
- if (apiFile != null) {
- options.addStringOption('api', apiFile.absolutePath)
-
- File removedApiFile = getRemovedApiFile()
- if (removedApiFile != null) {
- options.addStringOption('removedApi', removedApiFile.absolutePath)
- }
- }
-
- // If requested, generate the keep list.
- File keepListFile = getKeepListFile()
- if (keepListFile != null) {
- options.addStringOption('proguard', keepListFile.absolutePath)
- }
- // If requested, generate stubs.
- File stubsDir = getStubsDir()
- if (stubsDir != null) {
- options.addStringOption('stubs', stubsDir.absolutePath)
- Set<String> stubPackages = getStubPackages()
- if (stubPackages) {
- options.addStringOption('stubpackages', stubPackages.join(':'))
- }
- }
- // Always treat this as an Android docs task.
- options.addOption(new DoclavaJavadocOptionFileOption('android'))
- }
-
- @Override
- public void generate() {
- configureDoclava()
- super.generate()
- }
-}
diff --git a/buildSrc/src/main/java/android/support/LibraryGroups.java b/buildSrc/src/main/java/android/support/LibraryGroups.java
new file mode 100644
index 0000000..feaefbc
--- /dev/null
+++ b/buildSrc/src/main/java/android/support/LibraryGroups.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support;
+
+/**
+ * The list of maven group names of all the libraries in this project.
+ */
+public class LibraryGroups {
+ public static final String SUPPORT = "com.android.support";
+ public static final String ROOM = "android.arch.persistence.room";
+ public static final String PERSISTENCE = "android.arch.persistence";
+ public static final String LIFECYCLE = "android.arch.lifecycle";
+ public static final String ARCH_CORE = "android.arch.core";
+ public static final String PAGING = "android.arch.paging";
+ public static final String NAVIGATION = "android.arch.navigation";
+}
diff --git a/buildSrc/src/main/java/android/support/LibraryVersions.java b/buildSrc/src/main/java/android/support/LibraryVersions.java
index 64f1840..3f160df 100644
--- a/buildSrc/src/main/java/android/support/LibraryVersions.java
+++ b/buildSrc/src/main/java/android/support/LibraryVersions.java
@@ -23,7 +23,7 @@
/**
* Version code of the support library components.
*/
- public static final Version SUPPORT_LIBRARY = new Version("27.0.1");
+ public static final Version SUPPORT_LIBRARY = new Version("27.0.2");
/**
* Version code for flatfoot 1.0 projects (room, lifecycles)
diff --git a/buildSrc/src/main/kotlin/android/support/GroovyInteroperability.kt b/buildSrc/src/main/kotlin/android/support/GroovyInteroperability.kt
new file mode 100644
index 0000000..ec2d2f6
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/GroovyInteroperability.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * 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
+
+import groovy.lang.Closure
+import groovy.lang.GroovyObject
+import groovy.lang.MetaClass
+
+import org.codehaus.groovy.runtime.InvokerHelper.getMetaClass
+
+operator fun <T> Closure<T>.invoke(): T = call()
+
+operator fun <T> Closure<T>.invoke(x: Any?): T = call(x)
+
+operator fun <T> Closure<T>.invoke(vararg xs: Any?): T = call(*xs)
+
+
+/**
+ * Executes the given [builder] against this object's [GroovyBuilderScope].
+ *
+ * @see [GroovyBuilderScope]
+ */
+inline
+fun <T> Any.withGroovyBuilder(builder: GroovyBuilderScope.() -> T): T =
+ GroovyBuilderScope.of(this).builder()
+
+
+/**
+ * Provides a dynamic dispatching DSL with Groovy semantics for better integration with
+ * plugins that rely on Groovy builders such as the core `maven` plugin.
+ *
+ * It supports Groovy keyword arguments and arbitrary nesting, for instance, the following Groovy code:
+ *
+ * ```Groovy
+ * repository(url: "scp://repos.mycompany.com/releases") {
+ * authentication(userName: "me", password: "myPassword")
+ * }
+ * ```
+ *
+ * Can be mechanically translated to the following Kotlin with the aid of `withGroovyBuilder`:
+ *
+ * ```Kotlin
+ * withGroovyBuilder {
+ * "repository"("url" to "scp://repos.mycompany.com/releases") {
+ * "authentication"("userName" to "me", "password" to "myPassword")
+ * }
+ * }
+ * ```
+ *
+ * @see [withGroovyBuilder]
+ */
+interface GroovyBuilderScope : GroovyObject {
+
+ companion object {
+
+ fun of(value: Any): GroovyBuilderScope =
+ when (value) {
+ is GroovyObject -> GroovyBuilderScopeForGroovyObject(value)
+ else -> GroovyBuilderScopeForRegularObject(value)
+ }
+ }
+
+ val delegate: Any
+
+ operator fun String.invoke(vararg arguments: Any?): Any?
+
+ operator fun String.invoke(): Any? =
+ invoke(*emptyArray<Any>())
+
+ operator fun <T> String.invoke(vararg arguments: Any?, builder: GroovyBuilderScope.() -> T): Any? =
+ invoke(*arguments, closureFor(builder))
+
+ operator fun <T> String.invoke(builder: GroovyBuilderScope.() -> T): Any? =
+ invoke(closureFor(builder))
+
+ operator fun <T> String.invoke(vararg keywordArguments: Pair<String, Any?>, builder: GroovyBuilderScope.() -> T): Any? =
+ invoke(keywordArguments.toMap(), closureFor(builder))
+
+ operator fun String.invoke(vararg keywordArguments: Pair<String, Any?>): Any? =
+ invoke(keywordArguments.toMap())
+
+ private
+ fun <T> closureFor(builder: GroovyBuilderScope.() -> T): Closure<Any?> =
+ object : Closure<Any?>(this, this) {
+ @Suppress("unused")
+ fun doCall() = delegate.withGroovyBuilder(builder)
+ }
+}
+
+
+private
+class GroovyBuilderScopeForGroovyObject(override val delegate: GroovyObject) : GroovyBuilderScope, GroovyObject by delegate {
+
+ override fun String.invoke(vararg arguments: Any?): Any? =
+ delegate.invokeMethod(this, arguments)
+}
+
+
+private
+class GroovyBuilderScopeForRegularObject(override val delegate: Any) : GroovyBuilderScope {
+
+ private
+ val groovyMetaClass: MetaClass by lazy {
+ getMetaClass(delegate)
+ }
+
+ override fun invokeMethod(name: String, args: Any?): Any? =
+ groovyMetaClass.invokeMethod(delegate, name, args)
+
+ override fun setProperty(propertyName: String, newValue: Any?) =
+ groovyMetaClass.setProperty(delegate, propertyName, newValue)
+
+ override fun getProperty(propertyName: String): Any =
+ groovyMetaClass.getProperty(delegate, propertyName)
+
+ override fun setMetaClass(metaClass: MetaClass?) =
+ throw IllegalStateException()
+
+ override fun getMetaClass(): MetaClass =
+ groovyMetaClass
+
+ override fun String.invoke(vararg arguments: Any?): Any? =
+ groovyMetaClass.invokeMethod(delegate, this, arguments)
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/KotlinNoOp.kt b/buildSrc/src/main/kotlin/android/support/KotlinNoOp.kt
new file mode 100644
index 0000000..968f6ca
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/KotlinNoOp.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support
+
+class KotlinNoOp {
+
+ fun noOp() {
+ }
+
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt b/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt
new file mode 100644
index 0000000..1698bf9
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support
+
+import com.android.build.gradle.LibraryPlugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.artifacts.maven.MavenDeployer
+import org.gradle.api.tasks.Upload
+
+fun apply(project: Project, extension: SupportLibraryExtension) {
+ project.afterEvaluate {
+ if (extension.publish) {
+ if (extension.mavenGroup == null) {
+ throw Exception("You must specify mavenGroup for " + project.name + " project");
+ }
+ if (extension.mavenVersion == null) {
+ throw Exception("You must specify mavenVersion for " + project.name + " project");
+ }
+ project.group = extension.mavenGroup!!
+ project.version = extension.mavenVersion.toString()
+ }
+ }
+
+ project.apply(mapOf("plugin" to "maven"))
+
+ // Set uploadArchives options.
+ val uploadTask = project.tasks.getByName("uploadArchives") as Upload
+ project.afterEvaluate {
+ if (extension.publish) {
+ uploadTask.repositories.withType(MavenDeployer::class.java) { mavenDeployer ->
+
+ mavenDeployer.getPom().project {
+ it.withGroovyBuilder {
+ "name"(extension.name)
+ "description"(extension.description)
+ "url"(extension.url)
+ "inceptionYear"(extension.inceptionYear)
+
+ "licenses" {
+ "license" {
+ "name"("The Apache Software License, Version 2.0")
+ "url"("http://www.apache.org/licenses/LICENSE-2.0.txt")
+ "distribution"("repo")
+ }
+ for (license in extension.getLicenses()) {
+ "license" {
+ "name"(license.name)
+ "url"(license.url)
+ "distribution"("repo")
+ }
+ }
+ }
+
+ "scm" {
+ "url"("http://source.android.com")
+ "connection"("scm:git:https://android.googlesource.com/platform/frameworks/support")
+ }
+
+ "developers" {
+ "developer" {
+ "name"("The Android Open Source Project")
+ }
+ }
+ }
+ }
+
+ // TODO(aurimas): remove this when Gradle bug is fixed.
+ // https://github.com/gradle/gradle/issues/3170
+ uploadTask.doFirst {
+ val allDeps = HashSet<ProjectDependency>()
+ collectDependenciesForConfiguration(allDeps, project, "api");
+ collectDependenciesForConfiguration(allDeps, project, "implementation");
+ collectDependenciesForConfiguration(allDeps, project, "compile");
+
+ mavenDeployer.getPom().whenConfigured {
+ it.dependencies.forEach { dep ->
+ if (dep == null) {
+ return@forEach
+ }
+
+ val getGroupIdMethod =
+ dep::class.java.getDeclaredMethod("getGroupId")
+ val groupId : String = getGroupIdMethod.invoke(dep) as String
+ val getArtifactIdMethod =
+ dep::class.java.getDeclaredMethod("getArtifactId")
+ val artifactId : String = getArtifactIdMethod.invoke(dep) as String
+
+ if (isAndroidProject(groupId, artifactId, allDeps)) {
+ val setTypeMethod = dep::class.java.getDeclaredMethod("setType",
+ java.lang.String::class.java)
+ setTypeMethod.invoke(dep, "aar")
+ }
+ }
+ }
+ }
+ }
+ } else {
+ uploadTask.enabled = false
+ }
+ }
+}
+
+private fun collectDependenciesForConfiguration(projectDependencies : MutableSet<ProjectDependency>,
+ project : Project, name : String) {
+ val config = project.configurations.findByName(name)
+ if (config != null) {
+ config.dependencies.withType(ProjectDependency::class.java).forEach {
+ dep -> projectDependencies.add(dep)
+ }
+ }
+}
+
+private fun isAndroidProject(groupId : String, artifactId : String,
+ deps : Set<ProjectDependency>) : Boolean {
+ for (dep in deps) {
+ if (dep.group == groupId && dep.name == artifactId) {
+ return dep.getDependencyProject().plugins.hasPlugin(LibraryPlugin::class.java)
+ }
+ }
+ return false
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
new file mode 100644
index 0000000..b347415
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support
+
+import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.internal.dsl.LintOptions
+import net.ltgt.gradle.errorprone.ErrorProneBasePlugin
+import net.ltgt.gradle.errorprone.ErrorProneToolChain
+import org.gradle.api.JavaVersion
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import java.io.File
+import java.util.*
+
+/**
+ * Support library specific com.android.library plugin that sets common configurations needed for
+ * support library modules.
+ */
+class SupportAndroidLibraryPlugin : Plugin<Project> {
+
+ override fun apply(project: Project) {
+ val supportLibraryExtension = project.extensions.create("supportLibrary",
+ SupportLibraryExtension::class.java, project)
+ apply(project, supportLibraryExtension)
+
+ project.afterEvaluate {
+ val library = project.extensions.findByType(LibraryExtension::class.java)
+ ?: return@afterEvaluate
+
+ if (supportLibraryExtension.legacySourceLocation) {
+ // We use a non-standard manifest path.
+ library.sourceSets.getByName("main").manifest.srcFile("AndroidManifest.xml")
+
+ // We use a non-standard test directory structure.
+ val androidTest = library.sourceSets.getByName("androidTest")
+ androidTest.setRoot("tests")
+ androidTest.java.srcDir("tests/src")
+ androidTest.res.srcDir("tests/res")
+ androidTest.manifest.srcFile("tests/AndroidManifest.xml")
+ }
+
+ // Java 8 is only fully supported on API 24+ and not all Java 8 features are binary
+ // compatible with API < 24, so use Java 7 for both source AND target.
+ val javaVersion: JavaVersion;
+ if (supportLibraryExtension.java8Library) {
+ if (library.defaultConfig.minSdkVersion.apiLevel < 24) {
+ throw IllegalArgumentException("Libraries can only support Java 8 if "
+ + "minSdkVersion is 24 or higher");
+ }
+ javaVersion = JavaVersion.VERSION_1_8
+ } else {
+ javaVersion = JavaVersion.VERSION_1_7
+ }
+
+ library.compileOptions.setSourceCompatibility(javaVersion)
+ library.compileOptions.setTargetCompatibility(javaVersion)
+ }
+
+ VersionFileWriterTask.setUpAndroidLibrary(project)
+
+ project.apply(mapOf("plugin" to "com.android.library"))
+ project.apply(mapOf("plugin" to ErrorProneBasePlugin::class.java))
+
+ val library = project.extensions.findByType(LibraryExtension::class.java)
+ ?: throw Exception("Failed to find Android extension")
+
+ val currentSdk = project.property("currentSdk")
+ when (currentSdk) {
+ is Int -> library.compileSdkVersion(currentSdk)
+ is String -> library.compileSdkVersion(currentSdk)
+ }
+
+ // Update the version meta-data in each Manifest.
+ library.defaultConfig.addManifestPlaceholders(mapOf("target-sdk-version" to currentSdk))
+
+ // Set test runner.
+ library.defaultConfig.testInstrumentationRunner = INSTRUMENTATION_RUNNER
+
+ library.testOptions.unitTests.isReturnDefaultValues = true
+
+ // Use a local debug keystore to avoid build server issues.
+ val key = ((project.rootProject.property("init") as Properties)
+ .getValue("debugKeystore")) as File
+ library.signingConfigs.findByName("debug")?.storeFile = key
+
+ setUpLint(library.lintOptions, File(project.projectDir, "/lint-baseline.xml"))
+
+ if (project.rootProject.property("usingFullSdk") as Boolean) {
+ // Library projects don't run lint by default, so set up dependency.
+ project.tasks.getByName("uploadArchives").dependsOn("lintRelease")
+ }
+
+ SourceJarTaskHelper.setUpAndroidProject(project, library)
+
+ val toolChain = ErrorProneToolChain.create(project)
+ library.buildTypes.create("errorProne")
+ library.libraryVariants.all { libraryVariant ->
+ if (libraryVariant.getBuildType().getName().equals("errorProne")) {
+ @Suppress("DEPRECATION")
+ libraryVariant.getJavaCompile().setToolChain(toolChain);
+
+ @Suppress("DEPRECATION")
+ val compilerArgs = libraryVariant.getJavaCompile().options.compilerArgs
+ compilerArgs += arrayListOf(
+ "-XDcompilePolicy=simple", // Workaround for b/36098770
+
+ // Enforce the following checks.
+ "-Xep:MissingOverride:ERROR",
+ "-Xep:NarrowingCompoundAssignment:ERROR",
+ "-Xep:ClassNewInstance:ERROR",
+ "-Xep:ClassCanBeStatic:ERROR",
+ "-Xep:SynchronizeOnNonFinalField:ERROR",
+ "-Xep:OperatorPrecedence:ERROR"
+ )
+ }
+ }
+ }
+
+ companion object {
+ private val INSTRUMENTATION_RUNNER = "android.support.test.runner.AndroidJUnitRunner"
+ }
+}
+
+private fun setUpLint(lintOptions: LintOptions, baseline: File) {
+ // Always lint check NewApi as fatal.
+ lintOptions.isAbortOnError = true
+ lintOptions.isIgnoreWarnings = true
+
+ // Write output directly to the console (and nowhere else).
+ lintOptions.textOutput("stderr")
+ lintOptions.textReport = true
+ lintOptions.htmlReport = false
+
+ // Format output for convenience.
+ lintOptions.isExplainIssues = true
+ lintOptions.isNoLines = false
+ lintOptions.isQuiet = true
+
+ lintOptions.error("NewApi")
+
+ // Set baseline file for all legacy lint warnings.
+ if (System.getenv("GRADLE_PLUGIN_VERSION") != null) {
+ lintOptions.check("NewApi")
+ } else {
+ lintOptions.baseline(baseline)
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/SupportJavaLibraryPlugin.kt b/buildSrc/src/main/kotlin/android/support/SupportJavaLibraryPlugin.kt
new file mode 100644
index 0000000..8e0714bf
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/SupportJavaLibraryPlugin.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginConvention
+
+/**
+ * Support java library specific plugin that sets common configurations needed for
+ * support library modules.
+ */
+class SupportJavaLibraryPlugin : Plugin<Project> {
+
+ override fun apply(project: Project) {
+ val supportLibraryExtension = project.extensions.create("supportLibrary",
+ SupportLibraryExtension::class.java, project)
+ apply(project, supportLibraryExtension)
+
+ project.apply(mapOf("plugin" to "java"))
+ project.afterEvaluate {
+ val convention = project.convention.getPlugin(JavaPluginConvention::class.java)
+ if (supportLibraryExtension.java8Library) {
+ convention.sourceCompatibility = JavaVersion.VERSION_1_8
+ convention.targetCompatibility = JavaVersion.VERSION_1_8
+ } else {
+ convention.sourceCompatibility = JavaVersion.VERSION_1_7
+ convention.targetCompatibility = JavaVersion.VERSION_1_7
+ }
+ }
+
+ SourceJarTaskHelper.setUpJavaProject(project)
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/SupportLibraryExtension.kt b/buildSrc/src/main/kotlin/android/support/SupportLibraryExtension.kt
new file mode 100644
index 0000000..088e169
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/SupportLibraryExtension.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support
+
+import groovy.lang.Closure
+import org.gradle.api.Project
+import java.util.ArrayList
+
+/**
+ * Extension for [SupportAndroidLibraryPlugin] and [SupportJavaLibraryPlugin].
+ */
+open class SupportLibraryExtension(val project: Project) {
+ var name: String? = null
+ var mavenVersion: Version? = null
+ var mavenGroup: String? = null
+ var description: String? = null
+ var inceptionYear: String? = null
+ var url = SUPPORT_URL
+ private var licenses: MutableCollection<License> = ArrayList()
+ var java8Library = false
+ var legacySourceLocation = false
+ var publish = false
+
+ fun license(closure: Closure<*>): License {
+ val license = project.configure(License(), closure) as License
+ licenses.add(license)
+ return license
+ }
+
+ fun getLicenses(): Collection<License> {
+ return licenses
+ }
+
+ companion object {
+ @JvmField
+ val ARCHITECTURE_URL = "https://developer.android.com/topic/libraries/architecture/index.html"
+ @JvmField
+ val SUPPORT_URL = "http://developer.android.com/tools/extras/support-library.html"
+ }
+}
+
+class License {
+ var name: String? = null
+ var url: String? = null
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt b/buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt
new file mode 100644
index 0000000..8f96b2e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.checkapi
+
+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.InputFiles
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.*
+import java.security.MessageDigest
+
+
+/** Character that resets console output color. */
+private const val ANSI_RESET = "\u001B[0m"
+
+/** Character that sets console output color to red. */
+private const val ANSI_RED = "\u001B[31m"
+
+/** Character that sets console output color to yellow. */
+private const val ANSI_YELLOW = "\u001B[33m"
+
+private val ERROR_REGEX = Regex("^(.+):(.+): (\\w+) (\\d+): (.+)$")
+
+private fun ByteArray.encodeHex() = fold(StringBuilder(), { builder, byte ->
+ val hexString = Integer.toHexString(byte.toInt() and 0xFF)
+ if (hexString.length < 2) {
+ builder.append("0")
+ }
+ builder.append(hexString)
+}).toString()
+
+private fun getShortHash(src: String): String {
+ val str = MessageDigest.getInstance("SHA-1")
+ .digest(src.toByteArray()).encodeHex()
+ val len = str.length
+ return str.substring(len - 7, len)
+}
+
+/**
+ * Task used to verify changes between two API files.
+ * <p>
+ * This task may be configured to ignore, warn, or fail with a message for a specific set of
+ * Doclava-defined error codes. See {@link com.google.doclava.Errors} for a complete list of
+ * supported error codes.
+ * <p>
+ * Specific failures may be ignored by specifying a list of SHAs in {@link #whitelistErrors}. Each
+ * SHA is unique to a specific API change and is logged to the error output on failure.
+ */
+open class CheckApiTask : DefaultTask() {
+
+ /** API file that represents the existing API surface. */
+ @Optional
+ @InputFile
+ var oldApiFile: File? = null
+
+ /** API file that represents the existing API surface's removals. */
+ @Optional
+ @InputFile
+ var oldRemovedApiFile: File? = null
+
+ /** API file that represents the candidate API surface. */
+ @InputFile
+ var newApiFile: File? = null
+
+ /** API file that represents the candidate API surface's removals. */
+ @Optional
+ @InputFile
+ var newRemovedApiFile: File? = null
+
+ /** Optional file containing a newline-delimited list of error SHAs to ignore. */
+ var whitelistErrorsFile: File? = null
+
+ @Optional
+ @InputFile
+ fun getWhiteListErrorsFileInput(): File? {
+ // Gradle requires non-null InputFiles to exist -- even with Optional -- so work around that
+ // by returning null for this field if the file doesn't exist.
+ if (whitelistErrorsFile?.exists() == true) {
+ return whitelistErrorsFile
+ }
+ return null
+ }
+
+ /**
+ * Optional set of error SHAs to ignore.
+ * <p>
+ * Each error SHA is unique to a specific API change.
+ */
+ @Optional
+ @Input
+ var whitelistErrors = emptySet<String>()
+
+ var detectedWhitelistErrors = mutableSetOf<String>()
+
+ @InputFiles
+ var doclavaClasspath: Collection<File> = emptyList()
+
+ // A dummy output file meant only to tag when this check was last ran.
+ // Without any outputs, Gradle will run this task every time.
+ @Optional
+ private var mOutputFile: File? = null
+
+ @OutputFile
+ fun getOutputFile(): File {
+ return if (mOutputFile != null)
+ mOutputFile!!
+ else File(project.buildDir, "checkApi/${name}-completed")
+ }
+
+ @Optional
+ fun setOutputFile(outputFile: File) {
+ mOutputFile = outputFile
+ }
+
+ /**
+ * List of Doclava error codes to treat as errors.
+ * <p>
+ * See {@link com.google.doclava.Errors} for a complete list of error codes.
+ */
+ @Input
+ var checkApiErrors: Collection<Int> = emptyList()
+
+ /**
+ * List of Doclava error codes to treat as warnings.
+ * <p>
+ * See {@link com.google.doclava.Errors} for a complete list of error codes.
+ */
+ @Input
+ var checkApiWarnings: Collection<Int> = emptyList()
+
+ /**
+ * List of Doclava error codes to ignore.
+ * <p>
+ * See {@link com.google.doclava.Errors} for a complete list of error codes.
+ */
+ @Input
+ var checkApiHidden: Collection<Int> = emptyList()
+
+ /** Message to display on API check failure. */
+ @Input
+ var onFailMessage: String = "Failed."
+
+ init {
+ group = "Verification"
+ description = "Invoke Doclava\'s ApiCheck tool to make sure current.txt is up to date."
+ }
+
+ private fun collectAndVerifyInputs(): Set<File> {
+ if (oldRemovedApiFile != null && newRemovedApiFile != null) {
+ return setOf(oldApiFile!!, newApiFile!!, oldRemovedApiFile!!, newRemovedApiFile!!)
+ } else {
+ return setOf(oldApiFile!!, newApiFile!!)
+ }
+ }
+
+ @TaskAction
+ fun exec() {
+ if (oldApiFile == null) {
+ // Nothing to do.
+ return
+ }
+
+ val apiFiles = collectAndVerifyInputs()
+
+ val errStream = ByteArrayOutputStream()
+
+ // If either of those gets tweaked, then this should be refactored to extend JavaExec.
+ project.javaexec { spec ->
+ spec.apply {
+ // Put Doclava on the classpath so we can get the ApiCheck class.
+ classpath(doclavaClasspath)
+ main = "com.google.doclava.apicheck.ApiCheck"
+
+ minHeapSize = "128m"
+ maxHeapSize = "1024m"
+
+ // add -error LEVEL for every error level we want to fail the build on.
+ checkApiErrors.forEach { args("-error", it) }
+ checkApiWarnings.forEach { args("-warning", it) }
+ checkApiHidden.forEach { args("-hide", it) }
+
+ spec.args(apiFiles.map { it.absolutePath })
+
+ // Redirect error output so that we can whitelist specific errors.
+ errorOutput = errStream
+ // We will be handling failures ourselves with a custom message.
+ setIgnoreExitValue(true)
+ }
+ }
+
+
+ // Load the whitelist file, if present.
+ val whitelistFile = whitelistErrorsFile
+ if (whitelistFile?.exists() == true) {
+ whitelistErrors += whitelistFile.readLines()
+ }
+
+ // Parse the error output.
+ val unparsedErrors = mutableSetOf<String>()
+ val detectedErrors = mutableSetOf<List<String>>()
+ val parsedErrors = mutableSetOf<List<String>>()
+ ByteArrayInputStream(errStream.toByteArray()).bufferedReader().lines().forEach {
+ val match = ERROR_REGEX.matchEntire(it)
+
+ if (match == null) {
+ unparsedErrors.add(it)
+ } else if (match.groups[3]?.value == "error") {
+ val hash = getShortHash(match.groups[5]?.value!!)
+ val error = match.groupValues.subList(1, match.groupValues.size) + listOf(hash)
+ if (hash in whitelistErrors) {
+ detectedErrors.add(error)
+ detectedWhitelistErrors.add(error[5])
+ } else {
+ parsedErrors.add(error)
+ }
+ }
+ }
+
+ unparsedErrors.forEach { error -> logger.error("$ANSI_RED$error$ANSI_RESET") }
+ parsedErrors.forEach { logger.error("$ANSI_RED${it[5]}$ANSI_RESET ${it[4]}") }
+ detectedErrors.forEach { logger.warn("$ANSI_YELLOW${it[5]}$ANSI_RESET ${it[4]}") }
+
+ if (unparsedErrors.isNotEmpty() || parsedErrors.isNotEmpty()) {
+ throw GradleException(onFailMessage)
+ }
+
+ // Just create a dummy file upon completion. Without any outputs, Gradle will run this task
+ // every time.
+ val outputFile = getOutputFile()
+ outputFile.parentFile.mkdirs()
+ outputFile.createNewFile()
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/doclava/DoclavaJavadocOptionFileOption.kt b/buildSrc/src/main/kotlin/android/support/doclava/DoclavaJavadocOptionFileOption.kt
new file mode 100644
index 0000000..828fb25
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/doclava/DoclavaJavadocOptionFileOption.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.doclava
+
+import org.gradle.external.javadoc.internal.AbstractJavadocOptionFileOption
+import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext
+import java.io.IOException
+import java.util.ArrayList
+
+//TODO: remove this once https://github.com/gradle/gradle/issues/2354 is fixed
+class DoclavaJavadocOptionFileOption : AbstractJavadocOptionFileOption<Iterable<String>> {
+
+ constructor(option: String) : super(option, null)
+
+ constructor(option: String, value: Iterable<String>?) : super(option, value)
+
+ @Throws(IOException::class)
+ override fun write(writerContext: JavadocOptionFileWriterContext) {
+ writerContext.writeOptionHeader(getOption())
+ val args = getValue()
+ if (args != null) {
+ val iter = args.iterator()
+ while (true) {
+ writerContext.writeValue(iter.next())
+ if (!iter.hasNext()) {
+ break
+ }
+ writerContext.write(" ")
+ }
+ }
+ writerContext.newLine()
+ }
+ /**
+ * @return a deep copy of the option
+ */
+ override fun duplicate(): DoclavaJavadocOptionFileOption {
+ val value = getValue()
+ val valueCopy: ArrayList<String>?
+ if (value != null) {
+ valueCopy = ArrayList()
+ valueCopy += value
+ } else {
+ valueCopy = null
+ }
+ return DoclavaJavadocOptionFileOption(getOption(), valueCopy)
+ }
+}
diff --git a/buildSrc/src/main/kotlin/android/support/doclava/DoclavaTask.kt b/buildSrc/src/main/kotlin/android/support/doclava/DoclavaTask.kt
new file mode 100644
index 0000000..1007535
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/doclava/DoclavaTask.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.doclava
+
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.javadoc.Javadoc
+import org.gradle.external.javadoc.CoreJavadocOptions
+import java.io.File
+
+// external/doclava/src/com/google/doclava/Errors.java
+private val DEFAULT_DOCLAVA_ERRORS = setOf<Int>(
+ 101, // unresolved link
+ 103, // unknown tag
+ 104 // unknown param name
+)
+
+private val DEFAULT_DOCLAVA_WARNINGS = setOf<Int>(121 /* hidden type param */)
+
+private val DEFAULT_DOCLAVA_HIDDEN = setOf<Int>(
+ 111, // hidden super class
+ 113 // @deprecation mismatch
+)
+
+private fun <E> CoreJavadocOptions.addMultilineMultiValueOption(
+ name: String, values: Collection<E>) {
+ addMultilineMultiValueOption(name).setValue(values.map { listOf(it.toString()) })
+}
+
+open class DoclavaTask : Javadoc() {
+
+ // All lowercase name to match MinimalJavadocOptions#docletpath
+ private var docletpath: List<File> = emptyList()
+
+ // doclava error types which will cause the build to fail
+ @Input
+ var doclavaErrors = DEFAULT_DOCLAVA_ERRORS
+
+ @Input
+ var doclavaWarnings = DEFAULT_DOCLAVA_WARNINGS
+
+ // spammy doclava warnings which we want to hide
+ @Input
+ var doclavaHidden = DEFAULT_DOCLAVA_HIDDEN
+
+ /**
+ * If non-null, the list of packages that will be treated as if they were
+ * marked with {@literal @hide}.<br>
+ * Packages names will be matched exactly; sub-packages are not automatically recognized.
+ */
+ @Optional
+ @Input
+ var hiddenPackages: Collection<String>? = null
+
+ /**
+ * If non-null and not-empty, the whitelist of packages that will be present in the generated
+ * stubs; if null or empty, then all packages have stubs generated.<br>
+ * Wildcards are accepted.
+ */
+ @Optional
+ @Input
+ var stubPackages: Set<String>? = null
+
+ @Input
+ var generateDocs = true
+
+ /**
+ * If non-null, the location of where to place the generated api file.
+ * If this is non-null, then {@link #removedApiFile} must be non-null as well.
+ */
+ @Optional
+ @OutputFile
+ var apiFile: File? = null
+
+ /**
+ * If non-null, the location of where to place the generated removed api file.
+ * If this is non-null, then {@link #apiFile} must be non-null as well.
+ */
+ @Optional
+ @OutputFile
+ var removedApiFile: File? = null
+
+ /**
+ * If non-null, the location of the generated keep list.
+ */
+ @Optional
+ @OutputFile
+ var keepListFile: File? = null
+
+ /**
+ * If non-null, the location to put the generated stub sources.
+ */
+ @Optional
+ @OutputDirectory
+ var stubsDir: File? = null
+
+ init {
+ setFailOnError(true)
+ options.doclet = "com.google.doclava.Doclava"
+ options.encoding("UTF-8")
+ options.quiet()
+ // doclava doesn't understand '-doctitle'
+ title = null
+ maxMemory = "1280m"
+ // If none of generateDocs, apiFile, keepListFile, or stubJarsDir are true, then there is
+ // no work to do.
+ onlyIf({ generateDocs || apiFile != null || keepListFile != null || stubsDir != null })
+ }
+
+ /**
+ * The doclet path which has the {@code com.gogole.doclava.Doclava} class.
+ * This option will override any doclet path set in this instance's {@link #options JavadocOptions}.
+ * @see MinimalJavadocOptions#getDocletpath()
+ */
+ @InputFiles
+ fun getDocletpath(): List<File> {
+ return docletpath
+ }
+
+ /**
+ * Sets the doclet path which has the {@code com.gogole.doclava.Doclava} class.
+ * This option will override any doclet path set in this instance's {@link #options JavadocOptions}.
+ * @see MinimalJavadocOptions#setDocletpath(java.util.List)
+ */
+ fun setDocletpath(docletpath: Collection<File>) {
+ this.docletpath = docletpath.toList()
+ // Go ahead and keep the docletpath in our JavadocOptions object in sync.
+ options.docletpath = docletpath.toList()
+ }
+
+ fun setDoclavaErrors(errors: List<Int> ) {
+ // Make it serializable.
+ doclavaErrors = errors.toSet()
+ }
+
+ fun setDoclavaWarnings(warnings: List<Int>) {
+ // Make it serializable.
+ doclavaWarnings = warnings.toSet()
+ }
+
+ fun setDoclavaHidden(hidden: List<Int>) {
+ // Make it serializable.
+ doclavaHidden = hidden.toSet()
+ }
+
+ /**
+ * "Configures" this DoclavaTask with parameters that might not be at their final values
+ * until this task is run.
+ */
+ private fun configureDoclava() = (options as CoreJavadocOptions).apply {
+
+ docletpath = this@DoclavaTask.docletpath
+
+ // configure doclava error/warning/hide levels
+ addMultilineMultiValueOption("hide", doclavaHidden)
+ addMultilineMultiValueOption("warning", doclavaWarnings)
+ addMultilineMultiValueOption("error", doclavaErrors)
+
+ if (hiddenPackages != null) {
+ addMultilineMultiValueOption("hidePackage", hiddenPackages!!)
+ }
+
+ if (!generateDocs) {
+ addOption(DoclavaJavadocOptionFileOption("nodocs"))
+ }
+
+ // If requested, generate the API files.
+ if (apiFile != null) {
+ addFileOption("api", apiFile)
+ addFileOption("removedApi", removedApiFile)
+ }
+
+ // If requested, generate the keep list.
+ addFileOption("proguard", keepListFile)
+
+ // If requested, generate stubs.
+ if (stubsDir != null) {
+ addFileOption("stubs", stubsDir)
+ val stubs = stubPackages
+ if (stubs != null) {
+ addStringOption("stubpackages", stubs.joinToString(":"))
+ }
+ }
+ // Always treat this as an Android docs task.
+ addOption(DoclavaJavadocOptionFileOption("android"))
+ }
+
+ override fun generate() {
+ configureDoclava()
+ super.generate()
+ }
+}
+
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidLibraryPlugin.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidLibraryPlugin.properties
new file mode 100644
index 0000000..222602b
--- /dev/null
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportAndroidLibraryPlugin.properties
@@ -0,0 +1 @@
+implementation-class=android.support.SupportAndroidLibraryPlugin
\ 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
new file mode 100644
index 0000000..89755b7
--- /dev/null
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/SupportJavaLibraryPlugin.properties
@@ -0,0 +1 @@
+implementation-class=android.support.SupportJavaLibraryPlugin
\ No newline at end of file
diff --git a/compat/build.gradle b/compat/build.gradle
index 1e5d847..82d503c 100644
--- a/compat/build.gradle
+++ b/compat/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -30,8 +35,11 @@
}
supportLibrary {
- name 'Android Support Library compat'
- publish true
- inceptionYear '2015'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android Support Library compat"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index 1b55a2e..a608403 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -196,10 +196,9 @@
/**
* Call FontFamily#abortCreation()
*/
- private static boolean abortCreation(Object family) {
+ private static void abortCreation(Object family) {
try {
- Boolean result = (Boolean) sAbortCreation.invoke(family);
- return result.booleanValue();
+ sAbortCreation.invoke(family);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
diff --git a/compat/src/main/java/android/support/v4/view/NestedScrollingParent2.java b/compat/src/main/java/android/support/v4/view/NestedScrollingParent2.java
index db41c46..2ab463e 100644
--- a/compat/src/main/java/android/support/v4/view/NestedScrollingParent2.java
+++ b/compat/src/main/java/android/support/v4/view/NestedScrollingParent2.java
@@ -18,7 +18,6 @@
package android.support.v4.view;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat.NestedScrollType;
import android.support.v4.view.ViewCompat.ScrollAxis;
import android.view.MotionEvent;
@@ -144,7 +143,7 @@
* @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
* @param type the type of input which cause this scroll event
*/
- void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed,
+ void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
@NestedScrollType int type);
}
diff --git a/content/build.gradle b/content/build.gradle
index af33423..3639301 100644
--- a/content/build.gradle
+++ b/content/build.gradle
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -32,8 +37,11 @@
}
supportLibrary {
- name 'Android Support Content'
- publish true
- inceptionYear '2017'
- description 'Library providing support for paging across content exposed via a ContentProvider. Use of this library allows a client to avoid expensive interprocess "cursor window swaps" on the UI thread.'
+ name = "Android Support Content"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2017"
+ description = "Library providing support for paging across content exposed via a ContentProvider. Use of this library allows a client to avoid expensive interprocess \"cursor window swaps\" on the UI thread."
+ legacySourceLocation = true
}
diff --git a/core-ui/build.gradle b/core-ui/build.gradle
index 38a4fe2..cd70447 100644
--- a/core-ui/build.gradle
+++ b/core-ui/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -22,8 +27,11 @@
}
supportLibrary {
- name 'Android Support Library core UI'
- publish true
- inceptionYear '2011'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android Support Library core UI"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2011"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
diff --git a/core-utils/build.gradle b/core-utils/build.gradle
index 55fe2bb..b384a37 100644
--- a/core-utils/build.gradle
+++ b/core-utils/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -17,8 +22,11 @@
}
supportLibrary {
- name 'Android Support Library core utils'
- publish true
- inceptionYear '2011'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android Support Library core utils"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2011"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index fc90301..28b5f99 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-compat')
@@ -20,8 +25,11 @@
}
supportLibrary {
- name 'Android Support Custom Tabs'
- publish true
- inceptionYear '2015'
- description 'Android Support Custom Tabs'
+ name = "Android Support Custom Tabs"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "Android Support Custom Tabs"
+ legacySourceLocation = true
}
diff --git a/design/build.gradle b/design/build.gradle
index 4a82051..3af966d 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-v4')
@@ -59,8 +64,11 @@
}
supportLibrary {
- name 'Android Design Support Library'
- publish true
- inceptionYear '2015'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android Design Support Library"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
diff --git a/design/src/android/support/design/widget/BottomSheetBehavior.java b/design/src/android/support/design/widget/BottomSheetBehavior.java
index aaa9b80..00ce8f9 100644
--- a/design/src/android/support/design/widget/BottomSheetBehavior.java
+++ b/design/src/android/support/design/widget/BottomSheetBehavior.java
@@ -559,7 +559,7 @@
* Gets the current state of the bottom sheet.
*
* @return One of {@link #STATE_EXPANDED}, {@link #STATE_COLLAPSED}, {@link #STATE_DRAGGING},
- * and {@link #STATE_SETTLING}.
+ * {@link #STATE_SETTLING}, and {@link #STATE_HIDDEN}.
*/
@State
public final int getState() {
diff --git a/design/src/android/support/design/widget/TextInputEditText.java b/design/src/android/support/design/widget/TextInputEditText.java
index 7235ec2..ee6c32c 100644
--- a/design/src/android/support/design/widget/TextInputEditText.java
+++ b/design/src/android/support/design/widget/TextInputEditText.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.support.v7.widget.AppCompatEditText;
+import android.support.v7.widget.WithHint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewParent;
@@ -48,12 +49,12 @@
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
final InputConnection ic = super.onCreateInputConnection(outAttrs);
if (ic != null && outAttrs.hintText == null) {
- // If we don't have a hint and our parent is a TextInputLayout, use it's hint for the
+ // If we don't have a hint and our parent implements WithHint, use its hint for the
// EditorInfo. This allows us to display a hint in 'extract mode'.
ViewParent parent = getParent();
while (parent instanceof View) {
- if (parent instanceof TextInputLayout) {
- outAttrs.hintText = ((TextInputLayout) parent).getHint();
+ if (parent instanceof WithHint) {
+ outAttrs.hintText = ((WithHint) parent).getHint();
break;
}
parent = parent.getParent();
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index c9e8010..2ed79e4 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -53,6 +53,7 @@
import android.support.v7.widget.AppCompatDrawableManager;
import android.support.v7.widget.AppCompatTextView;
import android.support.v7.widget.TintTypedArray;
+import android.support.v7.widget.WithHint;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -113,7 +114,7 @@
* may not return the TextInputLayout itself, but rather an intermediate View. If you need
* to access a View directly, set an {@code android:id} and use {@link View#findViewById(int)}.
*/
-public class TextInputLayout extends LinearLayout {
+public class TextInputLayout extends LinearLayout implements WithHint {
private static final int ANIMATION_DURATION = 200;
private static final int INVALID_MAX_LENGTH = -1;
@@ -497,6 +498,7 @@
*
* @attr ref android.support.design.R.styleable#TextInputLayout_android_hint
*/
+ @Override
@Nullable
public CharSequence getHint() {
return mHintEnabled ? mHint : null;
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index 220758d..5069a73 100644
--- a/dynamic-animation/build.gradle
+++ b/dynamic-animation/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-core-utils')
@@ -16,8 +21,11 @@
}
supportLibrary {
- name 'Android Support DynamicAnimation'
- publish true
- inceptionYear '2017'
- description "Physics-based animation in support library, where the animations are driven by physics force. You can use this Animation library to create smooth and realistic animations."
+ name = "Android Support DynamicAnimation"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2017"
+ description = "Physics-based animation in support library, where the animations are driven by physics force. You can use this Animation library to create smooth and realistic animations."
+ legacySourceLocation = true
}
\ No newline at end of file
diff --git a/emoji/appcompat/build.gradle b/emoji/appcompat/build.gradle
index e22000c..7912c52 100644
--- a/emoji/appcompat/build.gradle
+++ b/emoji/appcompat/build.gradle
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api fileTree(include: ['*.jar'], dir: 'libs')
@@ -29,8 +34,11 @@
}
supportLibrary {
- name 'Android Emoji AppCompat'
- publish true
- inceptionYear '2017'
- description 'EmojiCompat Widgets for AppCompat integration'
+ name = "Android Emoji AppCompat"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2017"
+ description = "EmojiCompat Widgets for AppCompat integration"
+ legacySourceLocation = true
}
\ No newline at end of file
diff --git a/emoji/bundled/build.gradle b/emoji/bundled/build.gradle
index 766d165..c0613f22 100644
--- a/emoji/bundled/build.gradle
+++ b/emoji/bundled/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
ext {
fontDir = project(':noto-emoji-compat').projectDir
@@ -19,18 +24,21 @@
}
supportLibrary {
- name 'Android Emoji Compat'
- publish true
- inceptionYear '2017'
- description 'Library bundled with assets to enable emoji compatibility in Kitkat and newer devices to avoid the empty emoji characters.'
+ name = "Android Emoji Compat"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2017"
+ description = "Library bundled with assets to enable emoji compatibility in Kitkat and newer devices to avoid the empty emoji characters."
license {
- name 'SIL Open Font License, Version 1.1'
- url 'http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web'
+ name = "SIL Open Font License, Version 1.1"
+ url = "http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web"
}
license {
- name 'Unicode, Inc. License'
- url 'http://www.unicode.org/copyright.html#License'
+ name = "Unicode, Inc. License"
+ url = "http://www.unicode.org/copyright.html#License"
}
+ legacySourceLocation = true
}
\ No newline at end of file
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index b4cd3fc..f07c277 100644
--- a/emoji/core/build.gradle
+++ b/emoji/core/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
ext {
fontDir = project(':noto-emoji-compat').projectDir
@@ -29,13 +34,22 @@
}
sourceSets {
- main.res.srcDirs += 'src/main/res-public'
- main.resources {
- srcDirs = [fontDir.getAbsolutePath()]
- includes = ["LICENSE_UNICODE", "LICENSE_OFL"]
+ main {
+ // We use a non-standard manifest path.
+ manifest.srcFile 'AndroidManifest.xml'
+ res.srcDirs += 'src/main/res-public'
+ resources {
+ srcDirs = [fontDir.getAbsolutePath()]
+ includes = ["LICENSE_UNICODE", "LICENSE_OFL"]
+ }
}
androidTest {
+ // We use a non-standard test directory structure.
+ root 'tests'
+ java.srcDir 'tests/src'
+ res.srcDir 'tests/res'
+ manifest.srcFile 'tests/AndroidManifest.xml'
assets {
srcDirs = [new File(fontDir, "font").getAbsolutePath(),
new File(fontDir, "supported-emojis").getAbsolutePath()]
@@ -45,18 +59,20 @@
}
supportLibrary {
- name 'Android Emoji Compat'
- publish true
- inceptionYear '2017'
- description 'Core library to enable emoji compatibility in Kitkat and newer devices to avoid the empty emoji characters.'
+ name = "Android Emoji Compat"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2017"
+ description = "Core library to enable emoji compatibility in Kitkat and newer devices to avoid the empty emoji characters."
license {
- name 'SIL Open Font License, Version 1.1'
- url 'http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web'
+ name = "SIL Open Font License, Version 1.1"
+ url = "http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web"
}
license {
- name 'Unicode, Inc. License'
- url 'http://www.unicode.org/copyright.html#License'
+ name = "Unicode, Inc. License"
+ url = "http://www.unicode.org/copyright.html#License"
}
}
diff --git a/exifinterface/build.gradle b/exifinterface/build.gradle
index 3e034b1..b7218f9 100644
--- a/exifinterface/build.gradle
+++ b/exifinterface/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -13,8 +18,11 @@
}
supportLibrary {
- name 'Android Support ExifInterface'
- publish true
- inceptionYear '2016'
- description 'Android Support ExifInterface'
+ name = "Android Support ExifInterface"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2016"
+ description = "Android Support ExifInterface"
+ legacySourceLocation = true
}
diff --git a/fragment/build.gradle b/fragment/build.gradle
index 3a86f38..dfb6ca9 100644
--- a/fragment/build.gradle
+++ b/fragment/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-compat')
@@ -19,9 +24,11 @@
}
supportLibrary {
- name 'Android Support Library fragment'
- publish true
- inceptionYear '2011'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android Support Library fragment"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2011"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
-
diff --git a/fragment/src/main/java/android/support/v4/app/FragmentActivity.java b/fragment/src/main/java/android/support/v4/app/FragmentActivity.java
index 614ff35..78161a8 100644
--- a/fragment/src/main/java/android/support/v4/app/FragmentActivity.java
+++ b/fragment/src/main/java/android/support/v4/app/FragmentActivity.java
@@ -536,7 +536,7 @@
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ markFragmentsCreated();
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
@@ -591,7 +591,7 @@
super.onStop();
mStopped = true;
- markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ markFragmentsCreated();
mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
mFragments.dispatchStop();
@@ -970,18 +970,30 @@
}
}
- private static void markState(FragmentManager manager, Lifecycle.State state) {
+ private void markFragmentsCreated() {
+ boolean reiterate;
+ do {
+ reiterate = markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ } while (reiterate);
+ }
+
+ private static boolean markState(FragmentManager manager, Lifecycle.State state) {
+ boolean hadNotMarked = false;
Collection<Fragment> fragments = manager.getFragments();
for (Fragment fragment : fragments) {
if (fragment == null) {
continue;
}
- fragment.mLifecycleRegistry.markState(state);
+ if (fragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
+ fragment.mLifecycleRegistry.markState(state);
+ hadNotMarked = true;
+ }
FragmentManager childFragmentManager = fragment.peekChildFragmentManager();
if (childFragmentManager != null) {
- markState(childFragmentManager, state);
+ hadNotMarked |= markState(childFragmentManager, state);
}
}
+ return hadNotMarked;
}
}
diff --git a/fragment/tests/java/android/support/v4/app/FragmentArchLifecycleTest.java b/fragment/tests/java/android/support/v4/app/FragmentArchLifecycleTest.java
new file mode 100644
index 0000000..7e8ed1f
--- /dev/null
+++ b/fragment/tests/java/android/support/v4/app/FragmentArchLifecycleTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.os.Bundle;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.test.EmptyFragmentTestActivity;
+
+import junit.framework.Assert;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FragmentArchLifecycleTest {
+
+ @Rule
+ public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
+ new ActivityTestRule<>(EmptyFragmentTestActivity.class);
+
+ @Test
+ @UiThreadTest
+ public void testFragmentAdditionDuringOnStop() {
+ final EmptyFragmentTestActivity activity = mActivityRule.getActivity();
+ final FragmentManager fm = activity.getSupportFragmentManager();
+
+ final Fragment first = new Fragment();
+ final Fragment second = new Fragment();
+ fm.beginTransaction().add(first, "first").commitNow();
+ first.getLifecycle().addObserver(new LifecycleObserver() {
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ public void onStop() {
+ fm.beginTransaction().add(second, "second").commitNow();
+ first.getLifecycle().removeObserver(this);
+ }
+ });
+ activity.onSaveInstanceState(new Bundle());
+ Assert.assertEquals(Lifecycle.State.CREATED, first.getLifecycle().getCurrentState());
+ Assert.assertEquals(Lifecycle.State.CREATED, second.getLifecycle().getCurrentState());
+ Assert.assertEquals(Lifecycle.State.CREATED, activity.getLifecycle().getCurrentState());
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6314766..96e6dab 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-4.1-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-4.3-bin.zip
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index 6036284..bfe405f 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-vector-drawable')
@@ -25,8 +30,11 @@
}
supportLibrary {
- name 'Android Support AnimatedVectorDrawable'
- publish true
- inceptionYear '2015'
- description 'Android Support AnimatedVectorDrawable'
+ name = "Android Support AnimatedVectorDrawable"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "Android Support AnimatedVectorDrawable"
+ legacySourceLocation = true
}
\ No newline at end of file
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 949ba89..b8ab699 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -20,8 +25,11 @@
}
supportLibrary {
- name 'Android Support VectorDrawable'
- publish true
- inceptionYear '2015'
- description 'Android Support VectorDrawable'
-}
+ name = "Android Support VectorDrawable"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "Android Support VectorDrawable"
+ legacySourceLocation = true
+}
\ No newline at end of file
diff --git a/lifecycle/common-java8/build.gradle b/lifecycle/common-java8/build.gradle
index fccc813..7877b81 100644
--- a/lifecycle/common-java8/build.gradle
+++ b/lifecycle/common-java8/build.gradle
@@ -14,10 +14,13 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions;
import android.support.SupportLibraryExtension;
-apply plugin: android.support.SupportJavaLibraryPlugin
+plugins {
+ id("SupportJavaLibraryPlugin")
+}
dependencies {
testCompile libs.junit
@@ -28,12 +31,13 @@
createAndroidCheckstyle(project)
-version = LibraryVersions.LIFECYCLES_EXT.toString()
supportLibrary {
- name 'Android Lifecycle-Common for Java 8 Language'
- publish true
- inceptionYear '2017'
- description "Android Lifecycle-Common for Java 8 Language"
- url SupportLibraryExtension.ARCHITECTURE_URL
- java8Library true
+ name = "Android Lifecycle-Common for Java 8 Language"
+ publish = true
+ mavenVersion = LibraryVersions.LIFECYCLES_EXT
+ mavenGroup = LibraryGroups.LIFECYCLE
+ inceptionYear = "2017"
+ description = "Android Lifecycle-Common for Java 8 Language"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
+ java8Library = true
}
diff --git a/lifecycle/common/build.gradle b/lifecycle/common/build.gradle
index 57cfd00..a3b06dd 100644
--- a/lifecycle/common/build.gradle
+++ b/lifecycle/common/build.gradle
@@ -14,10 +14,13 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions;
import android.support.SupportLibraryExtension;
-apply plugin: android.support.SupportJavaLibraryPlugin
+plugins {
+ id("SupportJavaLibraryPlugin")
+}
dependencies {
testCompile libs.junit
@@ -27,11 +30,12 @@
createAndroidCheckstyle(project)
-version = LibraryVersions.LIFECYCLES_CORE.toString()
supportLibrary {
- name 'Android Lifecycle-Common'
- publish true
- inceptionYear '2017'
- description "Android Lifecycle-Common"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Lifecycle-Common"
+ publish = true
+ mavenVersion = LibraryVersions.LIFECYCLES_CORE
+ mavenGroup = LibraryGroups.LIFECYCLE
+ inceptionYear = "2017"
+ description = "Android Lifecycle-Common"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/lifecycle/compiler/build.gradle b/lifecycle/compiler/build.gradle
index e784f80..edc54b0 100644
--- a/lifecycle/compiler/build.gradle
+++ b/lifecycle/compiler/build.gradle
@@ -1,3 +1,4 @@
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
apply plugin: android.support.SupportKotlinLibraryPlugin
@@ -40,9 +41,11 @@
}
supportLibrary {
- name 'Android Lifecycles Compiler'
- publish true
- inceptionYear '2017'
- description "Android Lifecycles annotation processor"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Lifecycles Compiler"
+ publish = true
+ mavenVersion = LibraryVersions.LIFECYCLES_CORE
+ mavenGroup = LibraryGroups.LIFECYCLE
+ inceptionYear = "2017"
+ description = "Android Lifecycles annotation processor"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt
index 2edb234..5355615 100644
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt
@@ -24,7 +24,6 @@
import javax.lang.model.element.TypeElement
@SupportedAnnotationTypes("android.arch.lifecycle.OnLifecycleEvent")
-@SupportedSourceVersion(SourceVersion.RELEASE_7)
class LifecycleProcessor : AbstractProcessor() {
override fun process(annotations: MutableSet<out TypeElement>,
roundEnv: RoundEnvironment): Boolean {
@@ -32,4 +31,8 @@
writeModels(transformToOutput(processingEnv, input), processingEnv)
return true
}
+
+ override fun getSupportedSourceVersion(): SourceVersion {
+ return SourceVersion.latest()
+ }
}
diff --git a/lifecycle/extensions/build.gradle b/lifecycle/extensions/build.gradle
index 79be36c..6bd1bce 100644
--- a/lifecycle/extensions/build.gradle
+++ b/lifecycle/extensions/build.gradle
@@ -14,40 +14,34 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes.all {
consumerProguardFiles 'proguard-rules.pro'
}
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
}
-dependencies {
- compile project(":lifecycle:runtime")
- compile project(":arch:common")
- compile project(":arch:runtime")
- compile libs.support.fragments, libs.support_exclude_config
- compile project(":lifecycle:common")
- testCompile project(":arch:core-testing")
- testCompile libs.junit
- testCompile libs.mockito_core
+dependencies {
+ api project(":lifecycle:runtime")
+ api project(":arch:common")
+ api project(":arch:runtime")
+ api libs.support.fragments, libs.support_exclude_config
+ api project(":lifecycle:common")
+
+ testImplementation project(":arch:core-testing")
+ testImplementation libs.junit
+ testImplementation libs.mockito_core
androidTestImplementation libs.test_runner, { exclude module: 'support-annotations' }
androidTestImplementation libs.espresso_core, { exclude module: 'support-annotations' }
@@ -56,11 +50,12 @@
createAndroidCheckstyle(project)
-version = LibraryVersions.LIFECYCLES_EXT.toString()
supportLibrary {
- name 'Android Lifecycle Extensions'
- publish true
- inceptionYear '2017'
- description "Android Lifecycle Extensions"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Lifecycle Extensions"
+ publish = true
+ mavenVersion = LibraryVersions.LIFECYCLES_EXT
+ mavenGroup = LibraryGroups.LIFECYCLE
+ inceptionYear = "2017"
+ description = "Android Lifecycle Extensions"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/lifecycle/extensions/lint-baseline.xml b/lifecycle/extensions/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/lifecycle/extensions/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java
index 106b2ef..e8895bd 100644
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java
@@ -37,6 +37,7 @@
/**
* Return the application.
*/
+ @SuppressWarnings("TypeParameterUnusedInFormals")
@NonNull
public <T extends Application> T getApplication() {
//noinspection unchecked
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProvider.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProvider.java
index 29cbab8..a7b3aeb 100644
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProvider.java
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProvider.java
@@ -138,6 +138,7 @@
*/
public static class NewInstanceFactory implements Factory {
+ @SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
diff --git a/lifecycle/gradle/wrapper/gradle-wrapper.properties b/lifecycle/gradle/wrapper/gradle-wrapper.properties
index 467c103..b519e0a 100644
--- a/lifecycle/gradle/wrapper/gradle-wrapper.properties
+++ b/lifecycle/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-bin.zip
diff --git a/lifecycle/integration-tests/testapp/build.gradle b/lifecycle/integration-tests/testapp/build.gradle
index a1a16cb..64159bd 100644
--- a/lifecycle/integration-tests/testapp/build.gradle
+++ b/lifecycle/integration-tests/testapp/build.gradle
@@ -32,6 +32,13 @@
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
+ signingConfigs {
+ debug {
+ // Use a local debug keystore to avoid build server issues.
+ storeFile project.rootProject.init.debugKeystore
+ }
+ }
+
testOptions {
unitTests.returnDefaultValues = true
}
@@ -53,19 +60,21 @@
dependencies {
// IJ canont figure out transitive dependencies so need to declare them.
- compile project(":lifecycle:common")
- compile project(":lifecycle:runtime")
- compile project(":lifecycle:extensions")
+ implementation project(":lifecycle:common")
+ implementation project(":lifecycle:runtime")
+ implementation project(":lifecycle:extensions")
annotationProcessor project(":lifecycle:compiler")
+
androidTestAnnotationProcessor project(":lifecycle:compiler")
- androidTestCompile(libs.test_runner) {
+ androidTestImplementation(libs.test_runner) {
exclude module: 'support-annotations'
}
- androidTestCompile(libs.espresso_core, {
+ androidTestImplementation(libs.espresso_core, {
exclude group: 'com.android.support', module: 'support-annotations'
})
- testCompile libs.junit
- testCompile libs.mockito_core
+
+ testImplementation libs.junit
+ testImplementation libs.mockito_core
testAnnotationProcessor project(":lifecycle:compiler")
}
createAndroidCheckstyle(project)
diff --git a/lifecycle/reactivestreams/build.gradle b/lifecycle/reactivestreams/build.gradle
index bd64a32..d2beeed 100644
--- a/lifecycle/reactivestreams/build.gradle
+++ b/lifecycle/reactivestreams/build.gradle
@@ -14,53 +14,47 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
-
- testOptions {
- unitTests.returnDefaultValues = true
}
}
allprojects {
dependencies {
- compile project(":arch:common")
- compile project(":lifecycle:common")
- compile project(":lifecycle:extensions")
- compile project(":lifecycle:runtime")
- compile libs.support.annotations
- compile libs.reactive_streams
+ api project(":arch:common")
+ api project(":lifecycle:common")
+ api project(":lifecycle:extensions")
+ api project(":lifecycle:runtime")
+ api libs.support.annotations
+ api libs.reactive_streams
- testCompile libs.junit
- testCompile libs.rx_java
+ testImplementation libs.junit
+ testImplementation libs.rx_java
- testCompile(libs.test_runner) {
+ testImplementation(libs.test_runner) {
exclude module: 'support-annotations'
}
- androidTestCompile libs.support.app_compat, libs.support_exclude_config
+ androidTestImplementation libs.support.app_compat, libs.support_exclude_config
}
}
createAndroidCheckstyle(project)
-version = LibraryVersions.LIFECYCLES_EXT.toString()
supportLibrary {
- name 'Android Lifecycle Reactivestreams'
- publish true
- inceptionYear '2017'
- description "Android Lifecycle Reactivestreams"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Lifecycle Reactivestreams"
+ publish = true
+ mavenVersion = LibraryVersions.LIFECYCLES_EXT
+ mavenGroup = LibraryGroups.LIFECYCLE
+ inceptionYear = "2017"
+ description = "Android Lifecycle Reactivestreams"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/lifecycle/reactivestreams/lint-baseline.xml b/lifecycle/reactivestreams/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/lifecycle/reactivestreams/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/lifecycle/runtime/build.gradle b/lifecycle/runtime/build.gradle
index 111429c..01a8abb 100644
--- a/lifecycle/runtime/build.gradle
+++ b/lifecycle/runtime/build.gradle
@@ -1,37 +1,29 @@
+import android.support.LibraryGroups
import android.support.LibraryVersions
-
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes.all {
consumerProguardFiles 'proguard-rules.pro'
}
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
}
dependencies {
- compile project(":lifecycle:common")
- compile project(":arch:common")
+ api project(":lifecycle:common")
+ api project(":arch:common")
// necessary for IJ to resolve dependencies.
- compile libs.support.annotations
+ api libs.support.annotations
- testCompile libs.junit
- testCompile libs.mockito_core
+ testImplementation libs.junit
+ testImplementation libs.mockito_core
androidTestImplementation libs.junit
androidTestImplementation libs.test_runner, { exclude module: 'support-annotations' }
@@ -39,10 +31,11 @@
createAndroidCheckstyle(project)
-version = LibraryVersions.LIFECYCLES_RUNTIME.toString()
supportLibrary {
name 'Android Lifecycle Runtime'
publish true
+ mavenVersion = LibraryVersions.LIFECYCLES_RUNTIME
+ mavenGroup LibraryGroups.LIFECYCLE
inceptionYear '2017'
description "Android Lifecycle Runtime"
url SupportLibraryExtension.ARCHITECTURE_URL
diff --git a/lifecycle/runtime/lint-baseline.xml b/lifecycle/runtime/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/lifecycle/runtime/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/media-compat-test-client/build.gradle b/media-compat-test-client/build.gradle
index e4cec1d..47141db 100644
--- a/media-compat-test-client/build.gradle
+++ b/media-compat-test-client/build.gradle
@@ -13,7 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-apply plugin: android.support.SupportAndroidLibraryPlugin
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
androidTestImplementation project(':support-annotations')
@@ -30,4 +33,8 @@
defaultConfig {
minSdkVersion 14
}
+}
+
+supportLibrary {
+ legacySourceLocation = true
}
\ No newline at end of file
diff --git a/media-compat-test-service/build.gradle b/media-compat-test-service/build.gradle
index 1912719..946d48b 100644
--- a/media-compat-test-service/build.gradle
+++ b/media-compat-test-service/build.gradle
@@ -13,7 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-apply plugin: android.support.SupportAndroidLibraryPlugin
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
androidTestImplementation project(':support-annotations')
@@ -30,3 +33,7 @@
minSdkVersion 14
}
}
+
+supportLibrary {
+ legacySourceLocation = true
+}
diff --git a/media-compat/build.gradle b/media-compat/build.gradle
index c504996..ec4504e 100644
--- a/media-compat/build.gradle
+++ b/media-compat/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -35,8 +40,11 @@
}
supportLibrary {
- name 'Android Support Library media compat'
- publish true
- inceptionYear '2011'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android Support Library media compat"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2011"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
index 85f5a51..1b27925 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
@@ -41,10 +41,12 @@
import static android.support.v4.media.MediaBrowserProtocol.DATA_SEARCH_QUERY;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_CLIENT_VERSION;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_MESSENGER_BINDER;
+import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SERVICE_VERSION;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SESSION_BINDER;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT_FAILED;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_LOAD_CHILDREN;
+import static android.support.v4.media.MediaBrowserProtocol.SERVICE_VERSION_2;
import android.content.ComponentName;
import android.content.Context;
@@ -676,17 +678,15 @@
WeakReference<Subscription> mSubscriptionRef;
public SubscriptionCallback() {
+ mToken = new Binder();
if (Build.VERSION.SDK_INT >= 26) {
mSubscriptionCallbackObj =
MediaBrowserCompatApi26.createSubscriptionCallback(new StubApi26());
- mToken = null;
} else if (Build.VERSION.SDK_INT >= 21) {
mSubscriptionCallbackObj =
MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
- mToken = new Binder();
} else {
mSubscriptionCallbackObj = null;
- mToken = new Binder();
}
}
@@ -1583,6 +1583,7 @@
protected final CallbackHandler mHandler = new CallbackHandler(this);
private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
+ protected int mServiceVersion;
protected ServiceBinderWrapper mServiceBinderWrapper;
protected Messenger mCallbacksMessenger;
private MediaSessionCompat.Token mMediaSessionToken;
@@ -1852,6 +1853,7 @@
if (extras == null) {
return;
}
+ mServiceVersion = extras.getInt(EXTRA_SERVICE_VERSION, 0);
IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
if (serviceBinder != null) {
mServiceBinderWrapper = new ServiceBinderWrapper(serviceBinder, mRootHints);
@@ -1958,22 +1960,34 @@
@Override
public void subscribe(@NonNull String parentId, @Nullable Bundle options,
@NonNull SubscriptionCallback callback) {
- if (options == null) {
- MediaBrowserCompatApi21.subscribe(
- mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ // From service v2, we use compat code when subscribing.
+ // This is to prevent ClassNotFoundException when options has Parcelable in it.
+ if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
+ if (options == null) {
+ MediaBrowserCompatApi21.subscribe(
+ mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ } else {
+ MediaBrowserCompatApi26.subscribe(
+ mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+ }
} else {
- MediaBrowserCompatApi26.subscribe(
- mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+ super.subscribe(parentId, options, callback);
}
}
@Override
public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
- if (callback == null) {
- MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ // From service v2, we use compat code when subscribing.
+ // This is to prevent ClassNotFoundException when options has Parcelable in it.
+ if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
+ if (callback == null) {
+ MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ } else {
+ MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
+ callback.mSubscriptionCallbackObj);
+ }
} else {
- MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
- callback.mSubscriptionCallbackObj);
+ super.unsubscribe(parentId, callback);
}
}
}
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserProtocol.java b/media-compat/java/android/support/v4/media/MediaBrowserProtocol.java
index 7c23d26..8ed152d 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserProtocol.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserProtocol.java
@@ -45,7 +45,15 @@
* MediaBrowserServiceCompat.
*/
public static final int SERVICE_VERSION_1 = 1;
- public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1;
+
+ /**
+ * To prevent ClassNotFoundException from Parcelables, MediaBrowser(Service)Compat tries to
+ * avoid using framework code as much as possible (b/62648808). For backward compatibility,
+ * service v2 is introduced so that the browser can distinguish whether the service supports
+ * subscribing through compat code.
+ */
+ public static final int SERVICE_VERSION_2 = 2;
+ public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_2;
/*
* Messages sent from the media browser service compat to the media browser compat.
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
index 53b111a..27bf0e3 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -278,28 +278,8 @@
@Override
public void notifyChildrenChanged(final String parentId, final Bundle options) {
- if (mMessenger == null) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
- } else {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (IBinder binder : mConnections.keySet()) {
- ConnectionRecord connection = mConnections.get(binder);
- List<Pair<IBinder, Bundle>> callbackList =
- connection.subscriptions.get(parentId);
- if (callbackList != null) {
- for (Pair<IBinder, Bundle> callback : callbackList) {
- if (MediaBrowserCompatUtils.hasDuplicatedItems(
- options, callback.second)) {
- performLoadChildren(parentId, connection, callback.second);
- }
- }
- }
- }
- }
- });
- }
+ notifyChildrenChangedForFramework(parentId, options);
+ notifyChildrenChangedForCompat(parentId, options);
}
@Override
@@ -373,6 +353,31 @@
};
MediaBrowserServiceCompat.this.onLoadChildren(parentId, result);
}
+
+ void notifyChildrenChangedForFramework(final String parentId, final Bundle options) {
+ MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ }
+
+ void notifyChildrenChangedForCompat(final String parentId, final Bundle options) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (IBinder binder : mConnections.keySet()) {
+ ConnectionRecord connection = mConnections.get(binder);
+ List<Pair<IBinder, Bundle>> callbackList =
+ connection.subscriptions.get(parentId);
+ if (callbackList != null) {
+ for (Pair<IBinder, Bundle> callback : callbackList) {
+ if (MediaBrowserCompatUtils.hasDuplicatedItems(
+ options, callback.second)) {
+ performLoadChildren(parentId, connection, callback.second);
+ }
+ }
+ }
+ }
+ }
+ });
+ }
}
@RequiresApi(23)
@@ -421,16 +426,6 @@
}
@Override
- public void notifyChildrenChanged(final String parentId, final Bundle options) {
- if (options == null) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
- } else {
- MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
- options);
- }
- }
-
- @Override
public void onLoadChildren(String parentId,
final MediaBrowserServiceCompatApi26.ResultWrapper resultWrapper, Bundle options) {
final Result<List<MediaBrowserCompat.MediaItem>> result
@@ -466,6 +461,16 @@
}
return MediaBrowserServiceCompatApi26.getBrowserRootHints(mServiceObj);
}
+
+ @Override
+ void notifyChildrenChangedForFramework(final String parentId, final Bundle options) {
+ if (options != null) {
+ MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
+ options);
+ } else {
+ super.notifyChildrenChangedForFramework(parentId, options);
+ }
+ }
}
private final class ServiceHandler extends Handler {
diff --git a/media-compat/java/android/support/v4/media/MediaMetadataCompat.java b/media-compat/java/android/support/v4/media/MediaMetadataCompat.java
index 3ddf255..00f16cb 100644
--- a/media-compat/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaMetadataCompat.java
@@ -365,10 +365,12 @@
MediaMetadataCompat(Bundle bundle) {
mBundle = new Bundle(bundle);
+ mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader());
}
MediaMetadataCompat(Parcel in) {
mBundle = in.readBundle();
+ mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader());
}
/**
diff --git a/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java b/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
index df700cc..6356e33 100644
--- a/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
@@ -176,6 +176,39 @@
@Test
@SmallTest
+ public void testSubscriptionWithCustomOptionsWithRemoteService() throws Exception {
+ final String mediaId = "1000";
+ createMediaBrowser(TEST_REMOTE_BROWSER_SERVICE);
+ assertFalse(mMediaBrowser.isConnected());
+
+ connectMediaBrowserService();
+ assertTrue(mMediaBrowser.isConnected());
+
+ MediaMetadataCompat mediaMetadataCompat = new MediaMetadataCompat.Builder()
+ .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
+ .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
+ .putRating(MediaMetadataCompat.METADATA_KEY_RATING,
+ RatingCompat.newPercentageRating(0.5f))
+ .putRating(MediaMetadataCompat.METADATA_KEY_USER_RATING,
+ RatingCompat.newPercentageRating(0.8f))
+ .build();
+ Bundle options = new Bundle();
+ options.putParcelable(
+ StubRemoteMediaBrowserServiceCompat.MEDIA_METADATA, mediaMetadataCompat);
+
+ // Remote MediaBrowserService will create a media item with the given MediaMetadataCompat
+ mMediaBrowser.subscribe(mMediaBrowser.getRoot(), options, mSubscriptionCallback);
+ synchronized (mSubscriptionCallback.mWaitLock) {
+ mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
+ assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+ assertEquals(1, mSubscriptionCallback.mLastChildMediaItems.size());
+ assertEquals(mediaId, mSubscriptionCallback.mLastChildMediaItems.get(0).getMediaId());
+ }
+ mMediaBrowser.disconnect();
+ }
+
+ @Test
+ @SmallTest
public void testConnectTwice() throws Exception {
createMediaBrowser(TEST_BROWSER_SERVICE);
connectMediaBrowserService();
@@ -185,6 +218,7 @@
} catch (IllegalStateException e) {
// expected
}
+ mMediaBrowser.disconnect();
}
@Test
@@ -199,6 +233,7 @@
assertTrue(mConnectionCallback.mConnectionFailedCount > 0);
assertEquals(0, mConnectionCallback.mConnectedCount);
assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
+ mMediaBrowser.disconnect();
}
@Test
@@ -256,6 +291,7 @@
assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0],
mItemCallback.mLastMediaItem.getMediaId());
}
+ mMediaBrowser.disconnect();
}
@Test
@@ -330,6 +366,7 @@
}
// onChildrenLoaded should not be called.
assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+ mMediaBrowser.disconnect();
}
@Test
@@ -386,6 +423,7 @@
}
// onChildrenLoaded should not be called.
assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+ mMediaBrowser.disconnect();
}
@Test
@@ -401,6 +439,7 @@
assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID,
mSubscriptionCallback.mLastErrorId);
}
+ mMediaBrowser.disconnect();
}
@Test
@@ -427,6 +466,7 @@
assertEquals(pageSize,
mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
}
+ mMediaBrowser.disconnect();
}
@Test
@@ -474,6 +514,7 @@
for (StubSubscriptionCallback callback : subscriptionCallbacks) {
assertEquals(0, callback.mChildrenLoadedWithOptionCount);
}
+ mMediaBrowser.disconnect();
}
@Test
@@ -533,6 +574,7 @@
}
}
}
+ mMediaBrowser.disconnect();
}
@Test
@@ -549,6 +591,7 @@
assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0],
mItemCallback.mLastMediaItem.getMediaId());
}
+ mMediaBrowser.disconnect();
}
@Test
@@ -564,6 +607,7 @@
assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED,
mItemCallback.mLastErrorId);
}
+ mMediaBrowser.disconnect();
}
@Test
@@ -580,6 +624,7 @@
assertNull(mItemCallback.mLastMediaItem);
assertNull(mItemCallback.mLastErrorId);
}
+ mMediaBrowser.disconnect();
}
private void createMediaBrowser(final ComponentName component) {
diff --git a/media-compat/tests/src/android/support/v4/media/StubRemoteMediaBrowserServiceCompat.java b/media-compat/tests/src/android/support/v4/media/StubRemoteMediaBrowserServiceCompat.java
index 9d7e73d..8e03ab2 100644
--- a/media-compat/tests/src/android/support/v4/media/StubRemoteMediaBrowserServiceCompat.java
+++ b/media-compat/tests/src/android/support/v4/media/StubRemoteMediaBrowserServiceCompat.java
@@ -31,6 +31,7 @@
static final String EXTRAS_VALUE = "test_extras_value";
static final String MEDIA_ID_ROOT = "test_media_id_root";
+ static final String MEDIA_METADATA = "test_media_metadata";
static final String[] MEDIA_ID_CHILDREN = new String[]{
"test_media_id_children_0", "test_media_id_children_1",
@@ -66,6 +67,19 @@
}
}
+ @Override
+ public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result,
+ final Bundle options) {
+ MediaMetadataCompat metadata = options.getParcelable(MEDIA_METADATA);
+ if (metadata == null) {
+ super.onLoadChildren(parentMediaId, result, options);
+ } else {
+ List<MediaItem> mediaItems = new ArrayList<>();
+ mediaItems.add(new MediaItem(metadata.getDescription(), MediaItem.FLAG_PLAYABLE));
+ result.sendResult(mediaItems);
+ }
+ }
+
private MediaItem createMediaItem(String id) {
return new MediaItem(new MediaDescriptionCompat.Builder()
.setMediaId(id).setExtras(getBrowserRootHints()).build(),
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index bb9ad6b..d07103f 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -14,11 +14,14 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension;
-apply plugin: android.support.SupportJavaLibraryPlugin
-apply plugin: 'kotlin'
+plugins {
+ id("SupportJavaLibraryPlugin")
+ id("kotlin")
+}
dependencies {
compile libs.support.annotations
@@ -34,11 +37,12 @@
createAndroidCheckstyle(project)
createKotlinCheckstyle(project)
-version = LibraryVersions.PAGING.toString()
supportLibrary {
- name 'Android Paging-Common'
- publish true
- inceptionYear '2017'
- description "Android Paging-Common"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Paging-Common"
+ publish = true
+ mavenVersion = LibraryVersions.PAGING
+ mavenGroup = LibraryGroups.PAGING
+ inceptionYear = "2017"
+ description = "Android Paging-Common"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/paging/common/src/main/java/android/arch/paging/BoundedDataSource.java b/paging/common/src/main/java/android/arch/paging/BoundedDataSource.java
deleted file mode 100644
index 0656490..0000000
--- a/paging/common/src/main/java/android/arch/paging/BoundedDataSource.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Simplest data source form that provides all of its data through a single loadRange() method.
- * <p>
- * Requires that your data resides in positions <code>0</code> through <code>N</code>, where
- * <code>N</code> is the value returned from {@link #countItems()}. You must return the exact number
- * requested, so that the data as returned can be safely prepended/appended to what has already
- * been loaded.
- * <p>
- * For more flexibility in how many items to load, or to avoid counting your data source, override
- * {@link PositionalDataSource} directly.
- *
- * @param <Value> Value type returned by the data source.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class BoundedDataSource<Value> extends PositionalDataSource<Value> {
- /**
- * Called to load items at from the specified position range.
- *
- * @param startPosition Index of first item to load.
- * @param loadCount Exact number of items to load. Returning a different number will cause
- * an exception to be thrown.
- * @return List of loaded items. Null if the BoundedDataSource is no longer valid, and should
- * not be queried again.
- */
- @WorkerThread
- @Nullable
- public abstract List<Value> loadRange(int startPosition, int loadCount);
-
- @WorkerThread
- @Nullable
- @Override
- public List<Value> loadAfter(int startIndex, int pageSize) {
- return loadRange(startIndex, pageSize);
- }
-
- @WorkerThread
- @Nullable
- @Override
- public List<Value> loadBefore(int startIndex, int pageSize) {
- if (startIndex < 0) {
- return new ArrayList<>();
- }
- int loadSize = Math.min(pageSize, startIndex + 1);
- startIndex = startIndex - loadSize + 1;
- List<Value> result = loadRange(startIndex, loadSize);
- if (result != null) {
- if (result.size() != loadSize) {
- throw new IllegalStateException("invalid number of items returned.");
- }
- }
- return result;
- }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java b/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
index 414c4ff..03f2e86 100644
--- a/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
@@ -19,44 +19,21 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import java.util.List;
-
abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
@Override
boolean isContiguous() {
return true;
}
- abstract void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders,
- @NonNull PageResult.Receiver<Key, Value> receiver);
+ public abstract void loadInitial(@Nullable Key key, int initialLoadSize,
+ boolean enablePlaceholders,
+ @NonNull InitialLoadCallback<Value> callback);
- void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
- @NonNull PageResult.Receiver<Key, Value> receiver) {
- if (!isInvalid()) {
- List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize);
+ abstract void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ @NonNull LoadCallback<Value> callback);
- if (list != null && !isInvalid()) {
- receiver.postOnPageResult(new PageResult<>(
- PageResult.APPEND, new Page<Key, Value>(list), 0, 0, 0));
- return;
- }
- }
- receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.APPEND));
- }
-
- void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
- @NonNull PageResult.Receiver<Key, Value> receiver) {
- if (!isInvalid()) {
- List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize);
-
- if (list != null && !isInvalid()) {
- receiver.postOnPageResult(new PageResult<>(
- PageResult.PREPEND, new Page<Key, Value>(list), 0, 0, 0));
- return;
- }
- }
- receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.PREPEND));
- }
+ abstract void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ @NonNull LoadCallback<Value> callback);
/**
* Get the key from either the position, or item, or null if position/item invalid.
@@ -65,12 +42,4 @@
* that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
*/
abstract Key getKey(int position, Value item);
-
- @Nullable
- abstract List<Value> loadAfterImpl(int currentEndIndex,
- @NonNull Value currentEndItem, int pageSize);
-
- @Nullable
- abstract List<Value> loadBeforeImpl(int currentBeginIndex,
- @NonNull Value currentBeginItem, int pageSize);
}
diff --git a/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java b/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
index cdff391..b54cb84 100644
--- a/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
+++ b/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
@@ -21,6 +21,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import java.util.List;
import java.util.concurrent.Executor;
class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
@@ -31,28 +32,14 @@
private int mPrependItemsRequested = 0;
private int mAppendItemsRequested = 0;
- @SuppressWarnings("unchecked")
- private final PagedStorage<K, V> mKeyedStorage = (PagedStorage<K, V>) mStorage;
-
- private final PageResult.Receiver<K, V> mReceiver = new PageResult.Receiver<K, V>() {
- @AnyThread
- @Override
- public void postOnPageResult(@NonNull final PageResult<K, V> pageResult) {
- // NOTE: if we're already on main thread, this can delay page receive by a frame
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- onPageResult(pageResult);
- }
- });
- }
-
+ private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
// Creation thread for initial synchronous load, otherwise main thread
// Safe to access main thread only state - no other thread has reference during construction
@AnyThread
@Override
- public void onPageResult(@NonNull PageResult<K, V> pageResult) {
- if (pageResult.page == null) {
+ public void onPageResult(@PageResult.ResultType int resultType,
+ @NonNull PageResult<V> pageResult) {
+ if (pageResult.isInvalid()) {
detach();
return;
}
@@ -62,25 +49,25 @@
return;
}
- Page<K, V> page = pageResult.page;
- if (pageResult.type == PageResult.INIT) {
- mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
+ List<V> page = pageResult.page;
+ if (resultType == PageResult.INIT) {
+ mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
- notifyInserted(0, mKeyedStorage.size());
- } else if (pageResult.type == PageResult.APPEND) {
- mKeyedStorage.appendPage(page, ContiguousPagedList.this);
- } else if (pageResult.type == PageResult.PREPEND) {
- mKeyedStorage.prependPage(page, ContiguousPagedList.this);
+ mLastLoad = pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
+ } else if (resultType == PageResult.APPEND) {
+ mStorage.appendPage(page, ContiguousPagedList.this);
+ } else if (resultType == PageResult.PREPEND) {
+ mStorage.prependPage(page, ContiguousPagedList.this);
}
if (mBoundaryCallback != null) {
boolean deferEmpty = mStorage.size() == 0;
boolean deferBegin = !deferEmpty
- && pageResult.type == PageResult.PREPEND
- && pageResult.page.items.size() == 0;
+ && resultType == PageResult.PREPEND
+ && pageResult.page.size() == 0;
boolean deferEnd = !deferEmpty
- && pageResult.type == PageResult.APPEND
- && pageResult.page.items.size() == 0;
+ && resultType == PageResult.APPEND
+ && pageResult.page.size() == 0;
deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
}
}
@@ -93,24 +80,36 @@
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key) {
- super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor,
+ super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
- // blocking init just triggers the initial load on the construction thread -
- // Could still be posted with callback, if desired.
- mDataSource.loadInitial(key,
- mConfig.initialLoadSizeHint,
- mConfig.enablePlaceholders,
- mReceiver);
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ @DataSource.LoadCountType int type = mConfig.enablePlaceholders
+ ? DataSource.LOAD_COUNT_ACCEPTED
+ : DataSource.LOAD_COUNT_PREVENTED;
+
+ DataSource.InitialLoadCallback<V> callback = new DataSource.InitialLoadCallback<>(
+ type, mConfig.pageSize, mDataSource, mReceiver);
+ mDataSource.loadInitial(key,
+ mConfig.initialLoadSizeHint,
+ mConfig.enablePlaceholders,
+ callback);
+
+ // If initialLoad's callback is not called within the body, we force any following calls
+ // to post to the UI thread. This constructor may be run on a background thread, but
+ // after constructor, mutation must happen on UI thread.
+ callback.setPostExecutor(mMainThreadExecutor);
+ }
}
@MainThread
@Override
void dispatchUpdatesSinceSnapshot(
@NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
-
- final PagedStorage<?, V> snapshot = pagedListSnapshot.mStorage;
+ final PagedStorage<V> snapshot = pagedListSnapshot.mStorage;
final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
@@ -120,7 +119,8 @@
// Validate that the snapshot looks like a previous version of this list - if it's not,
// we can't be sure we'll dispatch callbacks safely
- if (newlyAppended < 0
+ if (snapshot.isEmpty()
+ || newlyAppended < 0
|| newlyPrepended < 0
|| mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
|| mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
@@ -190,7 +190,14 @@
if (isDetached()) {
return;
}
- mDataSource.loadBefore(position, item, mConfig.pageSize, mReceiver);
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ DataSource.LoadCallback<V> callback = new DataSource.LoadCallback<>(
+ PageResult.PREPEND, mMainThreadExecutor, mDataSource, mReceiver);
+ mDataSource.loadBefore(position, item, mConfig.pageSize, callback);
+ }
+
}
});
}
@@ -213,7 +220,13 @@
if (isDetached()) {
return;
}
- mDataSource.loadAfter(position, item, mConfig.pageSize, mReceiver);
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ DataSource.LoadCallback<V> callback = new DataSource.LoadCallback<>(
+ PageResult.APPEND, mMainThreadExecutor, mDataSource, mReceiver);
+ mDataSource.loadAfter(position, item, mConfig.pageSize, callback);
+ }
}
});
}
diff --git a/paging/common/src/main/java/android/arch/paging/DataSource.java b/paging/common/src/main/java/android/arch/paging/DataSource.java
index ff44521..2e41cf6 100644
--- a/paging/common/src/main/java/android/arch/paging/DataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/DataSource.java
@@ -16,30 +16,76 @@
package android.arch.paging;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.support.annotation.AnyThread;
+import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
+import java.lang.annotation.Retention;
+import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Base class for incremental data loading, used in list paging. To implement, extend either the
- * {@link KeyedDataSource}, or {@link TiledDataSource} subclass.
+ * Base class for loading pages of snapshot data into a {@link PagedList}.
* <p>
- * Choose based on whether each load operation is based on the position of the data in the list.
+ * DataSource is queried to load pages of content into a {@link PagedList}. A PagedList can grow as
+ * it loads more data, but the data loaded cannot be updated.
* <p>
- * Use {@link KeyedDataSource} if you need to use data from item <code>N-1</code> to load item
- * <code>N</code>. For example, if requesting the backend for the next comments in the list
+ * A PagedList / DataSource pair serve as a snapshot of the data set being loaded. If the
+ * underlying data set is modified, a new PagedList / DataSource pair must be created to represent
+ * the new data.
+ * <h4>Loading Pages</h4>
+ * PagedList queries data from its DataSource in response to loading hints. {@link PagedListAdapter}
+ * calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
+ * <p>
+ * To control how and when a PagedList queries data from its DataSource, see
+ * {@link PagedList.Config}. The Config object defines things like load sizes and prefetch distance.
+ * <h4>Updating Paged Data</h4>
+ * A PagedList / DataSource pair are a snapshot of the data set. A new pair of
+ * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or
+ * content update occurs. A DataSource must detect that it cannot continue loading its
+ * snapshot (for instance, when Database query notices a table being invalidated), and call
+ * {@link #invalidate()}. Then a new PagedList / DataSource pair would be created to load data from
+ * the new state of the Database query.
+ * <p>
+ * To page in data that doesn't update, you can create a single DataSource, and pass it to a single
+ * PagedList. For example, loading from network when the network's paging API doesn't provide
+ * updates.
+ * <p>
+ * To page in data from a source that does provide updates, you can create a
+ * {@link DataSource.Factory}, where each DataSource created is invalidated when an update to the
+ * data set occurs that makes the current snapshot invalid. For example, when paging a query from
+ * the Database, and the table being queried inserts or removes items. You can also use a
+ * DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content
+ * (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data,
+ * you can connect an explicit refresh signal to call {@link #invalidate()} on the current
+ * DataSource.
+ * <p>
+ * If you have more granular update signals, such as a network API signaling an update to a single
+ * item in the list, it's recommended to load data from network into memory. Then present that
+ * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory
+ * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
+ * snapshot can be created.
+ * <h4>Implementing a DataSource</h4>
+ * To implement, extend either the {@link KeyedDataSource}, or {@link PositionalDataSource}
+ * subclass. Choose based on whether each load operation is based on the position of the data in the
+ * list.
+ * <p>
+ * Use {@link KeyedDataSource} if you need to use data from item {@code N-1} to load item
+ * {@code N}. For example, if requesting the backend for the next comments in the list
* requires the ID or timestamp of the most recent loaded comment, or if querying the next users
* from a name-sorted database query requires the name and unique ID of the previous.
* <p>
- * Use {@link TiledDataSource} if you can load arbitrary pages based solely on position information,
- * and can provide a fixed item count. TiledDataSource supports querying pages at arbitrary
- * positions, so can provide data to PagedLists in arbitrary order.
+ * Use {@link PositionalDataSource} if you can load arbitrary pages based solely on position
+ * information, and can provide a fixed item count. PositionalDataSource supports querying pages at
+ * arbitrary positions, so can provide data to PagedLists in arbitrary order.
* <p>
- * Because a <code>null</code> item indicates a placeholder in {@link PagedList}, DataSource may not
- * return <code>null</code> items in lists that it loads. This is so that users of the PagedList
+ * Because a {@code null} item indicates a placeholder in {@link PagedList}, DataSource may not
+ * return {@code null} items in lists that it loads. This is so that users of the PagedList
* can differentiate unloaded placeholder items from content that has been paged in.
*
* @param <Key> Input used to trigger initial load from the DataSource. Often an Integer position.
@@ -47,8 +93,36 @@
*/
@SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety
public abstract class DataSource<Key, Value> {
-
+ /**
+ * Factory for DataSources.
+ * <p>
+ * Data-loading systems of an application or library can implement this interface to allow
+ * {@code LiveData<PagedList>}s to be created. For example, Room can provide a
+ * DataSource.Factory for a given SQL query:
+ *
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ * public abstract DataSource.Factory<Integer, User> usersByLastName();
+ * }
+ * </pre>
+ * In the above sample, {@code Integer} is used because it is the {@code Key} type of
+ * PositionalDataSource. Currently, Room uses the {@code LIMIT}/{@code OFFSET} SQL keywords to
+ * page a large query with a PositionalDataSource.
+ *
+ * @param <Key> Key identifying items in DataSource.
+ * @param <Value> Type of items in the list loaded by the DataSources.
+ */
public interface Factory<Key, Value> {
+ /**
+ * Create a DataSource.
+ * <p>
+ * The DataSource should invalidate itself if the snapshot is no longer valid, and a new
+ * DataSource should be queried from the Factory.
+ *
+ * @return the new DataSource.
+ */
DataSource<Key, Value> create();
}
@@ -58,19 +132,206 @@
}
/**
- * If returned by countItems(), indicates an undefined number of items are provided by the data
- * source. Continued querying in either direction may continue to produce more data.
- */
- @SuppressWarnings("WeakerAccess")
- public static int COUNT_UNDEFINED = -1;
-
- /**
* Returns true if the data source guaranteed to produce a contiguous set of items,
* never producing gaps.
*/
abstract boolean isContiguous();
/**
+ * Callback for DataSource initial loading methods to return data and position/count
+ * information.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
+ */
+ public static class InitialLoadCallback<T> extends LoadCallback<T> {
+ private final int mPageSize;
+
+ InitialLoadCallback(@LoadCountType int countType, int pageSize,
+ DataSource dataSource, PageResult.Receiver<T> receiver) {
+ super(PageResult.INIT, countType, dataSource, receiver);
+ mPageSize = pageSize;
+ if (mPageSize < 1) {
+ throw new IllegalArgumentException("Page size must be non-negative");
+ }
+ }
+
+ /**
+ * Called to pass initial load state from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * and inform how many placeholders should be shown before and after. If counting is cheap
+ * to compute (for example, if a network load returns the information regardless), it's
+ * recommended to pass data back through this method.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list, relative to the total
+ * count. If there are {@code N} items before the items in data that can be
+ * loaded from this DataSource, pass {@code N}.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial {@code data} parameter
+ * as well as any items that can be loaded in front or behind of
+ * {@code data}.
+ */
+ public void onResult(@NonNull List<T> data, int position, int totalCount) {
+ if (position < 0) {
+ throw new IllegalArgumentException("Position must be non-negative");
+ }
+ if (data.size() + position > totalCount) {
+ throw new IllegalArgumentException(
+ "List size + position too large; last item in list beyond totalCount");
+ }
+ if (data.size() == 0 && totalCount > 0) {
+ throw new IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set.");
+ }
+ if (mCountType == LOAD_COUNT_REQUIRED_TILED
+ && position + data.size() != totalCount
+ && data.size() % mPageSize != 0) {
+ throw new IllegalArgumentException("PositionalDataSource requires initial load size"
+ + " to be a multiple of page size to support internal tiling.");
+ }
+
+ int trailingUnloadedCount = totalCount - position - data.size();
+ if (mCountType == LOAD_COUNT_REQUIRED_TILED || mCountType == LOAD_COUNT_ACCEPTED) {
+ dispatchResultToReceiver(new PageResult<>(
+ data, position, trailingUnloadedCount, 0));
+ } else {
+ dispatchResultToReceiver(new PageResult<>(data, position));
+ }
+ }
+
+ /**
+ * Called to pass initial load state from a DataSource without supporting placeholders.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * if position is known but total size is not. If counting is not expensive, consider
+ * calling the three parameter variant: {@link #onResult(List, int, int)}.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are {@code N}
+ * items before the items in data that can be provided by this DataSource,
+ * pass {@code N}.
+ */
+ void onResult(@NonNull List<T> data, int position) {
+ // not counting, don't need to check mAcceptCount
+ dispatchResultToReceiver(new PageResult<>(
+ data, 0, 0, position));
+ }
+ }
+
+ @Retention(SOURCE)
+ @IntDef({LOAD_COUNT_PREVENTED, LOAD_COUNT_ACCEPTED, LOAD_COUNT_REQUIRED_TILED})
+ @interface LoadCountType {}
+ static final int LOAD_COUNT_PREVENTED = 0;
+ static final int LOAD_COUNT_ACCEPTED = 1;
+ static final int LOAD_COUNT_REQUIRED_TILED = 2;
+
+ /**
+ * Callback for DataSource loading methods to return data.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
+ */
+ public static class LoadCallback<T> {
+ @PageResult.ResultType
+ final int mResultType;
+ @LoadCountType
+ final int mCountType;
+ private final DataSource mDataSource;
+ private final PageResult.Receiver<T> mReceiver;
+
+ private int mPositionOffset = 0;
+
+ // mSignalLock protects mPostExecutor, and mHasSignalled
+ private final Object mSignalLock = new Object();
+ private Executor mPostExecutor = null;
+ private boolean mHasSignalled = false;
+
+ private LoadCallback(@PageResult.ResultType int resultType, @LoadCountType int countType,
+ DataSource dataSource, PageResult.Receiver<T> receiver) {
+ mResultType = resultType;
+ mCountType = countType;
+ mDataSource = dataSource;
+ mReceiver = receiver;
+ }
+
+ LoadCallback(int type, Executor mainThreadExecutor,
+ DataSource dataSource, PageResult.Receiver<T> receiver) {
+ mResultType = type;
+ mCountType = LOAD_COUNT_PREVENTED;
+ mPostExecutor = mainThreadExecutor;
+ mDataSource = dataSource;
+ mReceiver = receiver;
+ }
+
+ void setPositionOffset(int positionOffset) {
+ mPositionOffset = positionOffset;
+ }
+
+ void setPostExecutor(Executor postExecutor) {
+ synchronized (mSignalLock) {
+ mPostExecutor = postExecutor;
+ }
+ }
+
+ /**
+ * Called to pass loaded data from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code load} methods to return data.
+ *
+ * @param data List of items loaded from the DataSource.
+ */
+ public void onResult(@NonNull List<T> data) {
+ if (mCountType == LOAD_COUNT_REQUIRED_TILED && !data.isEmpty()) {
+ throw new IllegalArgumentException(
+ "PositionalDataSource requires calling the three argument version of"
+ + " InitialLoadCallback.onResult() to pass position information");
+ }
+ dispatchResultToReceiver(new PageResult<>(
+ data, 0, 0, mPositionOffset));
+ }
+
+ void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
+ Executor executor;
+ synchronized (mSignalLock) {
+ if (mHasSignalled) {
+ throw new IllegalStateException(
+ "LoadCallback already dispatched, cannot dispatch again.");
+ }
+ mHasSignalled = true;
+ executor = mPostExecutor;
+ }
+
+ final PageResult<T> resolvedResult =
+ mDataSource.isInvalid() ? PageResult.<T>getInvalidResult() : result;
+
+ if (executor != null) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mReceiver.onPageResult(mResultType, result);
+ }
+ });
+ } else {
+ mReceiver.onPageResult(mResultType, result);
+ }
+ }
+ }
+
+ /**
* Invalidation callback for DataSource.
* <p>
* Used to signal when a DataSource a data source has become invalid, and that a new data source
diff --git a/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
index 3214a4e..b6656f3 100644
--- a/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 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.
@@ -16,223 +16,127 @@
package android.arch.paging;
-import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-
/**
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
* <p>
- * Implement a DataSource using KeyedDataSource if you need to use data from item <code>N-1</code>
- * to load item <code>N</code>. This is common, for example, in sorted database queries where
+ * Implement a DataSource using KeyedDataSource if you need to use data from item {@code N - 1}
+ * to load item {@code N}. This is common, for example, in sorted database queries where
* attributes of the item such just before the next query define how to execute it.
- * <p>
- * A compute usage pattern with Room SQL queries would look like this (though note, Room plans to
- * provide generation of much of this code in the future):
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * from user ORDER BY name DESC LIMIT :limit")
- * public abstract List<User> userNameInitial(int limit);
- *
- * {@literal @}Query("SELECT * from user WHERE name < :key ORDER BY name DESC LIMIT :limit")
- * public abstract List<User> userNameLoadAfter(String key, int limit);
- *
- * {@literal @}Query("SELECT * from user WHERE name > :key ORDER BY name ASC LIMIT :limit")
- * public abstract List<User> userNameLoadBefore(String key, int limit);
- * }
- *
- * public class KeyedUserQueryDataSource extends KeyedDataSource<String, User> {
- * private MyDatabase mDb;
- * private final UserDao mUserDao;
- * {@literal @}SuppressWarnings("FieldCanBeLocal")
- * private final InvalidationTracker.Observer mObserver;
- *
- * public KeyedUserQueryDataSource(MyDatabase db) {
- * mDb = db;
- * mUserDao = db.getUserDao();
- * mObserver = new InvalidationTracker.Observer("user") {
- * {@literal @}Override
- * public void onInvalidated({@literal @}NonNull Set<String> tables) {
- * // the user table has been invalidated, invalidate the DataSource
- * invalidate();
- * }
- * };
- * db.getInvalidationTracker().addWeakObserver(mObserver);
- * }
- *
- * {@literal @}Override
- * public boolean isInvalid() {
- * mDb.getInvalidationTracker().refreshVersionsSync();
- * return super.isInvalid();
- * }
- *
- * {@literal @}Override
- * public String getKey({@literal @}NonNull User item) {
- * return item.getName();
- * }
- *
- * {@literal @}Override
- * public List<User> loadInitial(int pageSize) {
- * return mUserDao.userNameInitial(pageSize);
- * }
- *
- * {@literal @}Override
- * public List<User> loadBefore({@literal @}NonNull String userName, int pageSize) {
- * // Return items adjacent to 'userName' in reverse order
- * // it's valid to return a different-sized list of items than pageSize, if it's easier
- * return mUserDao.userNameLoadBefore(userName, pageSize);
- * }
- *
- * {@literal @}Override
- * public List<User> loadAfter({@literal @}Nullable String userName, int pageSize) {
- * // Return items adjacent to 'userName'
- * // it's valid to return a different-sized list of items than pageSize, if it's easier
- * return mUserDao.userNameLoadAfter(userName, pageSize);
- * }
- * }</pre>
*
* @param <Key> Type of data used to query Value types out of the DataSource.
* @param <Value> Type of items being loaded by the DataSource.
*/
public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
-
- @Nullable
@Override
- List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) {
- return loadAfter(getKey(currentEndItem), pageSize);
+ final void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ @NonNull LoadCallback<Value> callback) {
+ loadAfter(getKey(currentEndItem), pageSize, callback);
+ }
+
+ @Override
+ final void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ @NonNull LoadCallback<Value> callback) {
+ loadBefore(getKey(currentBeginItem), pageSize, callback);
}
@Nullable
@Override
- List<Value> loadBeforeImpl(
- int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize) {
- List<Value> list = loadBefore(getKey(currentBeginItem), pageSize);
-
- if (list != null && list.size() > 1) {
- // TODO: move out of keyed entirely, into the DB DataSource.
- list = new ArrayList<>(list);
- Collections.reverse(list);
+ final Key getKey(int position, Value item) {
+ if (item == null) {
+ return null;
}
- return list;
+
+ return getKey(item);
}
- @Override
- void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders,
- @NonNull PageResult.Receiver<Key, Value> receiver) {
-
- PageResult<Key, Value> pageResult =
- loadInitialInternal(key, initialLoadSize, enablePlaceholders);
- if (pageResult == null) {
- // loading failed, return empty page
- receiver.onPageResult(new PageResult<Key, Value>(PageResult.INIT));
- } else {
- receiver.onPageResult(pageResult);
- }
- }
-
/**
- * Try initial load, and either return the successful initial load to the receiver,
- * or null if unsuccessful.
+ * Load initial data.
+ * <p>
+ * This method is called first to initialize a PagedList with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
+ * the callback via the three-parameter
+ * {@link DataSource.InitialLoadCallback#onResult(List, int, int)}. This enables PagedLists
+ * presenting data from this source to display placeholders to represent unloaded items.
+ * <p>
+ * {@code initialLoadKey} and {@code requestedLoadSize} are hints, not requirements, so if it is
+ * difficult or impossible to respect them, they may be altered. Note that ignoring the
+ * {@code initialLoadKey} can prevent subsequent PagedList/DataSource pairs from initializing at
+ * the same location. If your data source never invalidates (for example, loading from the
+ * network without the network ever signalling that old data must be reloaded), it's fine to
+ * ignore the {@code initialLoadKey} and always start from the beginning of the data set.
+ *
+ * @param initialLoadKey Load items around this key, or at the beginning of the data set if null
+ * is passed.
+ * @param requestedLoadSize Suggested number of items to load.
+ * @param enablePlaceholders Signals whether counting is requested. If false, you can
+ * potentially save work by calling the single-parameter variant of
+ * {@link DataSource.LoadCallback#onResult(List)} and not counting the
+ * number of items in the data set.
+ * @param callback DataSource.LoadCallback that receives initial load data.
*/
- @Nullable
- private PageResult<Key, Value> loadInitialInternal(
- @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
- // check if invalid at beginning, and before returning a valid list
- if (isInvalid()) {
- return null;
- }
+ @Override
+ public abstract void loadInitial(@Nullable Key initialLoadKey, int requestedLoadSize,
+ boolean enablePlaceholders, @NonNull InitialLoadCallback<Value> callback);
- List<Value> list;
- if (key == null) {
- // no key, so load initial.
- list = loadInitial(initialLoadSize);
- if (list == null) {
- return null;
- }
- } else {
- List<Value> after = loadAfter(key, initialLoadSize / 2);
- if (after == null) {
- return null;
- }
+ /**
+ * Load list data after the specified item.
+ * <p>
+ * It's valid to return a different list size than the page size, if it's easier for this data
+ * source. It is generally safer to increase the number loaded than reduce.
+ * <p>
+ * Data may be passed synchronously during the loadAfter method, or deferred and called at a
+ * later time. Further loads going down will be blocked until the callback is called.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param currentEndKey Load items after this key. May be null on initial load, to indicate load
+ * from beginning.
+ * @param pageSize Suggested number of items to load.
+ * @param callback DataSource.LoadCallback that receives loaded data.
+ */
+ public abstract void loadAfter(@NonNull Key currentEndKey, int pageSize,
+ @NonNull LoadCallback<Value> callback);
- Key loadBeforeKey = after.isEmpty() ? key : getKey(after.get(0));
- List<Value> before = loadBefore(loadBeforeKey, initialLoadSize / 2);
- if (before == null) {
- return null;
- }
- if (!after.isEmpty() || !before.isEmpty()) {
- // one of the lists has data
- if (after.isEmpty()) {
- // retry loading after, since it may be that the key passed points to the end of
- // the list, so we need to load after the last item in the before list
- after = loadAfter(getKey(before.get(0)), initialLoadSize / 2);
- if (after == null) {
- return null;
- }
- }
- // assemble full list
- list = new ArrayList<>();
- list.addAll(before);
- // Note - we reverse the list instead of before, in case before is immutable
- Collections.reverse(list);
- list.addAll(after);
- } else {
- // load before(key) and load after(key) failed - try load initial to be *sure* we
- // catch the case where there's only one item, which is loaded by the key case
- list = loadInitial(initialLoadSize);
- if (list == null) {
- return null;
- }
- }
- }
-
- final Page<Key, Value> page = new Page<>(list);
-
- if (list.isEmpty()) {
- if (isInvalid()) {
- return null;
- }
- // wasn't able to load any items, but not invalid - return an empty page.
- return new PageResult<>(PageResult.INIT, page, 0, 0, 0);
- }
-
- int itemsBefore = COUNT_UNDEFINED;
- int itemsAfter = COUNT_UNDEFINED;
- if (enablePlaceholders) {
- itemsBefore = countItemsBefore(getKey(list.get(0)));
- itemsAfter = countItemsAfter(getKey(list.get(list.size() - 1)));
- }
-
- if (isInvalid()) {
- return null;
- }
- if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) {
- itemsBefore = 0;
- itemsAfter = 0;
- }
- return new PageResult<>(
- PageResult.INIT,
- page,
- itemsBefore,
- itemsAfter,
- 0);
- }
+ /**
+ * Load data before the currently loaded content.
+ * <p>
+ * It's valid to return a different list size than the page size, if it's easier for this data
+ * source. It is generally safer to increase the number loaded than reduce. Note that the last
+ * item returned must be directly adjacent to the key passed, so varying size from the pageSize
+ * requested should effectively grow or shrink the list by modifying the beginning, not the end.
+ * <p>
+ * Data may be passed synchronously during the loadBefore method, or deferred and called at a
+ * later time. Further loads going up will be blocked until the callback is called.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ * <p class="note"><strong>Note:</strong> Data must be returned in the order it will be
+ * presented in the list.
+ *
+ * @param currentBeginKey Load items before this key.
+ * @param pageSize Suggested number of items to load.
+ * @param callback DataSource.LoadCallback that receives loaded data.
+ */
+ public abstract void loadBefore(@NonNull Key currentBeginKey, int pageSize,
+ @NonNull LoadCallback<Value> callback);
/**
* Return a key associated with the given item.
* <p>
* If your KeyedDataSource is loading from a source that is sorted and loaded by a unique
* integer ID, you would return {@code item.getID()} here. This key can then be passed to
- * {@link #loadBefore(Key, int)} or {@link #loadAfter(Key, int)} to load additional items
- * adjacent to the item passed to this function.
+ * {@link #loadBefore(Object, int, LoadCallback)} or
+ * {@link #loadAfter(Object, int, LoadCallback)} to load additional items adjacent to the item
+ * passed to this function.
* <p>
* If your key is more complex, such as when you're sorting by name, then resolving collisions
* with integer ID, you'll need to return both. In such a case you would use a wrapper class,
@@ -243,101 +147,5 @@
* @return Key associated with given item.
*/
@NonNull
- @AnyThread
public abstract Key getKey(@NonNull Value item);
-
- /**
- * Return the number of items that occur before the item uniquely identified by {@code key} in
- * the data set.
- * <p>
- * For example, if you're loading items sorted by ID, then this would return the total number of
- * items with ID less than {@code key}.
- * <p>
- * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsAfter(Key)}, your
- * data source will not present placeholder null items in place of unloaded data.
- *
- * @param key A unique identifier of an item in the data set.
- * @return Number of items in the data set before the item identified by {@code key}, or
- * {@link #COUNT_UNDEFINED}.
- *
- * @see #countItemsAfter(Key)
- */
- @WorkerThread
- public int countItemsBefore(@NonNull Key key) {
- return COUNT_UNDEFINED;
- }
-
- /**
- * Return the number of items that occur after the item uniquely identified by {@code key} in
- * the data set.
- * <p>
- * For example, if you're loading items sorted by ID, then this would return the total number of
- * items with ID greater than {@code key}.
- * <p>
- * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsBefore(Key)}, your
- * data source will not present placeholder null items in place of unloaded data.
- *
- * @param key A unique identifier of an item in the data set.
- * @return Number of items in the data set after the item identified by {@code key}, or
- * {@link #COUNT_UNDEFINED}.
- *
- * @see #countItemsBefore(Key)
- */
- @WorkerThread
- public int countItemsAfter(@NonNull Key key) {
- return COUNT_UNDEFINED;
- }
-
- @WorkerThread
- @Nullable
- public abstract List<Value> loadInitial(int pageSize);
-
- /**
- * Load list data after the specified item.
- * <p>
- * It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase the number loaded than reduce.
- *
- * @param currentEndKey Load items after this key. May be null on initial load, to indicate load
- * from beginning.
- * @param pageSize Suggested number of items to load.
- * @return List of items, starting after the specified item. Null if the data source is
- * no longer valid, and should not be queried again.
- */
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- @Nullable
- public abstract List<Value> loadAfter(@NonNull Key currentEndKey, int pageSize);
-
- /**
- * Load data before the currently loaded content, starting at the provided index,
- * in reverse-display order.
- * <p>
- * It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase the number loaded than reduce.
- * <p class="note"><strong>Note:</strong> Items returned from loadBefore <em>must</em> be in
- * reverse order from how they will be presented in the list. The first item in the return list
- * will be prepended immediately before the current beginning of the list. This is so that the
- * KeyedDataSource may return a different number of items from the requested {@code pageSize} by
- * shortening or lengthening the return list as it desires.
- * <p>
- *
- * @param currentBeginKey Load items before this key.
- * @param pageSize Suggested number of items to load.
- * @return List of items, in descending order, starting after the specified item. Null if the
- * data source is no longer valid, and should not be queried again.
- */
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- @Nullable
- public abstract List<Value> loadBefore(@NonNull Key currentBeginKey, int pageSize);
-
- @Nullable
- @Override
- Key getKey(int position, Value item) {
- if (item == null) {
- return null;
- }
- return getKey(item);
- }
}
diff --git a/paging/common/src/main/java/android/arch/paging/ListDataSource.java b/paging/common/src/main/java/android/arch/paging/ListDataSource.java
index d3a171e..b6f366a 100644
--- a/paging/common/src/main/java/android/arch/paging/ListDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/ListDataSource.java
@@ -16,10 +16,12 @@
package android.arch.paging;
+import android.support.annotation.NonNull;
+
import java.util.ArrayList;
import java.util.List;
-public class ListDataSource<T> extends TiledDataSource<T> {
+class ListDataSource<T> extends PositionalDataSource<T> {
private final List<T> mList;
public ListDataSource(List<T> list) {
@@ -27,13 +29,22 @@
}
@Override
- public int countItems() {
- return mList.size();
+ public void loadInitial(int requestedStartPosition, int requestedLoadSize, int pageSize,
+ @NonNull InitialLoadCallback<T> callback) {
+ final int totalCount = mList.size();
+
+ final int firstLoadPosition = computeFirstLoadPosition(
+ requestedStartPosition, requestedLoadSize, pageSize, totalCount);
+ final int firstLoadSize = Math.min(totalCount - firstLoadPosition, requestedLoadSize);
+
+ // for simplicity, we could return everything immediately,
+ // but we tile here since it's expected behavior
+ List<T> sublist = mList.subList(firstLoadPosition, firstLoadPosition + firstLoadSize);
+ callback.onResult(sublist, firstLoadPosition, totalCount);
}
@Override
- public List<T> loadRange(int startPosition, int count) {
- int endExclusive = Math.min(mList.size(), startPosition + count);
- return mList.subList(startPosition, endExclusive);
+ public void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback) {
+ callback.onResult(mList.subList(startPosition, startPosition + count));
}
}
diff --git a/paging/common/src/main/java/android/arch/paging/Page.java b/paging/common/src/main/java/android/arch/paging/Page.java
deleted file mode 100644
index e9890ed..0000000
--- a/paging/common/src/main/java/android/arch/paging/Page.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.List;
-
-/**
- * Immutable class representing a page of data loaded from a DataSource.
- * <p>
- * Optionally stores before/after keys for cases where they cannot be computed, but the DataSource
- * can provide them as part of loading a page.
- * <p>
- * A page's list must never be modified.
- */
-class Page<K, V> {
- @SuppressWarnings("WeakerAccess")
- @Nullable
- public final K beforeKey;
- @NonNull
- public final List<V> items;
- @SuppressWarnings("WeakerAccess")
- @Nullable
- public K afterKey;
-
- Page(@NonNull List<V> items) {
- this(null, items, null);
- }
-
- Page(@Nullable K beforeKey, @NonNull List<V> items, @Nullable K afterKey) {
- this.beforeKey = beforeKey;
- this.items = items;
- this.afterKey = afterKey;
- }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/PageResult.java b/paging/common/src/main/java/android/arch/paging/PageResult.java
index 55d5fb7..cf2216f 100644
--- a/paging/common/src/main/java/android/arch/paging/PageResult.java
+++ b/paging/common/src/main/java/android/arch/paging/PageResult.java
@@ -16,11 +16,31 @@
package android.arch.paging;
-import android.support.annotation.AnyThread;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.support.annotation.IntDef;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
-class PageResult<K, V> {
+import java.lang.annotation.Retention;
+import java.util.Collections;
+import java.util.List;
+
+class PageResult<T> {
+ @SuppressWarnings("unchecked")
+ private static final PageResult INVALID_RESULT =
+ new PageResult(Collections.EMPTY_LIST, 0);
+
+ @SuppressWarnings("unchecked")
+ static <T> PageResult<T> getInvalidResult() {
+ return INVALID_RESULT;
+ }
+
+
+ @Retention(SOURCE)
+ @IntDef({INIT, APPEND, PREPEND, TILE})
+ @interface ResultType {}
+
static final int INIT = 0;
// contiguous results
@@ -30,8 +50,8 @@
// non-contiguous, tile result
static final int TILE = 3;
- public final int type;
- public final Page<K, V> page;
+ @NonNull
+ public final List<T> page;
@SuppressWarnings("WeakerAccess")
public final int leadingNulls;
@SuppressWarnings("WeakerAccess")
@@ -39,26 +59,34 @@
@SuppressWarnings("WeakerAccess")
public final int positionOffset;
- PageResult(int type, Page<K, V> page, int leadingNulls, int trailingNulls, int positionOffset) {
- this.type = type;
- this.page = page;
+ PageResult(@NonNull List<T> list, int leadingNulls, int trailingNulls, int positionOffset) {
+ this.page = list;
this.leadingNulls = leadingNulls;
this.trailingNulls = trailingNulls;
this.positionOffset = positionOffset;
}
- PageResult(int type) {
- this.type = type;
- this.page = null;
+ PageResult(@NonNull List<T> list, int positionOffset) {
+ this.page = list;
this.leadingNulls = 0;
this.trailingNulls = 0;
- this.positionOffset = 0;
+ this.positionOffset = positionOffset;
}
- interface Receiver<K, V> {
- @AnyThread
- void postOnPageResult(@NonNull PageResult<K, V> pageResult);
+ @Override
+ public String toString() {
+ return "Result " + leadingNulls
+ + ", " + page
+ + ", " + trailingNulls
+ + ", offset " + positionOffset;
+ }
+
+ public boolean isInvalid() {
+ return this == INVALID_RESULT;
+ }
+
+ abstract static class Receiver<T> {
@MainThread
- void onPageResult(@NonNull PageResult<K, V> pageResult);
+ public abstract void onPageResult(@ResultType int type, @NonNull PageResult<T> pageResult);
}
}
diff --git a/paging/common/src/main/java/android/arch/paging/PagedList.java b/paging/common/src/main/java/android/arch/paging/PagedList.java
index f18e108..70d4075 100644
--- a/paging/common/src/main/java/android/arch/paging/PagedList.java
+++ b/paging/common/src/main/java/android/arch/paging/PagedList.java
@@ -17,6 +17,7 @@
package android.arch.paging;
import android.support.annotation.AnyThread;
+import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
@@ -51,7 +52,7 @@
* PagedList can present data for an unbounded, infinite scrolling list, or a very large but
* countable list. Use {@link Config} to control how many items a PagedList loads, and when.
* <p>
- * If you use {@link LivePagedListProvider} to get a
+ * If you use {@link LivePagedListBuilder} to get a
* {@link android.arch.lifecycle.LiveData}<PagedList>, it will initialize PagedLists on a
* background thread for you.
* <h4>Placeholders</h4>
@@ -88,9 +89,8 @@
* </ul>
* <p>
* Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
- * DataSource returns {@link DataSource#COUNT_UNDEFINED} from any item counting method, or if
- * {@code false} is passed to {@link Config.Builder#setEnablePlaceholders(boolean)} when building a
- * {@link Config}.
+ * DataSource does not count its data set in its initial load, or if {@code false} is passed to
+ * {@link Config.Builder#setEnablePlaceholders(boolean)} when building a {@link Config}.
*
* @param <T> The type of the entries in the list.
*/
@@ -104,7 +104,7 @@
@NonNull
final Config mConfig;
@NonNull
- final PagedStorage<?, T> mStorage;
+ final PagedStorage<T> mStorage;
int mLastLoad = 0;
T mLastItem = null;
@@ -123,7 +123,7 @@
protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
- PagedList(@NonNull PagedStorage<?, T> storage,
+ PagedList(@NonNull PagedStorage<T> storage,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@@ -162,7 +162,8 @@
if (dataSource.isContiguous() || !config.enablePlaceholders) {
if (!dataSource.isContiguous()) {
//noinspection unchecked
- dataSource = (DataSource<K, T>) ((TiledDataSource<T>) dataSource).getAsContiguous();
+ dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
+ .wrapAsContiguousWithoutPlaceholders();
}
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
return new ContiguousPagedList<>(contigDataSource,
@@ -172,7 +173,7 @@
config,
key);
} else {
- return new TiledPagedList<>((TiledDataSource<T>) dataSource,
+ return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
mainThreadExecutor,
backgroundThreadExecutor,
boundaryCallback,
@@ -184,18 +185,14 @@
/**
* Builder class for PagedList.
* <p>
- * DataSource, main thread and background executor, and Config must all be provided.
+ * DataSource, Config, main thread and background executor must all be provided.
* <p>
- * A valid PagedList may not be constructed without data, so building a PagedList queries
- * initial data from the data source. This is done because it's generally undesired to present a
- * PagedList with no data in it to the UI. It's better to present initial data, so that the UI
- * doesn't show an empty list, or placeholders for a few frames, just before showing initial
- * content.
+ * A PagedList queries initial data from its DataSource during construction, to avoid empty
+ * PagedLists being presented to the UI when possible. It's preferred to present initial data,
+ * so that the UI doesn't show an empty list, or placeholders for a few frames, just before
+ * showing initial content.
* <p>
- * Because PagedLists are initialized with data, PagedLists must be built on a background
- * thread.
- * <p>
- * {@link LivePagedListProvider} does this creation on a background thread automatically, if you
+ * {@link LivePagedListBuilder} does this creation on a background thread automatically, if you
* want to receive a {@code LiveData<PagedList<...>>}.
*
* @param <Key> Type of key used to load data from the DataSource.
@@ -203,26 +200,48 @@
*/
@SuppressWarnings("WeakerAccess")
public static class Builder<Key, Value> {
- private DataSource<Key, Value> mDataSource;
+ private final DataSource<Key, Value> mDataSource;
+ private final Config mConfig;
private Executor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
private BoundaryCallback mBoundaryCallback;
- private Config mConfig;
private Key mInitialKey;
/**
- * The source of data that the PagedList should load from.
- * @param dataSource Source of data for the PagedList.
+ * Create a PagedList.Builder with the provided {@link DataSource} and {@link Config}.
*
- * @return this
+ * @param dataSource DataSource the PagedList will load from.
+ * @param config Config that defines how the PagedList loads data from its DataSource.
*/
- @NonNull
- public Builder<Key, Value> setDataSource(@NonNull DataSource<Key, Value> dataSource) {
+ public Builder(@NonNull DataSource<Key, Value> dataSource, @NonNull Config config) {
+ //noinspection ConstantConditions
+ if (dataSource == null) {
+ throw new IllegalArgumentException("DataSource may not be null");
+ }
+ //noinspection ConstantConditions
+ if (config == null) {
+ throw new IllegalArgumentException("Config may not be null");
+ }
mDataSource = dataSource;
- return this;
+ mConfig = config;
}
/**
+ * Create a PagedList.Builder with the provided {@link DataSource} and page size.
+ * <p>
+ * This method is a convenience for:
+ * <pre>
+ * PagedList.Builder(dataSource,
+ * new PagedList.Config.Builder().setPageSize(pageSize).build());
+ * </pre>
+ *
+ * @param dataSource DataSource the PagedList will load from.
+ * @param pageSize Config that defines how the PagedList loads data from its DataSource.
+ */
+ public Builder(@NonNull DataSource<Key, Value> dataSource, int pageSize) {
+ this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build());
+ }
+ /**
* The executor defining where main/UI thread for page loading updates.
*
* @param mainThreadExecutor Executor for main/UI thread to receive {@link Callback} calls.
@@ -250,6 +269,15 @@
return this;
}
+ /**
+ * The BoundaryCallback for out of data events.
+ * <p>
+ * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load.
+ *
+ * @param boundaryCallback BoundaryCallback for listening to out-of-data events.
+ * @return this
+ */
+ @SuppressWarnings("unused")
@NonNull
public Builder<Key, Value> setBoundaryCallback(
@Nullable BoundaryCallback boundaryCallback) {
@@ -257,20 +285,6 @@
return this;
}
-
- /**
- * The Config defining how the PagedList should load from the DataSource.
- *
- * @param config The config that will define how the PagedList loads from the DataSource.
- *
- * @return this
- */
- @NonNull
- public Builder<Key, Value> setConfig(@NonNull Config config) {
- mConfig = config;
- return this;
- }
-
/**
* Sets the initial key the DataSource should load around as part of initialization.
*
@@ -286,8 +300,21 @@
/**
* Creates a {@link PagedList} with the given parameters.
* <p>
- * This call will initial data and perform any counting needed to initialize the PagedList,
- * therefore it should only be called on a worker thread.
+ * This call will dispatch the {@link DataSource}'s loadInitial method immediately. If a
+ * DataSource posts all of its work (e.g. to a network thread), the PagedList will
+ * be immediately created as empty, and grow to its initial size when the initial load
+ * completes.
+ * <p>
+ * If the DataSource implements its load synchronously, doing the load work immediately in
+ * the loadInitial method, the PagedList will block on that load before completing
+ * construction. In this case, use a background thread to create a PagedList.
+ * <p>
+ * It's fine to create a PagedList with an async DataSource on the main thread, such as in
+ * the constructor of a ViewModel. An async network load won't block the initialLoad
+ * function. For a synchronous DataSource such as one created from a Room database, a
+ * {@code LiveData<PagedList>} can be safely constructed with {@link LivePagedListBuilder}
+ * on the main thread, since actual construction work is deferred, and done on a background
+ * thread.
* <p>
* While build() will always return a PagedList, it's important to note that the PagedList
* initial load may fail to acquire data from the DataSource. This can happen for example if
@@ -300,18 +327,13 @@
@WorkerThread
@NonNull
public PagedList<Value> build() {
- if (mDataSource == null) {
- throw new IllegalArgumentException("DataSource required");
- }
+ // TODO: define defaults, once they can be used in module without android dependency
if (mMainThreadExecutor == null) {
throw new IllegalArgumentException("MainThreadExecutor required");
}
if (mBackgroundThreadExecutor == null) {
throw new IllegalArgumentException("BackgroundThreadExecutor required");
}
- if (mConfig == null) {
- throw new IllegalArgumentException("Config required");
- }
//noinspection unchecked
return PagedList.create(
@@ -451,13 +473,11 @@
// safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
if (begin) {
//noinspection ConstantConditions
- mBoundaryCallback.onItemAtFrontLoaded(
- snapshot(), mStorage.getFirstLoadedItem(), mStorage.size());
+ mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem());
}
if (end) {
//noinspection ConstantConditions
- mBoundaryCallback.onItemAtEndLoaded(
- snapshot(), mStorage.getLastLoadedItem(), mStorage.size());
+ mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem());
}
}
@@ -501,7 +521,6 @@
if (isImmutable()) {
return this;
}
-
return new SnapshotPagedList<>(this);
}
@@ -522,7 +541,7 @@
* <p>
* When a PagedList is invalidated, you can pass the key returned by this function to initialize
* the next PagedList. This ensures (depending on load times) that the next PagedList that
- * arrives will have data that overlaps. If you use {@link LivePagedListProvider}, it will do
+ * arrives will have data that overlaps. If you use {@link LivePagedListBuilder}, it will do
* this for you.
*
* @return Key of position most recently passed to {@link #loadAround(int)}.
@@ -556,8 +575,8 @@
/**
* Position offset of the data in the list.
* <p>
- * If data is supplied by a {@link TiledDataSource}, the item returned from <code>get(i)</code>
- * has a position of <code>i + getPositionOffset()</code>.
+ * If data is supplied by a {@link PositionalDataSource}, the item returned from
+ * <code>get(i)</code> has a position of <code>i + getPositionOffset()</code>.
* <p>
* If the DataSource is a {@link KeyedDataSource}, and thus doesn't use positions, returns 0.
*/
@@ -583,15 +602,25 @@
* GC'd.
*
* @param previousSnapshot Snapshot previously captured from this List, or null.
- * @param callback Callback to dispatch to.
+ * @param callback LoadCallback to dispatch to.
* @see #removeWeakCallback(Callback)
*/
@SuppressWarnings("WeakerAccess")
public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
if (previousSnapshot != null && previousSnapshot != this) {
- PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
- //noinspection unchecked
- dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
+
+ if (previousSnapshot.isEmpty()) {
+ if (!mStorage.isEmpty()) {
+ // If snapshot is empty, diff is trivial - just notify number new items.
+ // Note: occurs in async init, when snapshot taken before init page arrives
+ callback.onInserted(0, mStorage.size());
+ }
+ } else {
+ PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
+
+ //noinspection unchecked
+ dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
+ }
}
// first, clean up any empty weak refs
@@ -608,7 +637,7 @@
/**
* Removes a previously added callback.
*
- * @param callback Callback, previously added.
+ * @param callback LoadCallback, previously added.
* @see #addWeakCallback(List, Callback)
*/
@SuppressWarnings("WeakerAccess")
@@ -645,6 +674,14 @@
}
}
+
+
+ /**
+ * Dispatch updates since the non-empty snapshot was taken.
+ *
+ * @param snapshot Non-empty snapshot.
+ * @param callback LoadCallback for updates that have occurred since snapshot.
+ */
abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot,
@NonNull Callback callback);
@@ -826,13 +863,6 @@
* This value is typically larger than page size, so on first load data there's a large
* enough range of content loaded to cover small scrolls.
* <p>
- * If used with a {@link TiledDataSource}, this value is rounded to the nearest number
- * of pages, with a minimum of two pages, and loaded with a single call to
- * {@link TiledDataSource#loadRange(int, int)}.
- * <p>
- * If used with a {@link KeyedDataSource}, this value will be passed to
- * {@link KeyedDataSource#loadInitial(int)}.
- * <p>
* If not set, defaults to three times page size.
*
* @param initialLoadSizeHint Number of items to load while initializing the PagedList.
@@ -873,13 +903,43 @@
}
/**
- * WIP API for load-more-into-local-storage callbacks
+ * Signals when a PagedList has reached the end of available data.
+ * <p>
+ * This can be used to implement paging from the network into a local database - when the
+ * database has no more data to present, a BoundaryCallback can be used to fetch more data.
+ * <p>
+ * If an instance is shared across multiple PagedLists (e.g. when passed to
+ * {@link LivePagedListBuilder#setBoundaryCallback}), the callbacks may be issued multiple
+ * times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should
+ * avoid triggering it again while the load is ongoing.
+ *
+ * @param <T> Type loaded by the PagedList.
*/
+ @MainThread
public abstract static class BoundaryCallback<T> {
+ /**
+ * Called when zero items are returned from an initial load of the PagedList's data source.
+ */
public abstract void onZeroItemsLoaded();
- public abstract void onItemAtFrontLoaded(@NonNull List<T> pagedListSnapshot,
- @NonNull T itemAtFront, int pagedListSize);
- public abstract void onItemAtEndLoaded(@NonNull List<T> pagedListSnapshot,
- @NonNull T itemAtEnd, int pagedListSize);
+
+ /**
+ * Called when the item at the front of the PagedList has been loaded, and access has
+ * occurred within {@link Config#prefetchDistance} of it.
+ * <p>
+ * No more data will be prepended to the PagedList before this item.
+ *
+ * @param itemAtFront The first item of PagedList
+ */
+ public abstract void onItemAtFrontLoaded(@NonNull T itemAtFront);
+
+ /**
+ * Called when the item at the end of the PagedList has been loaded, and access has
+ * occurred within {@link Config#prefetchDistance} of it.
+ * <p>
+ * No more data will be appended to the PagedList after this item.
+ *
+ * @param itemAtEnd The first item of PagedList
+ */
+ public abstract void onItemAtEndLoaded(@NonNull T itemAtEnd);
}
}
diff --git a/paging/common/src/main/java/android/arch/paging/PagedStorage.java b/paging/common/src/main/java/android/arch/paging/PagedStorage.java
index b857462..d4531d3 100644
--- a/paging/common/src/main/java/android/arch/paging/PagedStorage.java
+++ b/paging/common/src/main/java/android/arch/paging/PagedStorage.java
@@ -17,13 +17,21 @@
package android.arch.paging;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import java.util.AbstractList;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-final class PagedStorage<K, V> extends AbstractList<V> {
+final class PagedStorage<T> extends AbstractList<T> {
+ /**
+ * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item
+ * in that position is already loading. We use a singleton placeholder list that is distinct
+ * from Collections.EMPTY_LIST for safety.
+ */
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ private static final List PLACEHOLDER_LIST = new ArrayList();
+
// Always set
private int mLeadingNullCount;
/**
@@ -37,7 +45,7 @@
* Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true.
* mPages may have nulls, or placeholder (empty) pages while content is loading.
*/
- private final ArrayList<Page<K, V>> mPages;
+ private final ArrayList<List<T>> mPages;
private int mTrailingNullCount;
private int mPositionOffset;
@@ -53,9 +61,6 @@
private int mNumberPrepended;
private int mNumberAppended;
- // only used in tiling case
- private Page<K, V> mPlaceholderPage;
-
PagedStorage() {
mLeadingNullCount = 0;
mPages = new ArrayList<>();
@@ -67,12 +72,12 @@
mNumberAppended = 0;
}
- PagedStorage(int leadingNulls, Page<K, V> page, int trailingNulls) {
+ PagedStorage(int leadingNulls, List<T> page, int trailingNulls) {
this();
init(leadingNulls, page, trailingNulls, 0);
}
- private PagedStorage(PagedStorage<K, V> other) {
+ private PagedStorage(PagedStorage<T> other) {
mLeadingNullCount = other.mLeadingNullCount;
mPages = new ArrayList<>(other.mPages);
mTrailingNullCount = other.mTrailingNullCount;
@@ -81,40 +86,37 @@
mPageSize = other.mPageSize;
mNumberPrepended = other.mNumberPrepended;
mNumberAppended = other.mNumberAppended;
-
- // preserve placeholder page so we can locate placeholder pages if needed later
- mPlaceholderPage = other.mPlaceholderPage;
}
- PagedStorage<K, V> snapshot() {
+ PagedStorage<T> snapshot() {
return new PagedStorage<>(this);
}
- private void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset) {
+ private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
mLeadingNullCount = leadingNulls;
mPages.clear();
mPages.add(page);
mTrailingNullCount = trailingNulls;
mPositionOffset = positionOffset;
- mStorageCount = page.items.size();
+ mStorageCount = page.size();
// initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
// even if it will break if nulls convert.
- mPageSize = page.items.size();
+ mPageSize = page.size();
mNumberPrepended = 0;
mNumberAppended = 0;
}
- void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset,
+ void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
@NonNull Callback callback) {
init(leadingNulls, page, trailingNulls, positionOffset);
callback.onInitialized(size());
}
@Override
- public V get(int i) {
+ public T get(int i) {
if (i < 0 || i >= size()) {
throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
}
@@ -138,7 +140,7 @@
pageInternalIndex = localIndex;
final int localPageCount = mPages.size();
for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
- int pageSize = mPages.get(localPageIndex).items.size();
+ int pageSize = mPages.get(localPageIndex).size();
if (pageSize > pageInternalIndex) {
// stop, found the page
break;
@@ -147,12 +149,12 @@
}
}
- Page<?, V> page = mPages.get(localPageIndex);
- if (page == null || page.items.size() == 0) {
+ List<T> page = mPages.get(localPageIndex);
+ if (page == null || page.size() == 0) {
// can only occur in tiled case, with untouched inner/placeholder pages
return null;
}
- return page.items.get(pageInternalIndex);
+ return page.get(pageInternalIndex);
}
/**
@@ -207,8 +209,8 @@
int total = mLeadingNullCount;
final int pageCount = mPages.size();
for (int i = 0; i < pageCount; i++) {
- Page page = mPages.get(i);
- if (page != null && page != mPlaceholderPage) {
+ List page = mPages.get(i);
+ if (page != null && page != PLACEHOLDER_LIST) {
break;
}
total += mPageSize;
@@ -219,8 +221,8 @@
int computeTrailingNulls() {
int total = mTrailingNullCount;
for (int i = mPages.size() - 1; i >= 0; i--) {
- Page page = mPages.get(i);
- if (page != null && page != mPlaceholderPage) {
+ List page = mPages.get(i);
+ if (page != null && page != PLACEHOLDER_LIST) {
break;
}
total += mPageSize;
@@ -230,21 +232,21 @@
// ---------------- Contiguous API -------------------
- V getFirstLoadedItem() {
+ T getFirstLoadedItem() {
// safe to access first page's first item here:
// If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
- return mPages.get(0).items.get(0);
+ return mPages.get(0).get(0);
}
- V getLastLoadedItem() {
+ T getLastLoadedItem() {
// safe to access last page's last item here:
// If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
- Page<K, V> page = mPages.get(mPages.size() - 1);
- return page.items.get(page.items.size() - 1);
+ List<T> page = mPages.get(mPages.size() - 1);
+ return page.get(page.size() - 1);
}
- public void prependPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
- final int count = page.items.size();
+ void prependPage(@NonNull List<T> page, @NonNull Callback callback) {
+ final int count = page.size();
if (count == 0) {
// Nothing returned from source, stop loading in this direction
return;
@@ -274,8 +276,8 @@
callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
}
- public void appendPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
- final int count = page.items.size();
+ void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
+ final int count = page.size();
if (count == 0) {
// Nothing returned from source, stop loading in this direction
return;
@@ -284,7 +286,7 @@
if (mPageSize > 0) {
// if the previous page was smaller than mPageSize,
// or if this page is larger than the previous, disable tiling
- if (mPages.get(mPages.size() - 1).items.size() != mPageSize
+ if (mPages.get(mPages.size() - 1).size() != mPageSize
|| count > mPageSize) {
mPageSize = -1;
}
@@ -306,8 +308,30 @@
// ------------------ Non-Contiguous API (tiling required) ----------------------
- public void insertPage(int position, @NonNull Page<K, V> page, Callback callback) {
- final int newPageSize = page.items.size();
+ void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
+ int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
+
+ int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize;
+ for (int i = 0; i < pageCount; i++) {
+ int beginInclusive = i * pageSize;
+ int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize);
+
+ List<T> sublist = multiPageList.subList(beginInclusive, endExclusive);
+
+ if (i == 0) {
+ // Trailing nulls for first page includes other pages in multiPageList
+ int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size();
+ init(leadingNulls, sublist, initialTrailingNulls, positionOffset);
+ } else {
+ int insertPosition = leadingNulls + beginInclusive;
+ insertPage(insertPosition, sublist, null);
+ }
+ }
+ callback.onInitialized(size());
+ }
+
+ public void insertPage(int position, @NonNull List<T> page, @Nullable Callback callback) {
+ final int newPageSize = page.size();
if (newPageSize != mPageSize) {
// differing page size is OK in 2 cases, when the page is being added:
// 1) to the end (in which case, ignore new smaller size)
@@ -334,22 +358,15 @@
int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
- Page<K, V> oldPage = mPages.get(localPageIndex);
- if (oldPage != null && oldPage != mPlaceholderPage) {
+ List<T> oldPage = mPages.get(localPageIndex);
+ if (oldPage != null && oldPage != PLACEHOLDER_LIST) {
throw new IllegalArgumentException(
"Invalid position " + position + ": data already loaded");
}
mPages.set(localPageIndex, page);
- callback.onPageInserted(position, page.items.size());
- }
-
- private Page<K, V> getPlaceholderPage() {
- if (mPlaceholderPage == null) {
- @SuppressWarnings("unchecked")
- List<V> list = Collections.emptyList();
- mPlaceholderPage = new Page<>(null, list, null);
+ if (callback != null) {
+ callback.onPageInserted(position, page.size());
}
- return mPlaceholderPage;
}
private void allocatePageRange(final int minimumPage, final int maximumPage) {
@@ -399,7 +416,8 @@
for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
int localPageIndex = pageIndex - leadingNullPages;
if (mPages.get(localPageIndex) == null) {
- mPages.set(localPageIndex, getPlaceholderPage());
+ //noinspection unchecked
+ mPages.set(localPageIndex, PLACEHOLDER_LIST);
callback.onPagePlaceholderInserted(pageIndex);
}
}
@@ -414,9 +432,9 @@
return false;
}
- Page<K, V> page = mPages.get(index - leadingNullPages);
+ List<T> page = mPages.get(index - leadingNullPages);
- return page != null && page != mPlaceholderPage;
+ return page != null && page != PLACEHOLDER_LIST;
}
@Override
diff --git a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
index fa2932a..5dd3a83 100644
--- a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 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.
@@ -20,115 +20,139 @@
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
-import java.util.List;
+import java.util.Collections;
/**
- * Incremental data loader for paging positional content, where content can be loaded based on its
- * integer position.
+ * Position-based data loader for a fixed-size, countable data set, supporting loads at arbitrary
+ * positions.
* <p>
- * Use PositionalDataSource if you only need position as input for item loading - if for example,
- * you're asking the backend for items at positions 10 through 20, or using a limit/offset database
- * query to load items at query position 10 through 20.
+ * Extend PositionalDataSource if you can support counting your data set, and loading based on
+ * position information.
* <p>
- * Implement a DataSource using PositionalDataSource if position is the only information you need to
- * load items.
+ * Note that unless {@link PagedList.Config#enablePlaceholders placeholders are disabled}
+ * PositionalDataSource requires counting the size of the dataset. This allows pages to be tiled in
+ * at arbitrary, non-contiguous locations based upon what the user observes in a {@link PagedList}.
* <p>
- * Note that {@link BoundedDataSource} provides a simpler API for positional loading, if your
- * backend or data store doesn't require
- * <p>
- * @param <Value> Value type of items being loaded by the DataSource.
+ * Room can generate a Factory of PositionalDataSources for you:
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC")
+ * public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
+ * }</pre>
+ *
+ * @param <T> Type of items being loaded by the PositionalDataSource.
*/
-abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
+public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
/**
- * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
- *
- * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
- * if difficult or undesired to compute.
- */
- public int countItems() {
- return COUNT_UNDEFINED;
- }
-
- @Nullable
- @Override
- List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) {
- return loadAfter(currentEndIndex + 1, pageSize);
- }
-
- @Nullable
- @Override
- List<Value> loadBeforeImpl(int currentBeginIndex, @NonNull Value currentBeginItem,
- int pageSize) {
- return loadBefore(currentBeginIndex - 1, pageSize);
- }
-
- @Override
- void loadInitial(Integer position, int initialLoadSize, boolean enablePlaceholders,
- @NonNull PageResult.Receiver<Integer, Value> receiver) {
-
- final int convertPosition = position == null ? 0 : position;
- final int loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2));
-
- int count = COUNT_UNDEFINED;
- if (enablePlaceholders) {
- count = countItems();
- }
- List<Value> data = loadAfter(loadPosition, initialLoadSize);
-
- if (data == null) {
- receiver.onPageResult(new PageResult<Integer, Value>(PageResult.INIT));
- return;
- }
-
- final boolean uncounted = count == COUNT_UNDEFINED;
- int leadingNullCount = uncounted ? 0 : loadPosition;
- int trailingNullCount = uncounted ? 0 : count - leadingNullCount - data.size();
- int positionOffset = uncounted ? loadPosition : 0;
-
- receiver.onPageResult(new PageResult<>(
- PageResult.INIT,
- new Page<Integer, Value>(data),
- leadingNullCount,
- trailingNullCount,
- positionOffset));
- }
-
- /**
- * Load data after currently loaded content, starting at the provided index.
+ * Load initial list data.
* <p>
- * It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase the number loaded than reduce.
+ * This method is called to load the initial page(s) from the DataSource.
+ * <p>
+ * Result list must be a multiple of pageSize to enable efficient tiling.
*
- * @param startIndex Load items starting at this index.
- * @param pageSize Suggested number of items to load.
- * @return List of items, starting at position currentEndIndex + 1. Null if the data source is
- * no longer valid, and should not be queried again.
+ * @param requestedStartPosition Initial load position requested. Note that this may not be
+ * within the bounds of your data set, it should be corrected
+ * before you make your query.
+ * @param requestedLoadSize Requested number of items to load. Note that this may be larger than
+ * available data.
+ * @param pageSize Defines page size acceptable for return values. List of items passed to the
+ * callback must be an integer multiple of page size.
+ * @param callback DataSource.InitialLoadCallback that receives initial load data, including
+ * position and total data set size.
*/
@WorkerThread
- @Nullable
- public abstract List<Value> loadAfter(int startIndex, int pageSize);
+ public abstract void loadInitial(int requestedStartPosition, int requestedLoadSize,
+ int pageSize, @NonNull InitialLoadCallback<T> callback);
/**
- * Load data before the currently loaded content, starting at the provided index.
+ * Called to load a range of data from the DataSource.
* <p>
- * It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase the number loaded than reduce.
+ * This method is called to load additional pages from the DataSource after the
+ * InitialLoadCallback passed to loadInitial has initialized a PagedList.
+ * <p>
+ * Unlike {@link #loadInitial(int, int, int, InitialLoadCallback)}, this method must return the
+ * number of items requested, at the position requested.
*
- * @param startIndex Load items, starting at this index.
- * @param pageSize Suggested number of items to load.
- * @return List of items, in descending order, starting at position currentBeginIndex - 1. Null
- * if the data source is no longer valid, and should not be queried again.
+ * @param startPosition Initial load position.
+ * @param count Number of items to load.
+ * @param callback DataSource.LoadCallback that receives loaded data.
*/
@WorkerThread
- @Nullable
- public abstract List<Value> loadBefore(int startIndex, int pageSize);
+ public abstract void loadRange(int startPosition, int count,
+ @NonNull LoadCallback<T> callback);
@Override
- Integer getKey(int position, Value item) {
- if (position < 0) {
- return null;
+ boolean isContiguous() {
+ return false;
+ }
+
+ @NonNull
+ ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() {
+ return new ContiguousWithoutPlaceholdersWrapper<>(this);
+ }
+
+ static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) {
+ int roundedPageStart = Math.round(position / pageSize) * pageSize;
+
+ // maximum start pos is that which will encompass end of list
+ int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize;
+ roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
+
+ // minimum start position is 0
+ roundedPageStart = Math.max(0, roundedPageStart);
+
+ return roundedPageStart;
+ }
+
+ @SuppressWarnings("deprecation")
+ static class ContiguousWithoutPlaceholdersWrapper<Value>
+ extends ContiguousDataSource<Integer, Value> {
+
+ @NonNull
+ final PositionalDataSource<Value> mPositionalDataSource;
+
+ ContiguousWithoutPlaceholdersWrapper(
+ @NonNull PositionalDataSource<Value> positionalDataSource) {
+ mPositionalDataSource = positionalDataSource;
}
- return position;
+
+ @Override
+ public void loadInitial(@Nullable Integer position, int initialLoadSize,
+ boolean enablePlaceholders, @NonNull InitialLoadCallback<Value> callback) {
+ final int convertPosition = position == null ? 0 : position;
+
+ // Note enablePlaceholders will be false here, but we don't have a way to communicate
+ // this to PositionalDataSource. This is fine, because only the list and its position
+ // offset will be consumed by the InitialLoadCallback.
+ mPositionalDataSource.loadInitial(
+ convertPosition, initialLoadSize, initialLoadSize, callback);
+ }
+
+ @Override
+ void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ @NonNull LoadCallback<Value> callback) {
+ int startIndex = currentEndIndex + 1;
+ mPositionalDataSource.loadRange(startIndex, pageSize, callback);
+ }
+
+ @Override
+ void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ @NonNull LoadCallback<Value> callback) {
+ int startIndex = currentBeginIndex - 1;
+ if (startIndex < 0) {
+ callback.onResult(Collections.<Value>emptyList());
+ } else {
+ int loadSize = Math.min(pageSize, startIndex + 1);
+ startIndex = startIndex - loadSize + 1;
+ mPositionalDataSource.loadRange(startIndex, loadSize, callback);
+ }
+ }
+
+ @Override
+ Integer getKey(int position, Value item) {
+ return position;
+ }
}
}
diff --git a/paging/common/src/main/java/android/arch/paging/TiledDataSource.java b/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
index 0ea9428..27aeda0 100644
--- a/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
@@ -16,84 +16,24 @@
package android.arch.paging;
-import android.support.annotation.Nullable;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.util.Collections;
import java.util.List;
/**
- * Position-based data loader for fixed size, arbitrary positioned loading.
- * <p>
- * Extend TiledDataSource if you want to load arbitrary pages based solely on position information,
- * and can generate pages of a provided fixed size.
- * <p>
- * Room can generate a TiledDataSource for you:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC")
- * public abstract TiledDataSource<User> loadUsersByAgeDesc();
- * }</pre>
+ * @param <T> Type loaded by the TiledDataSource.
*
- * Under the hood, Room will generate code equivalent to the below, using a limit/offset SQL query:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT COUNT(*) from user")
- * public abstract Integer getUserCount();
- *
- * {@literal @}Query("SELECT * from user ORDER BY mName DESC LIMIT :limit OFFSET :offset")
- * public abstract List<User> userNameLimitOffset(int limit, int offset);
- * }
- *
- * public class OffsetUserQueryDataSource extends TiledDataSource<User> {
- * private MyDatabase mDb;
- * private final UserDao mUserDao;
- * {@literal @}SuppressWarnings("FieldCanBeLocal")
- * private final InvalidationTracker.Observer mObserver;
- *
- * public OffsetUserQueryDataSource(MyDatabase db) {
- * mDb = db;
- * mUserDao = db.getUserDao();
- * mObserver = new InvalidationTracker.Observer("user") {
- * {@literal @}Override
- * public void onInvalidated({@literal @}NonNull Set<String> tables) {
- * // the user table has been invalidated, invalidate the DataSource
- * invalidate();
- * }
- * };
- * db.getInvalidationTracker().addWeakObserver(mObserver);
- * }
- *
- * {@literal @}Override
- * public boolean isInvalid() {
- * mDb.getInvalidationTracker().refreshVersionsSync();
- * return super.isInvalid();
- * }
- *
- * {@literal @}Override
- * public int countItems() {
- * return mUserDao.getUserCount();
- * }
- *
- * {@literal @}Override
- * public List<User> loadRange(int startPosition, int loadCount) {
- * return mUserDao.userNameLimitOffset(loadCount, startPosition);
- * }
- * }</pre>
- *
- * @param <Type> Type of items being loaded by the TiledDataSource.
+ * @deprecated Use {@link PositionalDataSource}
+ * @hide
*/
-public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
+@SuppressWarnings("DeprecatedIsStillUsed")
+@Deprecated
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class TiledDataSource<T> extends PositionalDataSource<T> {
- private int mItemCount;
-
- /**
- * Number of items that this DataSource can provide in total.
- *
- * @return Number of items this DataSource can provide. Must be <code>0</code> or greater.
- */
@WorkerThread
public abstract int countItems();
@@ -102,111 +42,39 @@
return false;
}
- /**
- * Called to load items at from the specified position range.
- * <p>
- * This method must return a list of requested size, unless at the end of list. Fixed size pages
- * enable TiledDataSource to navigate tiles efficiently, and quickly accesss any position in the
- * data set.
- * <p>
- * If a list of a different size is returned, but it is not the last list in the data set based
- * on the return value from {@link #countItems()}, an exception will be thrown.
- *
- * @param startPosition Index of first item to load.
- * @param count Number of items to load.
- * @return List of loaded items, of the requested length unless at end of list. Null if the
- * DataSource is no longer valid, and should not be queried again.
- */
@WorkerThread
- public abstract List<Type> loadRange(int startPosition, int count);
+ public abstract List<T> loadRange(int startPosition, int count);
- /**
- * blocking, and splits pages
- */
- void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount,
- PageResult.Receiver<Integer, Type> receiver) {
- mItemCount = itemCount;
-
- if (itemCount == 0) {
- // no data to load, just immediately return empty
- receiver.onPageResult(new PageResult<>(
- PageResult.INIT, new Page<Integer, Type>(Collections.<Type>emptyList()),
- 0, 0, startPosition));
+ @Override
+ public void loadInitial(int requestedStartPosition, int requestedLoadSize, int pageSize,
+ @NonNull InitialLoadCallback<T> callback) {
+ int totalCount = countItems();
+ if (totalCount == 0) {
+ callback.onResult(Collections.<T>emptyList());
return;
}
- List<Type> list = loadRangeWrapper(startPosition, count);
+ // bound the size requested, based on known count
+ final int firstLoadPosition = computeFirstLoadPosition(
+ requestedStartPosition, requestedLoadSize, pageSize, totalCount);
+ final int firstLoadSize = Math.min(totalCount - firstLoadPosition, requestedLoadSize);
- count = Math.min(count, itemCount - startPosition);
-
- if (list == null) {
- // invalid data, pass to receiver
- receiver.onPageResult(new PageResult<Integer, Type>(
- PageResult.INIT, null, 0, 0, startPosition));
- return;
- }
-
- if (list.size() != count) {
- throw new IllegalStateException("Invalid list, requested size: " + count
- + ", returned size: " + list.size());
- }
-
- // emit the results as multiple pages
- int pageCount = (count + (pageSize - 1)) / pageSize;
- for (int i = 0; i < pageCount; i++) {
- int beginInclusive = i * pageSize;
- int endExclusive = Math.min(count, (i + 1) * pageSize);
-
- Page<Integer, Type> page = new Page<>(list.subList(beginInclusive, endExclusive));
-
- int leadingNulls = startPosition + beginInclusive;
- int trailingNulls = itemCount - leadingNulls - page.items.size();
- receiver.onPageResult(new PageResult<>(
- PageResult.INIT, page, leadingNulls, trailingNulls, 0));
- }
- }
-
- void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) {
- List<Type> list = loadRangeWrapper(startPosition, count);
-
- Page<Integer, Type> page = null;
- int trailingNulls = mItemCount - startPosition;
-
+ // convert from legacy behavior
+ List<T> list = loadRange(firstLoadPosition, firstLoadSize);
if (list != null) {
- page = new Page<Integer, Type>(list);
- trailingNulls -= list.size();
+ callback.onResult(list, firstLoadPosition, totalCount);
+ } else {
+ invalidate();
}
- receiver.postOnPageResult(new PageResult<>(
- PageResult.TILE, page, startPosition, trailingNulls, 0));
}
- private List<Type> loadRangeWrapper(int startPosition, int count) {
- if (isInvalid()) {
- return null;
- }
- List<Type> list = loadRange(startPosition, count);
- if (isInvalid()) {
- return null;
- }
- return list;
- }
-
- ContiguousDataSource<Integer, Type> getAsContiguous() {
- return new TiledAsBoundedDataSource<>(this);
- }
-
- static class TiledAsBoundedDataSource<Value> extends BoundedDataSource<Value> {
- final TiledDataSource<Value> mTiledDataSource;
-
- TiledAsBoundedDataSource(TiledDataSource<Value> tiledDataSource) {
- mTiledDataSource = tiledDataSource;
- }
-
- @WorkerThread
- @Nullable
- @Override
- public List<Value> loadRange(int startPosition, int loadCount) {
- return mTiledDataSource.loadRange(startPosition, loadCount);
+ @Override
+ public void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback) {
+ List<T> list = loadRange(startPosition, count);
+ if (list != null) {
+ callback.onResult(list);
+ } else {
+ invalidate();
}
}
}
diff --git a/paging/common/src/main/java/android/arch/paging/TiledPagedList.java b/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
index 76bb682..652b489 100644
--- a/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
+++ b/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
@@ -25,32 +25,16 @@
class TiledPagedList<T> extends PagedList<T>
implements PagedStorage.Callback {
+ private final PositionalDataSource<T> mDataSource;
- private final TiledDataSource<T> mDataSource;
-
- @SuppressWarnings("unchecked")
- private final PagedStorage<Integer, T> mKeyedStorage = (PagedStorage<Integer, T>) mStorage;
-
- private final PageResult.Receiver<Integer, T> mReceiver =
- new PageResult.Receiver<Integer, T>() {
- @AnyThread
- @Override
- public void postOnPageResult(@NonNull final PageResult<Integer, T> pageResult) {
- // NOTE: if we're already on main thread, this can delay page receive by a frame
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- onPageResult(pageResult);
- }
- });
- }
-
+ private PageResult.Receiver<T> mReceiver = new PageResult.Receiver<T>() {
// Creation thread for initial synchronous load, otherwise main thread
// Safe to access main thread only state - no other thread has reference during construction
@AnyThread
@Override
- public void onPageResult(@NonNull PageResult<Integer, T> pageResult) {
- if (pageResult.page == null) {
+ public void onPageResult(@PageResult.ResultType int type,
+ @NonNull PageResult<T> pageResult) {
+ if (pageResult.isInvalid()) {
detach();
return;
}
@@ -61,60 +45,63 @@
}
if (mStorage.getPageCount() == 0) {
- mKeyedStorage.init(
+ mStorage.initAndSplit(
pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
- pageResult.positionOffset, TiledPagedList.this);
+ pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this);
} else {
- mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page,
+ mStorage.insertPage(pageResult.positionOffset, pageResult.page,
TiledPagedList.this);
}
if (mBoundaryCallback != null) {
boolean deferEmpty = mStorage.size() == 0;
- boolean deferBegin = !deferEmpty && pageResult.leadingNulls == 0;
- boolean deferEnd = !deferEmpty && pageResult.trailingNulls == 0;
+ boolean deferBegin = !deferEmpty
+ && pageResult.leadingNulls == 0
+ && pageResult.positionOffset == 0;
+ int size = size();
+ boolean deferEnd = !deferEmpty
+ && ((type == PageResult.INIT && pageResult.trailingNulls == 0)
+ || (type == PageResult.TILE
+ && pageResult.positionOffset
+ == (size - size % mConfig.pageSize)));
deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
}
}
};
@WorkerThread
- TiledPagedList(@NonNull TiledDataSource<T> dataSource,
+ TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
int position) {
- super(new PagedStorage<Integer, T>(), mainThreadExecutor, backgroundThreadExecutor,
+ super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
final int pageSize = mConfig.pageSize;
+ mLastLoad = position;
- final int itemCount = mDataSource.countItems();
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ final int firstLoadSize =
+ (Math.max(Math.round(mConfig.initialLoadSizeHint / pageSize), 2)) * pageSize;
- final int firstLoadSize = Math.min(itemCount,
- (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize);
- final int firstLoadPosition = computeFirstLoadPosition(
- position, firstLoadSize, pageSize, itemCount);
+ final int idealStart = position - firstLoadSize / 2;
+ final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize);
- mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize,
- itemCount, mReceiver);
- }
+ DataSource.InitialLoadCallback<T> callback = new DataSource.InitialLoadCallback<>(
+ DataSource.LOAD_COUNT_REQUIRED_TILED,
+ mConfig.pageSize, mDataSource, mReceiver);
+ mDataSource.loadInitial(roundedPageStart, firstLoadSize, pageSize, callback);
- static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) {
- int idealStart = position - firstLoadSize / 2;
-
- int roundedPageStart = Math.round(idealStart / pageSize) * pageSize;
-
- // minimum start position is 0
- roundedPageStart = Math.max(0, roundedPageStart);
-
- // maximum start pos is that which will encompass end of list
- int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize;
- roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
-
- return roundedPageStart;
+ // If initialLoad's callback is not called within the body, we force any following calls
+ // to post to the UI thread. This constructor may be run on a background thread, but
+ // after constructor, mutation must happen on UI thread.
+ callback.setPostExecutor(mMainThreadExecutor);
+ }
}
@Override
@@ -132,7 +119,13 @@
protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
@NonNull Callback callback) {
//noinspection UnnecessaryLocalVariable
- final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage;
+ final PagedStorage<T> snapshot = pagedListSnapshot.mStorage;
+
+ if (snapshot.isEmpty()
+ || mStorage.size() != snapshot.size()) {
+ throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+ + " to be a snapshot of this PagedList");
+ }
// loop through each page and signal the callback for any pages that are present now,
// but not in the snapshot.
@@ -186,7 +179,17 @@
return;
}
final int pageSize = mConfig.pageSize;
- mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver);
+
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ int startPosition = pageIndex * pageSize;
+ int count = Math.min(pageSize, mStorage.size() - startPosition);
+ DataSource.LoadCallback<T> callback = new DataSource.LoadCallback<>(
+ PageResult.TILE, mMainThreadExecutor, mDataSource, mReceiver);
+ callback.setPositionOffset(startPosition);
+ mDataSource.loadRange(startPosition, count, callback);
+ }
}
});
}
diff --git a/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt b/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
new file mode 100644
index 0000000..48d6e29
--- /dev/null
+++ b/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging
+
+class AsyncListDataSource<T>(list: List<T>)
+ : PositionalDataSource<T>() {
+ val workItems: MutableList<() -> Unit> = ArrayList()
+ private val listDataSource = ListDataSource(list)
+
+ override fun loadInitial(requestedStartPosition: Int, requestedLoadSize: Int, pageSize: Int,
+ callback: InitialLoadCallback<T>) {
+ workItems.add {
+ listDataSource.loadInitial(requestedStartPosition, requestedLoadSize, pageSize, callback)
+ }
+ }
+
+ override fun loadRange(startPosition: Int, count: Int, callback: LoadCallback<T>) {
+ workItems.add {
+ listDataSource.loadRange(startPosition, count, callback)
+ }
+ }
+
+ fun flush() {
+ workItems.map { it() }
+ workItems.clear()
+ }
+}
diff --git a/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt b/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
index 7331bfeb..d8b8034 100644
--- a/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
@@ -18,17 +18,16 @@
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.any
-import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
-import java.util.Collections
+
@RunWith(Parameterized::class)
class ContiguousPagedListTest(private val mCounted: Boolean) {
@@ -44,33 +43,46 @@
}
private inner class TestSource(val listData: List<Item> = ITEMS)
- : PositionalDataSource<Item>() {
- override fun countItems(): Int {
- return if (mCounted) {
- listData.size
+ : ContiguousDataSource<Int, Item>() {
+ override fun loadInitial(key: Int?, initialLoadSize: Int,
+ enablePlaceholders: Boolean, callback: InitialLoadCallback<Item>) {
+
+ val convertPosition = key ?: 0
+ val loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2))
+
+ val data = getClampedRange(loadPosition, loadPosition + initialLoadSize)
+
+
+ if (enablePlaceholders && mCounted) {
+ callback.onResult(data, loadPosition, listData.size)
} else {
- DataSource.COUNT_UNDEFINED
+ // still must pass offset, even if not counted
+ callback.onResult(data, loadPosition)
}
}
- private fun getClampedRange(startInc: Int, endExc: Int, reverse: Boolean): List<Item> {
- val list = listData.subList(Math.max(0, startInc), Math.min(listData.size, endExc))
- if (reverse) {
- Collections.reverse(list)
- }
- return list
+ override fun loadAfter(currentEndIndex: Int, currentEndItem: Item, pageSize: Int,
+ callback: LoadCallback<Item>) {
+ val startIndex = currentEndIndex + 1
+ callback.onResult(getClampedRange(startIndex, startIndex + pageSize))
}
- override fun loadAfter(startIndex: Int, pageSize: Int): List<Item>? {
- return getClampedRange(startIndex, startIndex + pageSize, false)
+ override fun loadBefore(currentBeginIndex: Int, currentBeginItem: Item, pageSize: Int,
+ callback: LoadCallback<Item>) {
+ val startIndex = currentBeginIndex - 1
+ callback.onResult(getClampedRange(startIndex - pageSize + 1, startIndex + 1))
}
- override fun loadBefore(startIndex: Int, pageSize: Int): List<Item>? {
- return getClampedRange(startIndex - pageSize + 1, startIndex + 1, true)
+ override fun getKey(position: Int, item: Item?): Int {
+ return 0
+ }
+
+ private fun getClampedRange(startInc: Int, endExc: Int): List<Item> {
+ return listData.subList(Math.max(0, startInc), Math.min(listData.size, endExc))
}
}
- private fun verifyRange(start: Int, count: Int, actual: PagedStorage<*, Item>) {
+ private fun verifyRange(start: Int, count: Int, actual: PagedStorage<Item>) {
if (mCounted) {
// assert nulls + content
val expected = arrayOfNulls<Item>(ITEMS.size)
@@ -98,41 +110,6 @@
verifyRange(start, count, actual.mStorage)
}
- private fun verifyRange(start: Int, count: Int, actual: PageResult<Int, Item>) {
- if (mCounted) {
- assertEquals(start, actual.leadingNulls)
- assertEquals(ITEMS.size - start - count, actual.trailingNulls)
- assertEquals(0, actual.positionOffset)
- } else {
- assertEquals(0, actual.leadingNulls)
- assertEquals(0, actual.trailingNulls)
- assertEquals(start, actual.positionOffset)
- }
- assertEquals(ITEMS.subList(start, start + count), actual.page.items)
- }
-
- private fun verifyInitialLoad(start: Int, count : Int, initialPosition: Int, initialLoadSize: Int) {
- @Suppress("UNCHECKED_CAST")
- val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<Int, Item>
-
- @Suppress("UNCHECKED_CAST")
- val captor = ArgumentCaptor.forClass(PageResult::class.java)
- as ArgumentCaptor<PageResult<Int, Item>>
-
- TestSource().loadInitial(initialPosition, initialLoadSize, true, receiver)
-
- verify(receiver).onPageResult(captor.capture())
- verifyNoMoreInteractions(receiver)
- verifyRange(start, count, captor.value)
- }
-
- @Test
- fun initialLoad() {
- verifyInitialLoad(30, 40, 50, 40)
- verifyInitialLoad(0, 10, 5, 10)
- verifyInitialLoad(90, 10, 95, 10)
- }
-
private fun createCountedPagedList(
initialPosition: Int,
pageSize: Int = 20,
@@ -182,7 +159,6 @@
verifyNoMoreInteractions(callback)
}
-
@Test
fun prepend() {
val pagedList = createCountedPagedList(80)
@@ -311,6 +287,65 @@
}
@Test
+ fun initialLoadAsync() {
+ // Note: ignores Parameterized param
+ val asyncDataSource = AsyncListDataSource(ITEMS)
+ val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
+ val pagedList = ContiguousPagedList(
+ dataSource, mMainThread, mBackgroundThread, null,
+ PagedList.Config.Builder().setPageSize(10).build(), null)
+ val callback = mock(PagedList.Callback::class.java)
+ pagedList.addWeakCallback(null, callback)
+
+ assertTrue(pagedList.isEmpty())
+ drain()
+ assertTrue(pagedList.isEmpty())
+ asyncDataSource.flush()
+ assertTrue(pagedList.isEmpty())
+ mBackgroundThread.executeAll()
+ assertTrue(pagedList.isEmpty())
+ verifyZeroInteractions(callback)
+
+ // Data source defers callbacks until flush, which posts result to main thread
+ mMainThread.executeAll()
+ assertFalse(pagedList.isEmpty())
+ // callback onInsert called once with initial size
+ verify(callback).onInserted(0, pagedList.size)
+ verifyNoMoreInteractions(callback)
+ }
+
+ @Test
+ fun addWeakCallbackEmpty() {
+ // Note: ignores Parameterized param
+ val asyncDataSource = AsyncListDataSource(ITEMS)
+ val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
+ val pagedList = ContiguousPagedList(
+ dataSource, mMainThread, mBackgroundThread, null,
+ PagedList.Config.Builder().setPageSize(10).build(), null)
+ val callback = mock(PagedList.Callback::class.java)
+
+ // capture empty snapshot
+ val emptySnapshot = pagedList.snapshot()
+ assertTrue(pagedList.isEmpty())
+ assertTrue(emptySnapshot.isEmpty())
+
+ // verify that adding callback notifies nothing going from empty -> empty
+ pagedList.addWeakCallback(emptySnapshot, callback)
+ verifyZeroInteractions(callback)
+ pagedList.removeWeakCallback(callback)
+
+ // data added in asynchronously
+ asyncDataSource.flush()
+ drain()
+ assertFalse(pagedList.isEmpty())
+
+ // verify that adding callback notifies insert going from empty -> content
+ pagedList.addWeakCallback(emptySnapshot, callback)
+ verify(callback).onInserted(0, pagedList.size)
+ verifyNoMoreInteractions(callback)
+ }
+
+ @Test
fun boundaryCallback_empty() {
@Suppress("UNCHECKED_CAST")
val boundaryCallback =
@@ -347,8 +382,7 @@
pagedList.loadAround(if (mCounted) 99 else 19)
drain()
verifyRange(80, 20, pagedList)
- verify(boundaryCallback).onItemAtEndLoaded(
- any(), eq(ITEMS.last()), eq(if (mCounted) 100 else 20))
+ verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
verifyNoMoreInteractions(boundaryCallback)
@@ -371,7 +405,7 @@
// ... finally try prepend, see 0 items, which will dispatch front callback
pagedList.loadAround(0)
drain()
- verify(boundaryCallback).onItemAtFrontLoaded(any(), eq(ITEMS.first()), eq(100))
+ verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
verifyNoMoreInteractions(boundaryCallback)
}
diff --git a/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
index 115fea2..82ad9ac 100644
--- a/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
@@ -16,7 +16,6 @@
package android.arch.paging
-import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
@@ -24,6 +23,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -33,31 +33,20 @@
// ----- STANDARD -----
- @Test
- fun loadInitial_validateTestDataSource() {
- val dataSource = ItemDataSource()
-
- // all
- assertEquals(ITEMS_BY_NAME_ID, dataSource.loadInitial(ITEMS_BY_NAME_ID.size))
-
- // 10
- assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), dataSource.loadInitial(10))
-
- // too many
- assertEquals(ITEMS_BY_NAME_ID, dataSource.loadInitial(ITEMS_BY_NAME_ID.size + 10))
- }
-
private fun loadInitial(dataSource: ItemDataSource, key: Key?, initialLoadSize: Int,
- enablePlaceholders: Boolean): PageResult<Key, Item> {
+ enablePlaceholders: Boolean): PageResult<Item> {
@Suppress("UNCHECKED_CAST")
- val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<Key, Item>
+ val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<Item>
@Suppress("UNCHECKED_CAST")
val captor = ArgumentCaptor.forClass(PageResult::class.java)
- as ArgumentCaptor<PageResult<Key, Item>>
+ as ArgumentCaptor<PageResult<Item>>
- dataSource.loadInitial(key, initialLoadSize, enablePlaceholders, receiver)
+ val callback = DataSource.InitialLoadCallback(
+ DataSource.LOAD_COUNT_ACCEPTED, /* ignored page size */ 10, dataSource, receiver)
- verify(receiver).onPageResult(captor.capture())
+ dataSource.loadInitial(key, initialLoadSize, enablePlaceholders, callback)
+
+ verify(receiver).onPageResult(anyInt(), captor.capture())
verifyNoMoreInteractions(receiver)
assertNotNull(captor.value)
return captor.value
@@ -69,7 +58,7 @@
val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[49]), 10, true)
assertEquals(45, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page.items)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
assertEquals(45, result.trailingNulls)
}
@@ -81,7 +70,7 @@
val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[0]), 20, true)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(0, 1), result.page.items)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 1), result.page)
assertEquals(0, result.trailingNulls)
}
@@ -93,8 +82,8 @@
val key = dataSource.getKey(ITEMS_BY_NAME_ID.last())
val result = loadInitial(dataSource, key, 20, true)
- assertEquals(89, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(89, 100), result.page.items)
+ assertEquals(90, result.leadingNulls)
+ assertEquals(ITEMS_BY_NAME_ID.subList(90, 100), result.page)
assertEquals(0, result.trailingNulls)
}
@@ -106,7 +95,7 @@
val result = loadInitial(dataSource, null, 10, true)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page.items)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
assertEquals(90, result.trailingNulls)
}
@@ -122,7 +111,7 @@
// do: load after was empty, so pass full size to load before, since this can incur larger
// loads than requested (see keyMatchesLastItem test)
assertEquals(95, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(95, 100), result.page.items)
+ assertEquals(ITEMS_BY_NAME_ID.subList(95, 100), result.page)
assertEquals(0, result.trailingNulls)
}
@@ -137,7 +126,7 @@
val result = loadInitial(dataSource, key, 10, false)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page.items)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
assertEquals(0, result.trailingNulls)
}
@@ -150,7 +139,7 @@
val result = loadInitial(dataSource, key, 10, true)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page.items)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
assertEquals(0, result.trailingNulls)
}
@@ -162,7 +151,7 @@
val result = loadInitial(dataSource, null, 10, true)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page.items)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
assertEquals(0, result.trailingNulls)
}
@@ -177,7 +166,7 @@
val result = loadInitial(dataSource, key, 10, true)
assertEquals(0, result.leadingNulls)
- assertTrue(result.page.items.isEmpty())
+ assertTrue(result.page.isEmpty())
assertEquals(0, result.trailingNulls)
}
@@ -187,7 +176,7 @@
val result = loadInitial(dataSource, null, 10, true)
assertEquals(0, result.leadingNulls)
- assertTrue(result.page.items.isEmpty())
+ assertTrue(result.page.isEmpty())
assertEquals(0, result.trailingNulls)
}
@@ -197,21 +186,18 @@
fun loadBefore() {
val dataSource = ItemDataSource()
@Suppress("UNCHECKED_CAST")
- val receiver = mock(PageResult.Receiver::class.java)
- as PageResult.Receiver<Key, Item>
+ val callback = mock(DataSource.LoadCallback::class.java) as DataSource.LoadCallback<Item>
- dataSource.loadBefore(5, ITEMS_BY_NAME_ID[5], 5, receiver)
+ dataSource.loadBefore(5, ITEMS_BY_NAME_ID[5], 5, callback)
@Suppress("UNCHECKED_CAST")
- val argument = ArgumentCaptor.forClass(PageResult::class.java)
- as ArgumentCaptor<PageResult<Key, Item>>
- verify(receiver).postOnPageResult(argument.capture())
- verifyNoMoreInteractions(receiver)
+ val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
+ verify(callback).onResult(argument.capture())
+ verifyNoMoreInteractions(callback)
val observed = argument.value
- assertEquals(PageResult.PREPEND, observed.type)
- assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed.page.items)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed)
}
internal data class Key(val name: String, val id: Int)
@@ -219,62 +205,53 @@
internal data class Item(
val name: String, val id: Int, val balance: Double, val address: String)
- internal class ItemDataSource(val counted: Boolean = true,
- val items: List<Item> = ITEMS_BY_NAME_ID)
+ internal class ItemDataSource(private val counted: Boolean = true,
+ private val items: List<Item> = ITEMS_BY_NAME_ID)
: KeyedDataSource<Key, Item>() {
+ override fun loadInitial(initialLoadKey: Key?, initialLoadSize: Int,
+ enablePlaceholders: Boolean, callback: InitialLoadCallback<Item>) {
+ val key = initialLoadKey ?: Key("", Integer.MAX_VALUE)
+ val start = Math.max(0, findFirstIndexAfter(key) - initialLoadSize / 2)
+ val endExclusive = Math.min(start + initialLoadSize, items.size)
+
+
+ if (enablePlaceholders && counted) {
+ callback.onResult(items.subList(start, endExclusive), start, items.size)
+ } else {
+ callback.onResult(items.subList(start, endExclusive))
+ }
+ }
+
+ override fun loadAfter(currentEndKey: Key, pageSize: Int, callback: LoadCallback<Item>) {
+ val start = findFirstIndexAfter(currentEndKey)
+ val endExclusive = Math.min(start + pageSize, items.size)
+
+ callback.onResult(items.subList(start, endExclusive))
+ }
+
+ override fun loadBefore(currentBeginKey: Key, pageSize: Int, callback: LoadCallback<Item>) {
+ val firstIndexBefore = findFirstIndexBefore(currentBeginKey)
+ val endExclusive = Math.max(0, firstIndexBefore + 1)
+ val start = Math.max(0, firstIndexBefore - pageSize + 1)
+
+ callback.onResult(items.subList(start, endExclusive))
+ }
override fun getKey(item: Item): Key {
return Key(item.name, item.id)
}
- override fun loadInitial(pageSize: Int): List<Item>? {
- // call loadAfter with a default key
- return loadAfter(Key("", Integer.MAX_VALUE), pageSize)
- }
-
- fun findFirstIndexAfter(key: Key): Int {
+ private fun findFirstIndexAfter(key: Key): Int {
return items.indices.firstOrNull {
KEY_COMPARATOR.compare(key, getKey(items[it])) < 0
} ?: items.size
}
- fun findFirstIndexBefore(key: Key): Int {
+ private fun findFirstIndexBefore(key: Key): Int {
return items.indices.reversed().firstOrNull {
KEY_COMPARATOR.compare(key, getKey(items[it])) > 0
} ?: -1
}
-
- override fun countItemsBefore(key: Key): Int {
- if (!counted) {
- return DataSource.COUNT_UNDEFINED
- }
-
- return findFirstIndexBefore(key) + 1
- }
-
- override fun countItemsAfter(key: Key): Int {
- if (!counted) {
- return DataSource.COUNT_UNDEFINED
- }
-
- return items.size - findFirstIndexAfter(key)
- }
-
- override fun loadAfter(key: Key, pageSize: Int): List<Item>? {
- val start = findFirstIndexAfter(key)
- val endExclusive = Math.min(start + pageSize, items.size)
-
- return items.subList(start, endExclusive)
- }
-
- override fun loadBefore(key: Key, pageSize: Int): List<Item>? {
- val firstIndexBefore = findFirstIndexBefore(key)
- val endExclusive = Math.max(0, firstIndexBefore + 1)
- val start = Math.max(0, firstIndexBefore - pageSize + 1)
-
- val list = items.subList(start, endExclusive)
- return list.reversed()
- }
}
companion object {
diff --git a/paging/common/src/test/java/android/arch/paging/PagedStorageTest.kt b/paging/common/src/test/java/android/arch/paging/PagedStorageTest.kt
index 92b6c87..8fb54e0 100644
--- a/paging/common/src/test/java/android/arch/paging/PagedStorageTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/PagedStorageTest.kt
@@ -29,8 +29,8 @@
@RunWith(JUnit4::class)
class PagedStorageTest {
- private fun createPage(vararg strings: String): Page<Int, String> {
- return Page(strings.asList())
+ private fun createPage(vararg strings: String): List<String> {
+ return strings.asList()
}
@Test
@@ -216,7 +216,7 @@
@Test
fun insertOne() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(2, createPage("c", "d"), 3, 0, callback)
@@ -236,7 +236,7 @@
@Test
fun insertThree() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(2, createPage("c", "d"), 3, 0, callback)
@@ -273,7 +273,7 @@
@Test
fun insertLastFirst() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(6, createPage("g"), 0, 0, callback)
@@ -294,7 +294,7 @@
@Test(expected = IllegalArgumentException::class)
fun insertFailure_decreaseLast() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(2, createPage("c", "d"), 0, 0, callback)
@@ -305,7 +305,7 @@
@Test(expected = IllegalArgumentException::class)
fun insertFailure_increase() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(0, createPage("a", "b"), 3, 0, callback)
@@ -316,7 +316,7 @@
@Test
fun allocatePlaceholders_simple() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(2, createPage("c"), 2, 0, callback)
@@ -334,7 +334,7 @@
@Test
fun allocatePlaceholders_adoptPageSize() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(4, createPage("e"), 0, 0, callback)
@@ -352,7 +352,7 @@
@Test(expected = IllegalArgumentException::class)
fun allocatePlaceholders_cannotShrinkPageSize() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(4, createPage("e", "f"), 0, 0, callback)
@@ -365,7 +365,7 @@
@Test(expected = IllegalArgumentException::class)
fun allocatePlaceholders_cannotAdoptPageSize() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(2, createPage("c", "d"), 2, 0, callback)
@@ -377,7 +377,7 @@
@Test
fun get_placeholdersMulti() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(2, createPage("c", "d"), 3, 0, callback)
@@ -392,7 +392,7 @@
@Test
fun hasPage() {
val callback = mock(PagedStorage.Callback::class.java)
- val storage = PagedStorage<Int, String>()
+ val storage = PagedStorage<String>()
storage.init(4, createPage("e"), 0, 0, callback)
diff --git a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
new file mode 100644
index 0000000..34e0a57
--- /dev/null
+++ b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PositionalDataSourceTest {
+ @Test
+ fun computeFirstLoadPositionZero() {
+ assertEquals(0, PositionalDataSource.computeFirstLoadPosition(0, 30, 10, 100))
+ }
+
+ @Test
+ fun computeFirstLoadPositionRequestedPositionIncluded() {
+ assertEquals(10, PositionalDataSource.computeFirstLoadPosition(10, 10, 10, 100))
+ }
+
+ @Test
+ fun computeFirstLoadPositionRound() {
+ assertEquals(10, PositionalDataSource.computeFirstLoadPosition(13, 30, 10, 100))
+ }
+
+ @Test
+ fun computeFirstLoadPositionEndAdjusted() {
+ assertEquals(70, PositionalDataSource.computeFirstLoadPosition(99, 30, 10, 100))
+ }
+
+ @Test
+ fun computeFirstLoadPositionEndAdjustedAndAligned() {
+ assertEquals(70, PositionalDataSource.computeFirstLoadPosition(99, 35, 10, 100))
+ }
+}
diff --git a/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
index 2b16fb2..fe8b9e8 100644
--- a/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.arch.paging
import org.junit.Assert.assertEquals
@@ -5,41 +21,66 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import java.util.Collections
-
+@Suppress("DEPRECATION")
@RunWith(JUnit4::class)
class TiledDataSourceTest {
- @Test
- fun loadInitialEmpty() {
+
+ fun TiledDataSource<String>.loadInitial(startPosition: Int, count: Int, pageSize: Int)
+ : List<String> {
@Suppress("UNCHECKED_CAST")
- val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<Int, String>
- val dataSource = EmptyDataSource()
- dataSource.loadRangeInitial(0, 0, 1, 0, receiver)
+ val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<String>
+
+ val callback = DataSource.InitialLoadCallback(
+ DataSource.LOAD_COUNT_REQUIRED_TILED, pageSize, this, receiver)
+
+ this.loadInitial(startPosition, count, pageSize, callback)
@Suppress("UNCHECKED_CAST")
val argument = ArgumentCaptor.forClass(PageResult::class.java)
- as ArgumentCaptor<PageResult<Int, String>>
- verify(receiver).onPageResult(argument.capture())
+ as ArgumentCaptor<PageResult<String>>
+ verify(receiver).onPageResult(eq(PageResult.INIT), argument.capture())
verifyNoMoreInteractions(receiver)
val observed = argument.value
- assertEquals(PageResult.INIT, observed.type)
- assertEquals(Collections.EMPTY_LIST, observed.page.items)
+ return observed.page
}
- class EmptyDataSource : TiledDataSource<String>() {
- override fun countItems(): Int {
- return 0
+ @Test
+ fun loadInitialEmpty() {
+ class EmptyDataSource : TiledDataSource<String>() {
+ override fun countItems(): Int {
+ return 0
+ }
+
+ override fun loadRange(startPosition: Int, count: Int): List<String> {
+ return emptyList()
+ }
}
- override fun loadRange(startPosition: Int, count: Int): List<String> {
- @Suppress("UNCHECKED_CAST")
- return Collections.EMPTY_LIST as List<String>
- }
+ assertEquals(Collections.EMPTY_LIST, EmptyDataSource().loadInitial(0, 1, 5))
}
-}
\ No newline at end of file
+
+ @Test
+ fun loadInitialTooLong() {
+ val list = List(26) { "" + 'a' + it}
+ class AlphabetDataSource : TiledDataSource<String>() {
+ override fun countItems(): Int {
+ return list.size
+ }
+
+ override fun loadRange(startPosition: Int, count: Int): List<String> {
+ return list.subList(startPosition, startPosition + count)
+ }
+ }
+ // baseline behavior
+ assertEquals(list, AlphabetDataSource().loadInitial(0, 26, 10))
+ assertEquals(list, AlphabetDataSource().loadInitial(50, 26, 10))
+ }
+}
diff --git a/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt b/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
index 6524008..3e2a60d 100644
--- a/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
@@ -17,14 +17,14 @@
package android.arch.paging
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.Mockito.any
-import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -43,12 +43,12 @@
}
}
- private fun verifyLoadedPages(list: List<Item>, vararg loadedPages: Int, expected: List<Item> = ITEMS) {
+ private fun verifyLoadedPages(list: List<Item>, vararg loadedPages: Int) {
val loadedPageList = loadedPages.asList()
- assertEquals(expected.size, list.size)
+ assertEquals(ITEMS.size, list.size)
for (i in list.indices) {
if (loadedPageList.contains(i / PAGE_SIZE)) {
- assertSame("Index $i", expected[i], list[i])
+ assertSame("Index $i", ITEMS[i], list[i])
} else {
assertNull("Index $i", list[i])
}
@@ -70,21 +70,6 @@
}
@Test
- fun computeFirstLoadPosition_zero() {
- assertEquals(0, TiledPagedList.computeFirstLoadPosition(0, 30, 10, 100))
- }
-
- @Test
- fun computeFirstLoadPosition_requestedPositionIncluded() {
- assertEquals(0, TiledPagedList.computeFirstLoadPosition(10, 10, 10, 100))
- }
-
- @Test
- fun computeFirstLoadPosition_endAdjusted() {
- assertEquals(70, TiledPagedList.computeFirstLoadPosition(99, 30, 10, 100))
- }
-
- @Test
fun initialLoad_onePage() {
val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
verifyLoadedPages(pagedList, 0, 1)
@@ -121,6 +106,56 @@
}
@Test
+ fun initialLoad_initializesLastKey() {
+ val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
+ assertEquals(44, pagedList.lastKey)
+ }
+
+ @Test
+ fun initialLoadAsync() {
+ val dataSource = AsyncListDataSource(ITEMS)
+ val pagedList = TiledPagedList(
+ dataSource, mMainThread, mBackgroundThread, null,
+ PagedList.Config.Builder().setPageSize(10).build(), 0)
+
+ assertTrue(pagedList.isEmpty())
+ drain()
+ assertTrue(pagedList.isEmpty())
+ dataSource.flush()
+ assertTrue(pagedList.isEmpty())
+ mBackgroundThread.executeAll()
+ assertTrue(pagedList.isEmpty())
+
+ // Data source defers callbacks until flush, which posts result to main thread
+ mMainThread.executeAll()
+ assertFalse(pagedList.isEmpty())
+ }
+
+ @Test
+ fun addWeakCallbackEmpty() {
+ val dataSource = AsyncListDataSource(ITEMS)
+ val pagedList = TiledPagedList(
+ dataSource, mMainThread, mBackgroundThread, null,
+ PagedList.Config.Builder().setPageSize(10).build(), 0)
+
+ // capture empty snapshot
+ val emptySnapshot = pagedList.snapshot()
+ assertTrue(pagedList.isEmpty())
+ assertTrue(emptySnapshot.isEmpty())
+
+ // data added in asynchronously
+ dataSource.flush()
+ drain()
+ assertFalse(pagedList.isEmpty())
+
+ // verify that adding callback works with empty start point
+ val callback = mock(PagedList.Callback::class.java)
+ pagedList.addWeakCallback(emptySnapshot, callback)
+ verify(callback).onInserted(0, pagedList.size)
+ verifyNoMoreInteractions(callback)
+ }
+
+ @Test
fun append() {
val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
val callback = mock(PagedList.Callback::class.java)
@@ -251,16 +286,15 @@
@Test
fun placeholdersDisabled() {
// disable placeholders with config, so we create a contiguous version of the pagedlist
- val pagedList = PagedList.Builder<Int, Item>()
- .setDataSource(ListDataSource(ITEMS))
+ val config = PagedList.Config.Builder()
+ .setPageSize(PAGE_SIZE)
+ .setPrefetchDistance(PAGE_SIZE)
+ .setInitialLoadSizeHint(PAGE_SIZE)
+ .setEnablePlaceholders(false)
+ .build()
+ val pagedList = PagedList.Builder<Int, Item>(ListDataSource(ITEMS), config)
.setMainThreadExecutor(mMainThread)
.setBackgroundThreadExecutor(mBackgroundThread)
- .setConfig(PagedList.Config.Builder()
- .setPageSize(PAGE_SIZE)
- .setPrefetchDistance(PAGE_SIZE)
- .setInitialLoadSizeHint(PAGE_SIZE)
- .setEnablePlaceholders(false)
- .build())
.setInitialKey(20)
.build()
@@ -305,8 +339,8 @@
// callbacks posted, since creation often happens on BG thread
drain()
- verify(boundaryCallback).onItemAtFrontLoaded(any(), eq(ITEMS[0]), eq(2))
- verify(boundaryCallback).onItemAtEndLoaded(any(), eq(ITEMS[1]), eq(2))
+ verify(boundaryCallback).onItemAtFrontLoaded(ITEMS[0])
+ verify(boundaryCallback).onItemAtEndLoaded(ITEMS[1])
verifyNoMoreInteractions(boundaryCallback)
}
@@ -332,8 +366,8 @@
drain()
// first/last items loaded now, so callbacks dispatched
- verify(boundaryCallback).onItemAtFrontLoaded(any(), eq(ITEMS.first()), eq(45))
- verify(boundaryCallback).onItemAtEndLoaded(any(), eq(ITEMS.last()), eq(45))
+ verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
+ verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
verifyNoMoreInteractions(boundaryCallback)
}
@@ -360,11 +394,79 @@
drain()
// items accessed, so now posted callbacks are run
- verify(boundaryCallback).onItemAtFrontLoaded(any(), eq(ITEMS.first()), eq(45))
- verify(boundaryCallback).onItemAtEndLoaded(any(), eq(ITEMS.last()), eq(45))
+ verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
+ verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
verifyNoMoreInteractions(boundaryCallback)
}
+ private fun performInitialLoad(
+ callbackInvoker: (callback: DataSource.InitialLoadCallback<String>) -> Unit) {
+ val dataSource = object: PositionalDataSource<String>() {
+ override fun loadInitial(requestedStartPosition: Int, requestedLoadSize: Int,
+ pageSize: Int, callback: InitialLoadCallback<String>) {
+ callbackInvoker(callback)
+ }
+ override fun loadRange(startPosition: Int, count: Int, callback: LoadCallback<String>) {
+ fail("loadRange not expected")
+ }
+ }
+ TiledPagedList(
+ dataSource, mMainThread, mBackgroundThread, null,
+ PagedList.Config.Builder()
+ .setPageSize(PAGE_SIZE)
+ .build(),
+ 0)
+ }
+
+ @Test
+ fun initialLoadCallbackSuccess() = performInitialLoad {
+ // InitialLoadCallback correct usage
+ it.onResult(listOf("a", "b"), 0, 2)
+ }
+
+ @Test
+ fun initialLoadCallbackEmptySuccess() = performInitialLoad {
+ // InitialLoadCallback correct usage - empty special case
+ it.onResult(emptyList())
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackNotPageSizeMultiple() = performInitialLoad {
+ // Positional InitialLoadCallback can't accept result that's not a multiple of page size
+ val elevenLetterList = List(11) { "" + 'a' + it }
+ it.onResult(elevenLetterList, 0, 12)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackMissingPlaceholders() = performInitialLoad {
+ // Positional InitialLoadCallback can't accept list-only call
+ it.onResult(listOf("a", "b"))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackListTooBig() = performInitialLoad {
+ // InitialLoadCallback can't accept pos + list > totalCount
+ it.onResult(listOf("a", "b", "c"), 0, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackPositionTooLarge() = performInitialLoad {
+ // InitialLoadCallback can't accept pos + list > totalCount
+ it.onResult(listOf("a", "b"), 1, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackPositionNegative() = performInitialLoad {
+ // InitialLoadCallback can't accept negative position
+ it.onResult(listOf("a", "b", "c"), -1, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackEmptyCannotHavePlaceholders() = performInitialLoad {
+ // Positional InitialLoadCallback can't accept empty result unless data set is empty
+ it.onResult(emptyList(), 0, 2)
+ }
+
private fun drain() {
var executed: Boolean
do {
diff --git a/paging/integration-tests/testapp/build.gradle b/paging/integration-tests/testapp/build.gradle
index 47f45d1..fb19ba0 100644
--- a/paging/integration-tests/testapp/build.gradle
+++ b/paging/integration-tests/testapp/build.gradle
@@ -27,6 +27,14 @@
versionCode 1
versionName "1.0"
}
+
+ signingConfigs {
+ debug {
+ // Use a local debug keystore to avoid build server issues.
+ storeFile project.rootProject.init.debugKeystore
+ }
+ }
+
testOptions {
unitTests.returnDefaultValues = true
}
@@ -37,17 +45,17 @@
}
dependencies {
- compile project(':arch:runtime')
- compile project(':arch:common')
- compile project(':paging:common')
- compile project(':lifecycle:extensions')
- compile project(':lifecycle:runtime')
- compile project(':lifecycle:common')
- compile project(':paging:runtime')
- compile 'com.android.support:multidex:1.0.1'
+ implementation project(':arch:runtime')
+ implementation project(':arch:common')
+ implementation project(':paging:common')
+ implementation project(':lifecycle:extensions')
+ implementation project(':lifecycle:runtime')
+ implementation project(':lifecycle:common')
+ implementation project(':paging:runtime')
+ implementation 'com.android.support:multidex:1.0.1'
- compile libs.support.recyclerview, libs.support_exclude_config
- compile libs.support.app_compat, libs.support_exclude_config
+ implementation libs.support.recyclerview, libs.support_exclude_config
+ implementation libs.support.app_compat, libs.support_exclude_config
}
createAndroidCheckstyle(project)
diff --git a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/ItemDataSource.java b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/ItemDataSource.java
index 4690533..bbbfabb 100644
--- a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/ItemDataSource.java
+++ b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/ItemDataSource.java
@@ -16,9 +16,10 @@
package android.arch.paging.integration.testapp;
-import android.arch.paging.BoundedDataSource;
+import android.arch.paging.PositionalDataSource;
import android.graphics.Color;
import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
@@ -26,7 +27,7 @@
/**
* Sample data source with artificial data.
*/
-class ItemDataSource extends BoundedDataSource<Item> {
+class ItemDataSource extends PositionalDataSource<Item> {
private static final int COUNT = 500;
@ColorInt
@@ -39,18 +40,7 @@
private static int sGenerationId;
private final int mGenerationId = sGenerationId++;
- @Override
- public int countItems() {
- return COUNT;
- }
-
- @Override
- public List<Item> loadRange(int startPosition, int loadCount) {
- if (isInvalid()) {
- // abort!
- return null;
- }
-
+ private List<Item> loadRangeInternal(int startPosition, int loadCount) {
List<Item> items = new ArrayList<>();
int end = Math.min(COUNT, startPosition + loadCount);
int bgColor = COLORS[mGenerationId % COLORS.length];
@@ -63,11 +53,38 @@
for (int i = startPosition; i != end; i++) {
items.add(new Item(i, "item " + i, bgColor));
}
-
- if (isInvalid()) {
- // abort!
- return null;
- }
return items;
}
+
+ // TODO: open up this API in PositionalDataSource?
+ private static int computeFirstLoadPosition(int position, int firstLoadSize,
+ int pageSize, int size) {
+ int roundedPageStart = Math.round(position / pageSize) * pageSize;
+
+ // minimum start position is 0
+ roundedPageStart = Math.max(0, roundedPageStart);
+
+ // maximum start pos is that which will encompass end of list
+ int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize;
+ roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
+
+ return roundedPageStart;
+ }
+
+ @Override
+ public void loadInitial(int requestedStartPosition, int requestedLoadSize,
+ int pageSize, @NonNull InitialLoadCallback<Item> callback) {
+ requestedStartPosition = computeFirstLoadPosition(
+ requestedStartPosition, requestedLoadSize, pageSize, COUNT);
+
+ requestedLoadSize = Math.min(COUNT - requestedStartPosition, requestedLoadSize);
+ List<Item> data = loadRangeInternal(requestedStartPosition, requestedLoadSize);
+ callback.onResult(data, requestedStartPosition, COUNT);
+ }
+
+ @Override
+ public void loadRange(int startPosition, int count, @NonNull LoadCallback<Item> callback) {
+ List<Item> data = loadRangeInternal(startPosition, count);
+ callback.onResult(data);
+ }
}
diff --git a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemViewModel.java b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemViewModel.java
index 974eab9..be14bc1 100644
--- a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemViewModel.java
+++ b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemViewModel.java
@@ -27,10 +27,24 @@
*/
@SuppressWarnings("WeakerAccess")
public class PagedListItemViewModel extends ViewModel {
- private LiveData<PagedList<Item>> mLivePagedList;
private ItemDataSource mDataSource;
private final Object mDataSourceLock = new Object();
+ private final DataSource.Factory<Integer, Item> mFactory =
+ new DataSource.Factory<Integer, Item>() {
+ @Override
+ public DataSource<Integer, Item> create() {
+ ItemDataSource newDataSource = new ItemDataSource();
+ synchronized (mDataSourceLock) {
+ mDataSource = newDataSource;
+ return mDataSource;
+ }
+ }
+ };
+
+ private LiveData<PagedList<Item>> mLivePagedList =
+ new LivePagedListBuilder<>(mFactory, 20).build();
+
void invalidateList() {
synchronized (mDataSourceLock) {
if (mDataSource != null) {
@@ -40,22 +54,6 @@
}
LiveData<PagedList<Item>> getLivePagedList() {
- if (mLivePagedList == null) {
- mLivePagedList = new LivePagedListBuilder<Integer, Item>()
- .setPagingConfig(20)
- .setDataSourceFactory(new DataSource.Factory<Integer, Item>() {
- @Override
- public DataSource<Integer, Item> create() {
- ItemDataSource newDataSource = new ItemDataSource();
- synchronized (mDataSourceLock) {
- mDataSource = newDataSource;
- return mDataSource;
- }
- }
- })
- .build();
- }
-
return mLivePagedList;
}
}
diff --git a/paging/runtime/build.gradle b/paging/runtime/build.gradle
index c318c65..187acb98 100644
--- a/paging/runtime/build.gradle
+++ b/paging/runtime/build.gradle
@@ -14,36 +14,28 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
-apply plugin: 'kotlin-android'
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("kotlin-android")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
- compile project(":arch:runtime")
- compile project(":paging:common")
- compile project(":lifecycle:runtime")
- compile project(':lifecycle:extensions')
+ api project(":arch:runtime")
+ api project(":paging:common")
+ api project(":lifecycle:runtime")
+ api project(':lifecycle:extensions')
- compile libs.support.recyclerview, libs.support_exclude_config
+ api libs.support.recyclerview, libs.support_exclude_config
androidTestImplementation libs.junit
androidTestImplementation libs.mockito_core, { exclude group: 'net.bytebuddy' } // DexMaker has it"s own MockMaker
@@ -57,21 +49,12 @@
createAndroidCheckstyle(project)
createKotlinCheckstyle(project)
-android.libraryVariants.all { variant ->
- def name = variant.buildType.name
- def suffix = name.capitalize()
- project.tasks.create(name: "jar${suffix}", type: Jar){
- dependsOn variant.javaCompile
- from variant.javaCompile.destinationDir
- destinationDir new File(project.buildDir, "libJar")
- }
-}
-
-version = LibraryVersions.PAGING.toString()
supportLibrary {
- name 'Android Lifecycle Extensions'
- publish true
- inceptionYear '2017'
- description "Android Lifecycle Extensions"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Lifecycle Extensions"
+ publish = true
+ mavenVersion = LibraryVersions.PAGING
+ mavenGroup = LibraryGroups.PAGING
+ inceptionYear = "2017"
+ description = "Android Lifecycle Extensions"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/paging/runtime/lint-baseline.xml b/paging/runtime/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/paging/runtime/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.kt b/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.kt
index 735a61f..d3a910d 100644
--- a/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.kt
+++ b/paging/runtime/src/androidTest/java/android/arch/paging/PagedListAdapterHelperTest.kt
@@ -42,8 +42,8 @@
private val mPageLoadingThread = TestExecutor()
- private fun <T> createHelper(
- listUpdateCallback: ListUpdateCallback, diffCallback: DiffCallback<T>): PagedListAdapterHelper<T> {
+ private fun <T> createHelper(listUpdateCallback: ListUpdateCallback,
+ diffCallback: DiffCallback<T>): PagedListAdapterHelper<T> {
return PagedListAdapterHelper(listUpdateCallback,
ListAdapterConfig.Builder<T>()
.setDiffCallback(diffCallback)
@@ -54,12 +54,10 @@
private fun <V> createPagedListFromListAndPos(
config: PagedList.Config, data: List<V>, initialKey: Int): PagedList<V> {
- return PagedList.Builder<Int, V>()
+ return PagedList.Builder<Int, V>(ListDataSource(data), config)
.setInitialKey(initialKey)
- .setConfig(config)
.setMainThreadExecutor(mMainThread)
.setBackgroundThreadExecutor(mPageLoadingThread)
- .setDataSource(ListDataSource(data))
.build()
}
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/PagedStorageDiffHelperTest.kt b/paging/runtime/src/androidTest/java/android/arch/paging/PagedStorageDiffHelperTest.kt
index 64501f7..b64b27b 100644
--- a/paging/runtime/src/androidTest/java/android/arch/paging/PagedStorageDiffHelperTest.kt
+++ b/paging/runtime/src/androidTest/java/android/arch/paging/PagedStorageDiffHelperTest.kt
@@ -35,17 +35,17 @@
@Test
fun sameListNoUpdates() {
validateTwoListDiff(
- PagedStorage(5, createPage("a", "b", "c"), 5),
- PagedStorage(5, createPage("a", "b", "c"), 5)) {
+ PagedStorage(5, listOf("a", "b", "c"), 5),
+ PagedStorage(5, listOf("a", "b", "c"), 5)) {
verifyZeroInteractions(it)
}
}
@Test
fun sameListNoUpdatesPlaceholder() {
- val storageNoPlaceholder = PagedStorage(0, createPage("a", "b", "c"), 10)
+ val storageNoPlaceholder = PagedStorage(0, listOf("a", "b", "c"), 10)
- val storageWithPlaceholder = PagedStorage(0, createPage("a", "b", "c"), 10)
+ val storageWithPlaceholder = PagedStorage(0, listOf("a", "b", "c"), 10)
storageWithPlaceholder.allocatePlaceholders(3, 0, 3,
/* ignored */ mock(PagedStorage.Callback::class.java))
@@ -64,8 +64,8 @@
@Test
fun appendFill() {
validateTwoListDiff(
- PagedStorage(5, createPage("a", "b"), 5),
- PagedStorage(5, createPage("a", "b", "c"), 4)) {
+ PagedStorage(5, listOf("a", "b"), 5),
+ PagedStorage(5, listOf("a", "b", "c"), 4)) {
verify(it).onRemoved(11, 1)
verify(it).onInserted(7, 1)
// NOTE: ideally would be onChanged(7, 1, null)
@@ -76,8 +76,8 @@
@Test
fun prependFill() {
validateTwoListDiff(
- PagedStorage(5, createPage("b", "c"), 5),
- PagedStorage(4, createPage("a", "b", "c"), 5)) {
+ PagedStorage(5, listOf("b", "c"), 5),
+ PagedStorage(4, listOf("a", "b", "c"), 5)) {
verify(it).onRemoved(0, 1)
verify(it).onInserted(4, 1)
//NOTE: ideally would be onChanged(4, 1, null);
@@ -88,8 +88,8 @@
@Test
fun change() {
validateTwoListDiff(
- PagedStorage(5, createPage("a1", "b1", "c1"), 5),
- PagedStorage(5, createPage("a2", "b1", "c2"), 5)) {
+ PagedStorage(5, listOf("a1", "b1", "c1"), 5),
+ PagedStorage(5, listOf("a2", "b1", "c2"), 5)) {
verify(it).onChanged(5, 1, null)
verify(it).onChanged(7, 1, null)
verifyNoMoreInteractions(it)
@@ -108,12 +108,8 @@
}
}
- private fun createPage(vararg items: String): Page<Int, String> {
- return Page(items.toList())
- }
-
- private fun validateTwoListDiff(oldList: PagedStorage<*, String>,
- newList: PagedStorage<*, String>,
+ private fun validateTwoListDiff(oldList: PagedStorage<String>,
+ newList: PagedStorage<String>,
validator: (callback: ListUpdateCallback) -> Unit) {
val diffResult = PagedStorageDiffHelper.computeDiff(
oldList, newList, DIFF_CALLBACK)
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/StringPagedList.kt b/paging/runtime/src/androidTest/java/android/arch/paging/StringPagedList.kt
index c2e5ec7..48d51bc 100644
--- a/paging/runtime/src/androidTest/java/android/arch/paging/StringPagedList.kt
+++ b/paging/runtime/src/androidTest/java/android/arch/paging/StringPagedList.kt
@@ -17,13 +17,13 @@
package android.arch.paging
class StringPagedList constructor(leadingNulls: Int, trailingNulls: Int, vararg items: String)
- : PagedList<String>(PagedStorage<Int, String>(), TestExecutor(), TestExecutor(), null,
+ : PagedList<String>(PagedStorage<String>(), TestExecutor(), TestExecutor(), null,
PagedList.Config.Builder().setPageSize(1).build()), PagedStorage.Callback {
init {
@Suppress("UNCHECKED_CAST")
- val keyedStorage = mStorage as PagedStorage<Int, String>
+ val keyedStorage = mStorage as PagedStorage<String>
keyedStorage.init(leadingNulls,
- Page<Int, String>(null, items.toList(), null),
+ items.toList(),
trailingNulls,
0,
this)
diff --git a/paging/runtime/src/androidTest/java/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.kt b/paging/runtime/src/androidTest/java/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.kt
index 4c06f25..433f4c1 100644
--- a/paging/runtime/src/androidTest/java/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.kt
+++ b/paging/runtime/src/androidTest/java/android/support/v7/recyclerview/extensions/ListAdapterHelperTest.kt
@@ -34,8 +34,8 @@
private val mMainThread = TestExecutor()
private val mBackgroundThread = TestExecutor()
- private fun <T> createHelper(
- listUpdateCallback: ListUpdateCallback, diffCallback: DiffCallback<T>): ListAdapterHelper<T> {
+ private fun <T> createHelper(listUpdateCallback: ListUpdateCallback,
+ diffCallback: DiffCallback<T>): ListAdapterHelper<T> {
return ListAdapterHelper(listUpdateCallback,
ListAdapterConfig.Builder<T>()
.setDiffCallback(diffCallback)
diff --git a/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java b/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java
index ee1810b..b0fddba 100644
--- a/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java
+++ b/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java
@@ -25,42 +25,76 @@
import java.util.concurrent.Executor;
+/**
+ * Builder for {@code LiveData<PagedList>}, given a {@link DataSource.Factory} and a
+ * {@link PagedList.Config}.
+ * <p>
+ * The required parameters are in the constructor, so you can simply construct and build, or
+ * optionally enable extra features (such as initial load key, or BoundaryCallback.
+ *
+ * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
+ * you're using PositionalDataSource.
+ * @param <Value> Item type being presented.
+ */
public class LivePagedListBuilder<Key, Value> {
private Key mInitialLoadKey;
private PagedList.Config mConfig;
private DataSource.Factory<Key, Value> mDataSourceFactory;
private PagedList.BoundaryCallback mBoundaryCallback;
- private Executor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
- @SuppressWarnings("WeakerAccess")
+ /**
+ * Creates a LivePagedListBuilder with required parameters.
+ *
+ * @param dataSourceFactory DataSource factory providing DataSource generations.
+ * @param config Paging configuration.
+ */
+ public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ @NonNull PagedList.Config config) {
+ mDataSourceFactory = dataSourceFactory;
+ mConfig = config;
+ }
+
+ /**
+ * Creates a LivePagedListBuilder with required parameters.
+ * <p>
+ * This method is a convenience for:
+ * <pre>
+ * LivePagedListBuilder(dataSourceFactory,
+ * new PagedList.Config.Builder().setPageSize(pageSize).build())
+ * </pre>
+ *
+ * @param dataSourceFactory DataSource.Factory providing DataSource generations.
+ * @param pageSize Size of pages to load.
+ */
+ public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ int pageSize) {
+ this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build());
+ }
+
+ /**
+ * First loading key passed to the first PagedList/DataSource.
+ * <p>
+ * When a new PagedList/DataSource pair is created after the first, it acquires a load key from
+ * the previous generation so that data is loaded around the position already being observed.
+ *
+ * @param key Initial load key passed to the first PagedList/DataSource.
+ * @return this
+ */
@NonNull
public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) {
mInitialLoadKey = key;
return this;
}
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LivePagedListBuilder<Key, Value> setPagingConfig(@NonNull PagedList.Config config) {
- mConfig = config;
- return this;
- }
-
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LivePagedListBuilder<Key, Value> setPagingConfig(int pageSize) {
- mConfig = new PagedList.Config.Builder().setPageSize(pageSize).build();
- return this;
- }
-
- @NonNull
- public LivePagedListBuilder<Key, Value> setDataSourceFactory(
- @NonNull DataSource.Factory<Key, Value> dataSourceFactory) {
- mDataSourceFactory = dataSourceFactory;
- return this;
- }
-
+ /**
+ * Sets a {@link PagedList.BoundaryCallback} on each PagedList created.
+ * <p>
+ * This can be used to
+ *
+ * @param boundaryCallback The boundary callback for listening to PagedList load state.
+ * @return this
+ */
@SuppressWarnings("unused")
@NonNull
public LivePagedListBuilder<Key, Value> setBoundaryCallback(
@@ -69,14 +103,15 @@
return this;
}
- @SuppressWarnings("unused")
- @NonNull
- public LivePagedListBuilder<Key, Value> setMainThreadExecutor(
- @NonNull Executor mainThreadExecutor) {
- mMainThreadExecutor = mainThreadExecutor;
- return this;
- }
-
+ /**
+ * Sets executor which will be used for background loading of pages.
+ * <p>
+ * Does not affect initial load, which will be always be done on done on the Arch components
+ * I/O thread.
+ *
+ * @param backgroundThreadExecutor Executor for background DataSource loading.
+ * @return this
+ */
@SuppressWarnings("unused")
@NonNull
public LivePagedListBuilder<Key, Value> setBackgroundThreadExecutor(
@@ -85,6 +120,14 @@
return this;
}
+ /**
+ * Constructs the {@code LiveData<PagedList>}.
+ * <p>
+ * No work (such as loading) is done immediately, the creation of the first PagedList is is
+ * deferred until the LiveData is observed.
+ *
+ * @return The LiveData of PagedLists
+ */
@NonNull
public LiveData<PagedList<Value>> build() {
if (mConfig == null) {
@@ -93,20 +136,17 @@
if (mDataSourceFactory == null) {
throw new IllegalArgumentException("DataSource.Factory must be provided");
}
- if (mMainThreadExecutor == null) {
- mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor();
- }
if (mBackgroundThreadExecutor == null) {
mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
}
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
- mMainThreadExecutor, mBackgroundThreadExecutor);
+ ArchTaskExecutor.getMainThreadExecutor(), mBackgroundThreadExecutor);
}
@AnyThread
@NonNull
- public static <Key, Value> LiveData<PagedList<Value>> create(
+ private static <Key, Value> LiveData<PagedList<Value>> create(
@Nullable final Key initialLoadKey,
@NonNull final PagedList.Config config,
@Nullable final PagedList.BoundaryCallback boundaryCallback,
@@ -143,12 +183,10 @@
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
- mList = new PagedList.Builder<Key, Value>()
- .setDataSource(mDataSource)
+ mList = new PagedList.Builder<>(mDataSource, config)
.setMainThreadExecutor(mainThreadExecutor)
.setBackgroundThreadExecutor(backgroundThreadExecutor)
.setBoundaryCallback(boundaryCallback)
- .setConfig(config)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());
diff --git a/paging/runtime/src/main/java/android/arch/paging/LivePagedListProvider.java b/paging/runtime/src/main/java/android/arch/paging/LivePagedListProvider.java
index e0a03cb..44b71a8 100644
--- a/paging/runtime/src/main/java/android/arch/paging/LivePagedListProvider.java
+++ b/paging/runtime/src/main/java/android/arch/paging/LivePagedListProvider.java
@@ -22,45 +22,30 @@
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
+// NOTE: Room 1.0 depends on this class, so it should not be removed
+// until Room switches to using DataSource.Factory directly
/**
* Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource.
* <p>
* Return type for data-loading system of an application or library to produce a
* {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the
* consumer.
- * <p>
- * If you're using Room, it can generate a LivePagedListProvider from a query:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract LivePagedListProvider<Integer, User> usersByLastName();
- * }</pre>
- * In the above sample, {@code Integer} is used because it is the {@code Key} type of
- * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET},
- * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer}
- * here lets you pass an initial loading position as an integer.
- * <p>
- * In the future, Room plans to offer other key types to support paging content with a
- * {@link KeyedDataSource}.
*
* @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
- * you're using TiledDataSource.
+ * you're using PositionalDataSource.
* @param <Value> Data type produced by the DataSource, and held by the PagedLists.
*
* @see PagedListAdapter
* @see DataSource
* @see PagedList
*
- * @deprecated To construct a {@code LiveData<PagedList>}, use {@link LivePagedListBuilder}, which
- * provides the same construction capability with more customization, and better defaults. The role
+ * @deprecated use {@link LivePagedListBuilder} to construct a {@code LiveData<PagedList>}. It
+ * provides the same construction capability with more customization, and simpler defaults. The role
* of DataSource construction has been separated out to {@link DataSource.Factory} to access or
* provide a self-invalidating sequence of DataSources. If you were acquiring this from Room, you
- * can switch to having your Dao return a {@link DataSource.Factory} instead, and create a LiveData
- * of PagedList with a {@link LivePagedListBuilder}.
+ * can switch to having your Dao return a {@link DataSource.Factory} instead, and create a
+ * {@code LiveData<PagedList>} with a {@link LivePagedListBuilder}.
*/
-// NOTE: Room 1.0 depends on this class, so it should not be removed
-// until Room switches to using DataSource.Factory directly
@Deprecated
public abstract class LivePagedListProvider<Key, Value> implements DataSource.Factory<Key, Value> {
@@ -93,10 +78,8 @@
@AnyThread
@NonNull
public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
- return new LivePagedListBuilder<Key, Value>()
+ return new LivePagedListBuilder<>(this, pageSize)
.setInitialLoadKey(initialLoadKey)
- .setPagingConfig(pageSize)
- .setDataSourceFactory(this)
.build();
}
@@ -116,10 +99,8 @@
@NonNull
public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey,
@NonNull PagedList.Config config) {
- return new LivePagedListBuilder<Key, Value>()
+ return new LivePagedListBuilder<>(this, config)
.setInitialLoadKey(initialLoadKey)
- .setPagingConfig(config)
- .setDataSourceFactory(this)
.build();
}
}
diff --git a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java
index 89b9c2e..a8158c23 100644
--- a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java
+++ b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java
@@ -44,18 +44,14 @@
* {@literal @}Dao
* interface UserDao {
* {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract LivePagedListProvider<Integer, User> usersByLastName();
+ * public abstract DataSource.Factory<Integer, User> usersByLastName();
* }
*
* class MyViewModel extends ViewModel {
* public final LiveData<PagedList<User>> usersList;
* public MyViewModel(UserDao userDao) {
- * usersList = userDao.usersByLastName().create(
- * /* initial load position {@literal *}/ 0,
- * new PagedList.Config.Builder()
- * .setPageSize(50)
- * .setPrefetchDistance(50)
- * .build());
+ * usersList = LivePagedListBuilder<>(
+ * userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
* }
* }
*
diff --git a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
index 51a6e37..7a0b81a 100644
--- a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
+++ b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
@@ -48,18 +48,14 @@
* {@literal @}Dao
* interface UserDao {
* {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract LivePagedListProvider<Integer, User> usersByLastName();
+ * public abstract DataSource.Factory<Integer, User> usersByLastName();
* }
*
* class MyViewModel extends ViewModel {
* public final LiveData<PagedList<User>> usersList;
* public MyViewModel(UserDao userDao) {
- * usersList = userDao.usersByLastName().create(
- * /* initial load position {@literal *}/ 0,
- * new PagedList.Config.Builder()
- * .setPageSize(50)
- * .setPrefetchDistance(50)
- * .build());
+ * usersList = LivePagedListBuilder<>(
+ * userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
* }
* }
*
diff --git a/paging/runtime/src/main/java/android/arch/paging/PagedStorageDiffHelper.java b/paging/runtime/src/main/java/android/arch/paging/PagedStorageDiffHelper.java
index 6fc7039..d991b72 100644
--- a/paging/runtime/src/main/java/android/arch/paging/PagedStorageDiffHelper.java
+++ b/paging/runtime/src/main/java/android/arch/paging/PagedStorageDiffHelper.java
@@ -26,8 +26,8 @@
}
static <T> DiffUtil.DiffResult computeDiff(
- final PagedStorage<?, T> oldList,
- final PagedStorage<?, T> newList,
+ final PagedStorage<T> oldList,
+ final PagedStorage<T> newList,
final DiffCallback<T> diffCallback) {
final int oldOffset = oldList.computeLeadingNulls();
final int newOffset = newList.computeLeadingNulls();
@@ -131,8 +131,8 @@
* immediately after dispatching this diff.
*/
static <T> void dispatchDiff(ListUpdateCallback callback,
- final PagedStorage<?, T> oldList,
- final PagedStorage<?, T> newList,
+ final PagedStorage<T> oldList,
+ final PagedStorage<T> newList,
final DiffUtil.DiffResult diffResult) {
final int trailingOld = oldList.computeTrailingNulls();
diff --git a/percent/build.gradle b/percent/build.gradle
index aecaa28..ccee3d2 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-compat')
@@ -18,8 +23,11 @@
}
supportLibrary {
- name 'Android Percent Support Library'
- publish true
- inceptionYear '2015'
- description 'Android Percent Support Library'
+ name = "Android Percent Support Library"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "Android Percent Support Library"
+ legacySourceLocation = true
}
diff --git a/persistence/db-framework/build.gradle b/persistence/db-framework/build.gradle
index 0961ba5..8607bf6 100644
--- a/persistence/db-framework/build.gradle
+++ b/persistence/db-framework/build.gradle
@@ -14,53 +14,33 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-
- }
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
- compile libs.support.annotations
- compile project(":persistence:db")
+ api libs.support.annotations
+ api project(":persistence:db")
}
+
createAndroidCheckstyle(project)
-android.libraryVariants.all { variant ->
- def name = variant.buildType.name
- def suffix = name.capitalize()
- def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
- dependsOn variant.javaCompile
- from variant.javaCompile.destinationDir
- destinationDir new File(project.buildDir, "libJar")
- }
-}
-
-version = LibraryVersions.ROOM.toString()
supportLibrary {
- name 'Android Support SQLite - Framework Implementation'
- publish true
- inceptionYear '2017'
- description "The implementation of Support SQLite library using the framework code."
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Support SQLite - Framework Implementation"
+ publish = true
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.PERSISTENCE
+ inceptionYear = "2017"
+ description = "The implementation of Support SQLite library using the framework code."
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/persistence/db-framework/lint-baseline.xml b/persistence/db-framework/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/persistence/db-framework/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/persistence/db/build.gradle b/persistence/db/build.gradle
index 4757a79..085676d 100644
--- a/persistence/db/build.gradle
+++ b/persistence/db/build.gradle
@@ -14,37 +14,27 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-
- }
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
- compile libs.support.annotations
+ api libs.support.annotations
}
+
createAndroidCheckstyle(project)
+// Used by testCompile in room-compiler
android.libraryVariants.all { variant ->
def name = variant.buildType.name
def suffix = name.capitalize()
@@ -55,11 +45,12 @@
}
}
-version = LibraryVersions.ROOM.toString()
supportLibrary {
- name 'Android DB'
- publish true
- inceptionYear '2017'
- description "Android DB"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android DB"
+ publish = true
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.PERSISTENCE
+ inceptionYear = "2017"
+ description = "Android DB"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/persistence/db/lint-baseline.xml b/persistence/db/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/persistence/db/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/recommendation/build.gradle b/recommendation/build.gradle
index 0c38487..f6adcb9 100644
--- a/recommendation/build.gradle
+++ b/recommendation/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -11,8 +16,11 @@
}
supportLibrary {
- name 'Android Support Recommendation'
- publish true
- inceptionYear '2015'
- description 'Android Support Recommendation'
+ name = "Android Support Recommendation"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "Android Support Recommendation"
+ legacySourceLocation = true
}
diff --git a/room/common/build.gradle b/room/common/build.gradle
index 4336583..21441a2 100644
--- a/room/common/build.gradle
+++ b/room/common/build.gradle
@@ -14,10 +14,13 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension;
-apply plugin: android.support.SupportJavaLibraryPlugin
+plugins {
+ id("SupportJavaLibraryPlugin")
+}
dependencies {
compile libs.support.annotations
@@ -27,11 +30,12 @@
createAndroidCheckstyle(project)
-version = LibraryVersions.ROOM.toString()
supportLibrary {
- name 'Android Room-Common'
- publish true
- inceptionYear '2017'
- description "Android Room-Common"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Room-Common"
+ publish = true
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.ROOM
+ inceptionYear = "2017"
+ description = "Android Room-Common"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/room/compiler/build.gradle b/room/compiler/build.gradle
index 222a338..eb9628c 100644
--- a/room/compiler/build.gradle
+++ b/room/compiler/build.gradle
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
@@ -78,9 +80,11 @@
createKotlinCheckstyle(project)
supportLibrary {
- name 'Android Room Compiler'
- publish true
- inceptionYear '2017'
- description "Android Room annotation processor"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Room Compiler"
+ publish = true
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.ROOM
+ inceptionYear = "2017"
+ description = "Android Room annotation processor"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt
index ad7621f..76051c8 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt
@@ -34,7 +34,6 @@
/**
* The annotation processor for Room.
*/
-@SupportedSourceVersion(SourceVersion.RELEASE_7)
class RoomProcessor : BasicAnnotationProcessor() {
override fun initSteps(): MutableIterable<ProcessingStep>? {
val context = Context(processingEnv)
@@ -45,6 +44,10 @@
return Context.ARG_OPTIONS.toMutableSet()
}
+ override fun getSupportedSourceVersion(): SourceVersion {
+ return SourceVersion.latest()
+ }
+
class DatabaseProcessingStep(context: Context) : ContextBoundProcessingStep(context) {
override fun process(elementsByAnnotation: SetMultimap<Class<out Annotation>, Element>)
: MutableSet<Element> {
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
index 85e89d1..d165b55 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
@@ -85,7 +85,8 @@
syntaxErrors)
}
- override fun visitCommon_table_expression(ctx: SQLiteParser.Common_table_expressionContext): Void? {
+ override fun visitCommon_table_expression(
+ ctx: SQLiteParser.Common_table_expressionContext): Void? {
val tableName = ctx.table_name()?.text
if (tableName != null) {
withClauseNames.add(unescapeIdentifier(tableName))
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt
index 535e116..f4cd5f3 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt
@@ -333,7 +333,8 @@
relationElement: VariableElement)
: android.arch.persistence.room.vo.Relation? {
- val asTypeElement = MoreTypes.asTypeElement(MoreElements.asVariable(relationElement).asType())
+ val asTypeElement = MoreTypes.asTypeElement(
+ MoreElements.asVariable(relationElement).asType())
if (detectReferenceRecursion(asTypeElement)) {
return null
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt
index 4abf13d..631f511 100644
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt
@@ -1808,7 +1808,9 @@
@Embedded
MyEntity myEntity;
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyEntity -> foo.bar.MyEntity"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyEntity -> foo.bar.MyEntity"))
}
@Test
@@ -1825,7 +1827,9 @@
MyEntity myEntity;
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
}
@Test
@@ -1840,7 +1844,9 @@
MyEntity myEntity;
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
}
@Test
@@ -1856,7 +1862,9 @@
MyEntity myEntity;
}
""") { _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
}
fun okTableName() {
val annotation = mapOf("tableName" to "\"foo bar\"")
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt
index 4f14fdb..6fdb300 100644
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt
@@ -715,7 +715,9 @@
@Embedded
MyPojo myPojo;
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyPojo -> foo.bar.MyPojo"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyPojo -> foo.bar.MyPojo"))
}
@Test
@@ -735,7 +737,9 @@
MyPojo myPojo;
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyPojo -> foo.bar.MyPojo.MyEntity -> foo.bar.MyPojo"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyPojo -> foo.bar.MyPojo.MyEntity -> foo.bar.MyPojo"))
}
@Test
@@ -751,7 +755,9 @@
MyPojo myPojo;
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
}
@Test
@@ -765,7 +771,9 @@
MyPojo myPojo;
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
}
@Test
@@ -783,7 +791,9 @@
MyPojo myPojo;
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo.B -> foo.bar.MyPojo"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo.B -> foo.bar.MyPojo"))
}
@Test
@@ -801,7 +811,9 @@
A a;
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyPojo.A -> foo.bar.MyPojo.B -> foo.bar.MyPojo.A"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyPojo.A -> foo.bar.MyPojo.B -> foo.bar.MyPojo.A"))
}
@Test
@@ -825,7 +837,9 @@
MyPojo myPojo;
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyPojo -> foo.bar.MyPojo.C -> foo.bar.MyPojo"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyPojo -> foo.bar.MyPojo.C -> foo.bar.MyPojo"))
}
@Test
@@ -849,7 +863,9 @@
static class C {
}
"""){ _, _ ->
- }.failsToCompile().withErrorContaining(ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format("foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+ "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
}
fun singleRun(code: String, vararg jfos:JavaFileObject, handler: (Pojo) -> Unit)
diff --git a/room/gradle/wrapper/gradle-wrapper.properties b/room/gradle/wrapper/gradle-wrapper.properties
index a5292b0..2f8bf03 100644
--- a/room/gradle/wrapper/gradle-wrapper.properties
+++ b/room/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-bin.zip
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index f4df9cf..f1f5139 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -36,6 +36,14 @@
}
}
}
+
+ signingConfigs {
+ debug {
+ // Use a local debug keystore to avoid build server issues.
+ storeFile project.rootProject.init.debugKeystore
+ }
+ }
+
testOptions {
unitTests.returnDefaultValues = true
}
@@ -59,11 +67,11 @@
kapt project(":room:compiler")
kaptAndroidTest project(":room:compiler")
- androidTestCompile(libs.test_runner) {
+ androidTestImplementation(libs.test_runner) {
exclude module: 'support-annotations'
exclude module: 'hamcrest-core'
}
- androidTestCompile(libs.espresso_core, {
+ androidTestImplementation(libs.espresso_core, {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: "hamcrest-core"
})
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/StringToIntListConverters.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/StringToIntListConverters.kt
index 5c6620d..ec8fd4a 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/StringToIntListConverters.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/StringToIntListConverters.kt
@@ -8,9 +8,11 @@
// Specifying that a static method should be generated. Otherwise, the compiler looks for the
// constructor of the class, and a object has a private constructor.
@JvmStatic
- fun stringToIntList(data: String?): List<Int>? = if (data == null) null else StringUtil.splitToIntList(data)
+ fun stringToIntList(data: String?): List<Int>? =
+ if (data == null) null else StringUtil.splitToIntList(data)
@TypeConverter
@JvmStatic
- fun intListToString(ints: List<Int>?): String? = if (ints == null) null else StringUtil.joinIntoString(ints)
+ fun intListToString(ints: List<Int>?): String? =
+ if (ints == null) null else StringUtil.joinIntoString(ints)
}
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 6fefc29..b063595 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -34,6 +34,14 @@
}
}
}
+
+ signingConfigs {
+ debug {
+ // Use a local debug keystore to avoid build server issues.
+ storeFile project.rootProject.init.debugKeystore
+ }
+ }
+
testOptions {
unitTests.returnDefaultValues = true
}
@@ -47,42 +55,42 @@
}
dependencies {
- compile project(":room:common")
- compile project(":persistence:db")
- compile project(":persistence:db-framework")
- compile project(':room:runtime')
- compile project(':arch:runtime')
- compile project(':arch:common')
- compile project(':paging:common')
- compile project(':lifecycle:extensions')
- compile project(':lifecycle:runtime')
- compile project(':lifecycle:common')
- compile project(':room:rxjava2')
- compile project(':paging:runtime')
+ implementation project(":room:common")
+ implementation project(":persistence:db")
+ implementation project(":persistence:db-framework")
+ implementation project(':room:runtime')
+ implementation project(':arch:runtime')
+ implementation project(':arch:common')
+ implementation project(':paging:common')
+ implementation project(':lifecycle:extensions')
+ implementation project(':lifecycle:runtime')
+ implementation project(':lifecycle:common')
+ implementation project(':room:rxjava2')
+ implementation project(':paging:runtime')
- compile libs.support.recyclerview, libs.support_exclude_config
- compile libs.support.app_compat, libs.support_exclude_config
+ implementation libs.support.recyclerview, libs.support_exclude_config
+ implementation libs.support.app_compat, libs.support_exclude_config
annotationProcessor project(":room:compiler")
androidTestAnnotationProcessor project(":room:compiler")
// IJ's gradle integration just cannot figure this out ...
- androidTestCompile project(':lifecycle:extensions')
- androidTestCompile project(':lifecycle:common')
- androidTestCompile project(':lifecycle:runtime')
- androidTestCompile project(':room:testing')
- androidTestCompile project(':room:rxjava2')
- androidTestCompile project(':arch:core-testing')
- androidTestCompile libs.rx_java
- androidTestCompile(libs.test_runner) {
+ androidTestImplementation project(':lifecycle:extensions')
+ androidTestImplementation project(':lifecycle:common')
+ androidTestImplementation project(':lifecycle:runtime')
+ androidTestImplementation project(':room:testing')
+ androidTestImplementation project(':room:rxjava2')
+ androidTestImplementation project(':arch:core-testing')
+ androidTestImplementation libs.rx_java
+ androidTestImplementation(libs.test_runner) {
exclude module: 'support-annotations'
}
- androidTestCompile(libs.espresso_core, {
+ androidTestImplementation(libs.espresso_core, {
exclude group: 'com.android.support', module: 'support-annotations'
})
androidTestImplementation libs.mockito_core, { exclude group: 'net.bytebuddy' } // DexMaker has it's own MockMaker
androidTestImplementation libs.dexmaker_mockito, { exclude group: 'net.bytebuddy' } // DexMaker has it's own MockMaker
- testCompile libs.junit
+ testImplementation libs.junit
}
createAndroidCheckstyle(project)
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 cb2bb03..0b184a9 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
@@ -190,8 +190,12 @@
@Query("SELECT * FROM user where mAge > :age")
public abstract LivePagedListProvider<Integer, User> loadPagedByAge_legacy(int age);
+ // TODO: switch to PositionalDataSource once Room supports it
@Query("SELECT * FROM user ORDER BY mAge DESC")
- public abstract TiledDataSource<User> loadUsersByAgeDesc();
+ public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
+
+ @Query("SELECT * FROM user ORDER BY mAge DESC")
+ public abstract TiledDataSource<User> loadUsersByAgeDesc_legacy();
@Query("DELETE FROM User WHERE mId IN (:ids) AND mAge == :age")
public abstract int deleteByAgeAndIds(int age, List<Integer> ids);
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 c546531..b54abe8 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
@@ -63,12 +63,12 @@
validateUsersAsPagedList(new LivePagedListFactory() {
@Override
public LiveData<PagedList<User>> create() {
- return new LivePagedListBuilder<Integer, User>()
- .setPagingConfig(new PagedList.Config.Builder()
+ return new LivePagedListBuilder<>(
+ mUserDao.loadPagedByAge(3),
+ new PagedList.Config.Builder()
.setPageSize(10)
.setPrefetchDistance(1)
.setInitialLoadSizeHint(10).build())
- .setDataSourceFactory(mUserDao.loadPagedByAge(3))
.build();
}
});
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
index 8226759..f0285a0 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
@@ -45,8 +45,17 @@
mUserDao.deleteEverything();
}
+ // TODO: delete this and factory abstraction when LivePagedListProvider is removed
+ @Test
+ public void limitOffsetDataSource_legacyTiledDataSource() {
+ // Simple verification that loading a TiledDataSource still works.
+ LimitOffsetDataSource<User> dataSource =
+ (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc_legacy();
+ assertThat(dataSource.countItems(), is(0));
+ }
+
private LimitOffsetDataSource<User> loadUsersByAgeDesc() {
- return (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc();
+ return (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc().create();
}
@Test
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
index 291cfd6..f076cf1 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
@@ -203,10 +203,8 @@
@Test
public void pagedList() {
- LiveData<PagedList<Entity1>> pagedList = new LivePagedListBuilder<Integer, Entity1>()
- .setDataSourceFactory(mDao.pagedList())
- .setPagingConfig(10)
- .build();
+ LiveData<PagedList<Entity1>> pagedList =
+ new LivePagedListBuilder<>(mDao.pagedList(), 10).build();
observeForever(pagedList);
drain();
assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
@@ -228,6 +226,7 @@
mDao.insert(new Entity1(2, "bar"));
drain();
resetTransactionCount();
+ @SuppressWarnings("deprecation")
TiledDataSource<Entity1> dataSource = mDao.dataSource();
dataSource.loadRange(0, 10);
assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
index 89d16b7e..d8cd8d4 100644
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
@@ -83,13 +83,12 @@
private static <K> LiveData<PagedList<Customer>> getLivePagedList(
K initialLoadKey, DataSource.Factory<K, Customer> dataSourceFactory) {
- return new LivePagedListBuilder<K, Customer>()
+ PagedList.Config config = new PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(false)
+ .build();
+ return new LivePagedListBuilder<>(dataSourceFactory, config)
.setInitialLoadKey(initialLoadKey)
- .setPagingConfig(new PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(false)
- .build())
- .setDataSourceFactory(dataSourceFactory)
.build();
}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
index cdd464e..0c79aee 100644
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
@@ -16,7 +16,6 @@
package android.arch.persistence.room.integration.testapp;
-import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
@@ -75,7 +74,7 @@
mAdapter.setList(items);
}
});
- final Button button = findViewById(R.id.button);
+ final Button button = findViewById(R.id.addButton);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -114,11 +113,4 @@
protected boolean useKeyedQuery() {
return false;
}
-
- private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
-
- @Override
- public LifecycleRegistry getLifecycle() {
- return mLifecycleRegistry;
- }
}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
index a38d6ae..2db543b 100644
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
+++ b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
@@ -21,6 +21,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -60,7 +61,6 @@
@Override
public boolean isInvalid() {
mDb.getInvalidationTracker().refreshVersionsSync();
-
return super.isInvalid();
}
@@ -76,30 +76,48 @@
}
@Override
- public int countItemsBefore(@NonNull String customerName) {
- return mCustomerDao.customerNameCountBefore(customerName);
+ public void loadInitial(@Nullable String customerName, int initialLoadSize,
+ boolean enablePlaceholders, @NonNull InitialLoadCallback<Customer> callback) {
+ List<Customer> list;
+ if (customerName != null) {
+ // initial keyed load - load before 'customerName',
+ // and load after last item in before list
+ int pageSize = initialLoadSize / 2;
+ String key = customerName;
+ list = mCustomerDao.customerNameLoadBefore(key, pageSize);
+ Collections.reverse(list);
+ if (!list.isEmpty()) {
+ key = getKey(list.get(list.size() - 1));
+ }
+ list.addAll(mCustomerDao.customerNameLoadAfter(key, pageSize));
+ } else {
+ list = mCustomerDao.customerNameInitial(initialLoadSize);
+ }
+
+ if (enablePlaceholders && !list.isEmpty()) {
+ String firstKey = getKey(list.get(0));
+ String lastKey = getKey(list.get(list.size() - 1));
+
+ // only bother counting if placeholders are desired
+ final int position = mCustomerDao.customerNameCountBefore(firstKey);
+ final int count = position + list.size() + mCustomerDao.customerNameCountAfter(lastKey);
+ callback.onResult(list, position, count);
+ } else {
+ callback.onResult(list);
+ }
}
@Override
- public int countItemsAfter(@NonNull String customerName) {
- return mCustomerDao.customerNameCountAfter(customerName);
+ public void loadAfter(@NonNull String currentEndKey, int pageSize,
+ @NonNull LoadCallback<Customer> callback) {
+ callback.onResult(mCustomerDao.customerNameLoadAfter(currentEndKey, pageSize));
}
- @Nullable
@Override
- public List<Customer> loadInitial(int pageSize) {
- return mCustomerDao.customerNameInitial(pageSize);
- }
-
- @Nullable
- @Override
- public List<Customer> loadBefore(@NonNull String customerName, int pageSize) {
- return mCustomerDao.customerNameLoadBefore(customerName, pageSize);
- }
-
- @Nullable
- @Override
- public List<Customer> loadAfter(@Nullable String customerName, int pageSize) {
- return mCustomerDao.customerNameLoadAfter(customerName, pageSize);
+ public void loadBefore(@NonNull String currentBeginKey, int pageSize,
+ @NonNull LoadCallback<Customer> callback) {
+ List<Customer> list = mCustomerDao.customerNameLoadBefore(currentBeginKey, pageSize);
+ Collections.reverse(list);
+ callback.onResult(list);
}
}
diff --git a/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml b/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
index 71dae1b..34e4491 100644
--- a/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
+++ b/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
@@ -35,7 +35,7 @@
android:paddingTop="@dimen/activity_vertical_margin"
/>
<Button
- android:id="@+id/button"
+ android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
diff --git a/room/migration/build.gradle b/room/migration/build.gradle
index 3f5bdd6..086aac7 100644
--- a/room/migration/build.gradle
+++ b/room/migration/build.gradle
@@ -14,10 +14,13 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension;
-apply plugin: android.support.SupportJavaLibraryPlugin
+plugins {
+ id("SupportJavaLibraryPlugin")
+}
sourceSets {
test.java.srcDirs += 'src/tests/kotlin'
@@ -31,11 +34,12 @@
testCompile libs.mockito_core
}
-version = LibraryVersions.ROOM.toString()
supportLibrary {
- name 'Android Room Migration'
- publish true
- inceptionYear '2017'
- description "Android Room Migration"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Room Migration"
+ publish = true
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.ROOM
+ inceptionYear = "2017"
+ description = "Android Room Migration"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index a170c8e..0570fe8 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -14,34 +14,22 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-
}
buildTypes.all {
consumerProguardFiles 'proguard-rules.pro'
}
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
- }
}
dependencies {
@@ -49,15 +37,15 @@
api project(":persistence:db-framework")
api project(":persistence:db")
api project(":arch:runtime")
- provided project(":paging:common")
- provided project(":lifecycle:runtime")
- provided project(":lifecycle:extensions")
- compile libs.support.core_utils, libs.support_exclude_config
+ compileOnly project(":paging:common")
+ compileOnly project(":lifecycle:runtime")
+ compileOnly project(":lifecycle:extensions")
+ api libs.support.core_utils, libs.support_exclude_config
- testCompile project(":arch:core-testing")
- testCompile libs.junit
- testCompile libs.mockito_core
- testCompile libs.support.annotations
+ testImplementation project(":arch:core-testing")
+ testImplementation libs.junit
+ testImplementation libs.mockito_core
+ testImplementation libs.support.annotations
androidTestImplementation libs.junit
androidTestImplementation libs.test_runner, { exclude module: 'support-annotations' }
@@ -66,6 +54,7 @@
createAndroidCheckstyle(project)
+// Used by testCompile in room-compiler
android.libraryVariants.all { variant ->
def name = variant.buildType.name
def suffix = name.capitalize()
@@ -76,11 +65,12 @@
}
}
-version = LibraryVersions.ROOM.toString()
supportLibrary {
- name 'Android Room-Runtime'
- publish true
- inceptionYear '2017'
- description "Android Room-Runtime"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Room-Runtime"
+ publish = true
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.ROOM
+ inceptionYear = "2017"
+ description = "Android Room-Runtime"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/room/runtime/lint-baseline.xml b/room/runtime/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/room/runtime/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java b/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
index 6f4aa68..373b122 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
@@ -45,6 +45,7 @@
*
* @return An SQL query that can delete or update instances of T.
*/
+ @Override
protected abstract String createQuery();
/**
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/Room.java b/room/runtime/src/main/java/android/arch/persistence/room/Room.java
index 2850b55..9b168fc 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/Room.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/Room.java
@@ -72,6 +72,7 @@
return new RoomDatabase.Builder<>(context, klass, null);
}
+ @SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
@NonNull
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
final String fullPackage = klass.getPackage().getName();
diff --git a/room/rxjava2/build.gradle b/room/rxjava2/build.gradle
index 7f86842..efdfc48 100644
--- a/room/rxjava2/build.gradle
+++ b/room/rxjava2/build.gradle
@@ -14,66 +14,39 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
- compile project(":room:common")
- compile project(":room:runtime")
- compile project(":arch:runtime")
- compile libs.support.core_utils, libs.support_exclude_config
- compile libs.rx_java
- testCompile libs.junit
- testCompile libs.mockito_core
- testCompile project(":arch:core-testing")
+ api project(":room:common")
+ api project(":room:runtime")
+ api project(":arch:runtime")
+ api libs.support.core_utils, libs.support_exclude_config
+ api libs.rx_java
+ testImplementation libs.junit
+ testImplementation libs.mockito_core
+ testImplementation project(":arch:core-testing")
}
createAndroidCheckstyle(project)
-android.libraryVariants.all { variant ->
- def name = variant.buildType.name
- def suffix = name.capitalize()
- project.tasks.create(name: "jar${suffix}", type: Jar){
- dependsOn variant.javaCompile
- from variant.javaCompile.destinationDir
- destinationDir new File(project.buildDir, "libJar")
- }
-}
-
-version = LibraryVersions.ROOM.toString()
supportLibrary {
- name 'Android Room RXJava2'
- publish true
- inceptionYear '2017'
- description "Android Room RXJava2"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Room RXJava2"
+ publish = true
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.ROOM
+ inceptionYear = "2017"
+ description = "Android Room RXJava2"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/room/rxjava2/lint-baseline.xml b/room/rxjava2/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/room/rxjava2/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/room/testing/build.gradle b/room/testing/build.gradle
index 078de88..876300a 100644
--- a/room/testing/build.gradle
+++ b/room/testing/build.gradle
@@ -14,59 +14,39 @@
* limitations under the License.
*/
+import android.support.LibraryGroups
import android.support.LibraryVersions
import android.support.SupportLibraryExtension
-apply plugin: android.support.FlatfootAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
android {
- compileSdkVersion tools.current_sdk
- buildToolsVersion tools.build_tools_version
-
defaultConfig {
minSdkVersion flatfoot.min_sdk
- targetSdkVersion tools.current_sdk
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
- compile project(":room:common")
- compile project(":room:runtime")
- compile project(":persistence:db")
- compile project(":persistence:db-framework")
- compile project(":room:migration")
- compile project(":arch:runtime")
- compile libs.support.core_utils, libs.support_exclude_config
- compile libs.junit
+ api project(":room:common")
+ api project(":room:runtime")
+ api project(":persistence:db")
+ api project(":persistence:db-framework")
+ api project(":room:migration")
+ api project(":arch:runtime")
+ api libs.support.core_utils, libs.support_exclude_config
+ api libs.junit
}
createAndroidCheckstyle(project)
-android.libraryVariants.all { variant ->
- def name = variant.buildType.name
- def suffix = name.capitalize()
- project.tasks.create(name: "jar${suffix}", type: Jar){
- dependsOn variant.javaCompile
- from variant.javaCompile.destinationDir
- destinationDir new File(project.buildDir, "libJar")
- }
-}
-
-version = LibraryVersions.ROOM.toString()
supportLibrary {
- name 'Android Room Testimg'
- publish true
- inceptionYear '2017'
- description "Android Room Testing"
- url SupportLibraryExtension.ARCHITECTURE_URL
+ name = "Android Room Testing"
+ publish = true
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.ROOM
+ inceptionYear = "2017"
+ description = "Android Room Testing"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
}
\ No newline at end of file
diff --git a/room/testing/lint-baseline.xml b/room/testing/lint-baseline.xml
new file mode 100644
index 0000000..dee4355
--- /dev/null
+++ b/room/testing/lint-baseline.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+ <issue
+ id="InvalidPackage"
+ message="Invalid package reference in library; not included in Android: `java.lang.management`. Referenced from `org.junit.internal.runners.statements.FailOnTimeout`.">
+ <location
+ file="../../../../prebuilts/tools/common/m2/repository/junit/junit/4.12/junit-4.12.jar"/>
+ </issue>
+
+</issues>
diff --git a/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java b/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java
index 18e0a14..2e93bbe 100644
--- a/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java
+++ b/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java
@@ -341,7 +341,7 @@
return 0;
}
- class MigratingDelegate extends RoomOpenHelperDelegate {
+ static class MigratingDelegate extends RoomOpenHelperDelegate {
private final boolean mVerifyDroppedTables;
MigratingDelegate(DatabaseBundle databaseBundle, boolean verifyDroppedTables) {
diff --git a/samples/SupportDesignDemos/src/main/res/layout/design_text_input.xml b/samples/SupportDesignDemos/src/main/res/layout/design_text_input.xml
index 6dbbb92..fc9ff00 100644
--- a/samples/SupportDesignDemos/src/main/res/layout/design_text_input.xml
+++ b/samples/SupportDesignDemos/src/main/res/layout/design_text_input.xml
@@ -96,6 +96,22 @@
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
+ android:id="@+id/input_auto_complete"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ app:counterEnabled="true"
+ app:counterMaxLength="30">
+
+ <AutoCompleteTextView
+ android:id="@+id/edit_auto_complete"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/form_auto_complete" />
+
+ </android.support.design.widget.TextInputLayout>
+
+ <android.support.design.widget.TextInputLayout
android:id="@+id/input_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/samples/SupportDesignDemos/src/main/res/values/strings.xml b/samples/SupportDesignDemos/src/main/res/values/strings.xml
index 74630d9..6ebe24c 100644
--- a/samples/SupportDesignDemos/src/main/res/values/strings.xml
+++ b/samples/SupportDesignDemos/src/main/res/values/strings.xml
@@ -69,6 +69,7 @@
<string name="form_username">Username</string>
<string name="form_email">Email address</string>
<string name="form_description">Description</string>
+ <string name="form_auto_complete">Auto complete</string>
<string name="form_password">Password field</string>
<string name="show_error">Show error</string>
<string name="toggle_enabled">Toggle enabled</string>
diff --git a/testutils/build.gradle b/testutils/build.gradle
index f1be70d..15eabaf 100644
--- a/testutils/build.gradle
+++ b/testutils/build.gradle
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-apply plugin: android.support.SupportAndroidLibraryPlugin
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
compile libs.junit
@@ -27,4 +29,8 @@
lintOptions {
disable 'InvalidPackage' // Lint is unhappy about junit package
}
+}
+
+supportLibrary {
+ legacySourceLocation = true
}
\ No newline at end of file
diff --git a/transition/build.gradle b/transition/build.gradle
index be366d6..326a681 100644
--- a/transition/build.gradle
+++ b/transition/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -44,8 +49,11 @@
}
supportLibrary {
- name 'Android Transition Support Library'
- publish true
- inceptionYear '2016'
- description 'Android Transition Support Library'
+ name = "Android Transition Support Library"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2016"
+ description = "Android Transition Support Library"
+ legacySourceLocation = true
}
diff --git a/tv-provider/build.gradle b/tv-provider/build.gradle
index 0545458..60c2b2e 100644
--- a/tv-provider/build.gradle
+++ b/tv-provider/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -14,8 +19,11 @@
}
supportLibrary {
- name 'Android Support TV Provider'
- publish true
- inceptionYear '2017'
- description 'Android Support Library for TV Provider'
+ name = "Android Support TV Provider"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2017"
+ description = "Android Support Library for TV Provider"
+ legacySourceLocation = true
}
\ No newline at end of file
diff --git a/v13/build.gradle b/v13/build.gradle
index 06774fd..bd01c55 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -23,8 +28,11 @@
}
supportLibrary {
- name 'Android Support Library v13'
- publish true
- inceptionYear '2011'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android Support Library v13"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2011"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
diff --git a/v14/preference/build.gradle b/v14/preference/build.gradle
index 8c66d6a..f68379a 100644
--- a/v14/preference/build.gradle
+++ b/v14/preference/build.gradle
@@ -14,7 +14,12 @@
* limitations under the License
*/
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-v4')
@@ -34,8 +39,11 @@
}
supportLibrary {
- name 'Android Support Preference v14'
- publish true
- inceptionYear '2015'
- description 'Android Support Preference v14'
+ name = "Android Support Preference v14"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "Android Support Preference v14"
+ legacySourceLocation = true
}
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index baefe46..4ebec71 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-compat')
@@ -31,8 +36,11 @@
}
supportLibrary {
- name 'Android Support Leanback v17'
- publish true
- inceptionYear '2014'
- description 'Android Support Leanback v17'
+ name = "Android Support Leanback v17"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2014"
+ description = "Android Support Leanback v17"
+ legacySourceLocation = true
}
diff --git a/v17/preference-leanback/build.gradle b/v17/preference-leanback/build.gradle
index f573908..85d2577 100644
--- a/v17/preference-leanback/build.gradle
+++ b/v17/preference-leanback/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-v4')
@@ -24,8 +29,11 @@
}
supportLibrary {
- name 'Android Support Leanback Preference v17'
- publish true
- inceptionYear '2015'
- description 'Android Support Leanback Preference v17'
+ name = "Android Support Leanback Preference v17"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "Android Support Leanback Preference v17"
+ legacySourceLocation = true
}
\ No newline at end of file
diff --git a/v4/build.gradle b/v4/build.gradle
index 737db27..0a17101 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-compat')
@@ -17,8 +22,11 @@
}
supportLibrary {
- name 'Android Support Library v4'
- publish true
- inceptionYear '2011'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android Support Library v4"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2011"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index 2d57ac4..308a122 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -32,8 +37,11 @@
}
supportLibrary {
- name 'Android AppCompat Library v7'
- publish true
- inceptionYear '2011'
- description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ name = "Android AppCompat Library v7"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2011"
+ description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ legacySourceLocation = true
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatAutoCompleteTextView.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatAutoCompleteTextView.java
index 5b0a2f8..e41bec7 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatAutoCompleteTextView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatAutoCompleteTextView.java
@@ -29,6 +29,8 @@
import android.support.v7.appcompat.R;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.AutoCompleteTextView;
/**
@@ -177,4 +179,10 @@
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatCheckedTextView.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatCheckedTextView.java
index 921f0a2..dca409c 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatCheckedTextView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatCheckedTextView.java
@@ -20,6 +20,8 @@
import android.support.annotation.DrawableRes;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.CheckedTextView;
/**
@@ -79,4 +81,10 @@
mTextHelper.applyCompoundDrawablesTints();
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
index 406e364..6831fcb 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
@@ -28,6 +28,8 @@
import android.support.v4.view.TintableBackgroundView;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.EditText;
/**
@@ -159,4 +161,10 @@
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatHintHelper.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatHintHelper.java
new file mode 100644
index 0000000..0d30fb7
--- /dev/null
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatHintHelper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.view.View;
+import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+class AppCompatHintHelper {
+
+ static InputConnection onCreateInputConnection(InputConnection ic, EditorInfo outAttrs,
+ View view) {
+ if (ic != null && outAttrs.hintText == null) {
+ // If we don't have a hint and the parent implements WithHint, use its hint for the
+ // EditorInfo. This allows us to display a hint in 'extract mode'.
+ ViewParent parent = view.getParent();
+ while (parent instanceof View) {
+ if (parent instanceof WithHint) {
+ outAttrs.hintText = ((WithHint) parent).getHint();
+ break;
+ }
+ parent = parent.getParent();
+ }
+ }
+ return ic;
+ }
+
+}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
index 8060d7d..b71b08a 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
@@ -29,6 +29,8 @@
import android.support.v7.appcompat.R;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.MultiAutoCompleteTextView;
/**
@@ -177,4 +179,10 @@
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextView.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextView.java
index cfa6a2a..d813277 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextView.java
@@ -31,6 +31,8 @@
import android.support.v4.widget.TextViewCompat;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.TextView;
/**
@@ -361,4 +363,10 @@
}
return new int[0];
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/main/java/android/support/v7/widget/Toolbar.java
index 45e2583..f383e90c 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/Toolbar.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/Toolbar.java
@@ -56,6 +56,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -2366,12 +2367,20 @@
@Override
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
ensureCollapseButtonView();
- if (mCollapseButtonView.getParent() != Toolbar.this) {
+ ViewParent collapseButtonParent = mCollapseButtonView.getParent();
+ if (collapseButtonParent != Toolbar.this) {
+ if (collapseButtonParent instanceof ViewGroup) {
+ ((ViewGroup) collapseButtonParent).removeView(mCollapseButtonView);
+ }
addView(mCollapseButtonView);
}
mExpandedActionView = item.getActionView();
mCurrentExpandedItem = item;
- if (mExpandedActionView.getParent() != Toolbar.this) {
+ ViewParent expandedActionParent = mExpandedActionView.getParent();
+ if (expandedActionParent != Toolbar.this) {
+ if (expandedActionParent instanceof ViewGroup) {
+ ((ViewGroup) expandedActionParent).removeView(mExpandedActionView);
+ }
final LayoutParams lp = generateDefaultLayoutParams();
lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
lp.mViewType = LayoutParams.EXPANDED;
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
index 5ce1f8b..3d3c300 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
@@ -66,6 +66,10 @@
private TooltipPopup mPopup;
private boolean mFromTouch;
+ // The handler currently scheduled to show a tooltip, triggered by a hover
+ // (there can be only one).
+ private static TooltipCompatHandler sPendingHandler;
+
// The handler currently showing a tooltip (there can be only one).
private static TooltipCompatHandler sActiveHandler;
@@ -76,6 +80,15 @@
* @param tooltipText the tooltip text
*/
public static void setTooltipText(View view, CharSequence tooltipText) {
+ // The code below is not attempting to update the tooltip text
+ // for a pending or currently active tooltip, because it may lead
+ // to updating the wrong tooltipin in some rare cases (e.g. when
+ // action menu item views are recycled). Instead, the tooltip is
+ // canceled/hidden. This might still be the wrong tooltip,
+ // but hiding wrong tooltip is less disruptive UX.
+ if (sPendingHandler != null && sPendingHandler.mAnchor == view) {
+ setPendingHandler(null);
+ }
if (TextUtils.isEmpty(tooltipText)) {
if (sActiveHandler != null && sActiveHandler.mAnchor == view) {
sActiveHandler.hide();
@@ -119,8 +132,7 @@
if (mAnchor.isEnabled() && mPopup == null) {
mAnchorX = (int) event.getX();
mAnchorY = (int) event.getY();
- mAnchor.removeCallbacks(mShowRunnable);
- mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+ setPendingHandler(this);
}
break;
case MotionEvent.ACTION_HOVER_EXIT:
@@ -145,6 +157,7 @@
if (!ViewCompat.isAttachedToWindow(mAnchor)) {
return;
}
+ setPendingHandler(null);
if (sActiveHandler != null) {
sActiveHandler.hide();
}
@@ -180,7 +193,27 @@
Log.e(TAG, "sActiveHandler.mPopup == null");
}
}
- mAnchor.removeCallbacks(mShowRunnable);
+ if (sPendingHandler == this) {
+ setPendingHandler(null);
+ }
mAnchor.removeCallbacks(mHideRunnable);
}
+
+ private static void setPendingHandler(TooltipCompatHandler handler) {
+ if (sPendingHandler != null) {
+ sPendingHandler.cancelPendingShow();
+ }
+ sPendingHandler = handler;
+ if (sPendingHandler != null) {
+ sPendingHandler.scheduleShow();
+ }
+ }
+
+ private void scheduleShow() {
+ mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+ }
+
+ private void cancelPendingShow() {
+ mAnchor.removeCallbacks(mShowRunnable);
+ }
}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/WithHint.java b/v7/appcompat/src/main/java/android/support/v7/widget/WithHint.java
new file mode 100644
index 0000000..d14f483
--- /dev/null
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/WithHint.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public interface WithHint {
+ /**
+ * Returns the hint which is displayed in the floating label, if enabled.
+ *
+ * @return the hint, or null if there isn't one set, or the hint is not enabled.
+ */
+ @Nullable
+ CharSequence getHint();
+}
diff --git a/v7/cardview/build.gradle b/v7/cardview/build.gradle
index 76c3bf3..23d8076 100644
--- a/v7/cardview/build.gradle
+++ b/v7/cardview/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -15,8 +20,11 @@
}
supportLibrary {
- name 'Android Support CardView v7'
- publish true
- inceptionYear '2011'
- description 'Android Support CardView v7'
+ name = "Android Support CardView v7"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2011"
+ description = "Android Support CardView v7"
+ legacySourceLocation = true
}
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index d4d467d..052b9db 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-compat')
@@ -19,8 +24,11 @@
}
supportLibrary {
- name 'Android Support Grid Layout'
- publish true
- inceptionYear '2013'
- description 'Android Support Grid Layout'
+ name = "Android Support Grid Layout"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2013"
+ description = "Android Support Grid Layout"
+ legacySourceLocation = true
}
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index f301324..09e48df 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(":support-media-compat")
@@ -28,8 +33,11 @@
}
supportLibrary {
- name 'Android MediaRouter Support Library'
- publish true
- inceptionYear '2013'
- description 'Android MediaRouter Support Library'
+ name = "Android MediaRouter Support Library"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2013"
+ description = "Android MediaRouter Support Library"
+ legacySourceLocation = true
}
diff --git a/v7/mediarouter/res/values/themes.xml b/v7/mediarouter/res/values/themes.xml
index 586db92..8c6e97a 100644
--- a/v7/mediarouter/res/values/themes.xml
+++ b/v7/mediarouter/res/values/themes.xml
@@ -17,7 +17,6 @@
<resources>
<style name="Theme.MediaRouter" parent="ThemeOverlay.AppCompat.Dark">
- <item name="android:backgroundDimEnabled">true</item>
<item name="windowNoTitle">true</item>
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton</item>
@@ -39,7 +38,6 @@
</style>
<style name="Theme.MediaRouter.Light" parent="ThemeOverlay.AppCompat.Light">
- <item name="android:backgroundDimEnabled">true</item>
<item name="windowNoTitle">true</item>
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.Light.MediaRouteButton</item>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index d3f7020..fdbcf9a 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -121,8 +121,7 @@
}
public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(MediaRouterThemeHelper.createThemedContext(context, defStyleAttr), attrs,
- defStyleAttr);
+ super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index 0ab2eb1..17364ef 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -92,10 +92,8 @@
}
public MediaRouteChooserDialog(Context context, int theme) {
- // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
- // which may override our style settings. Passes our uppermost theme ID to prevent this.
- super(MediaRouterThemeHelper.createThemedContext(context, theme),
- theme == 0 ? MediaRouterThemeHelper.createThemeForDialog(context, theme) : theme);
+ super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
+ MediaRouterThemeHelper.createThemedDialogStyle(context));
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 4b9a17a..d89bf21e 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -201,12 +201,8 @@
}
public MediaRouteControllerDialog(Context context, int theme) {
- // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
- // which may override our style settings. Passes our uppermost theme ID to prevent this.
- super(MediaRouterThemeHelper.createThemedContext(context,
- MediaRouterThemeHelper.getAlertDialogResolvedTheme(context, theme)), theme == 0
- ? MediaRouterThemeHelper.createThemeForDialog(context, MediaRouterThemeHelper
- .getAlertDialogResolvedTheme(context, theme)) : theme);
+ super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true),
+ MediaRouterThemeHelper.createThemedDialogStyle(context));
mContext = getContext();
mControllerCallback = new MediaControllerCallback();
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
index 9ef218e..69e40ac 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
@@ -42,47 +42,76 @@
private MediaRouterThemeHelper() {
}
- /**
- * Creates a themed context based on the explicit style resource or the parent context's default
- * theme.
- * <p>
- * The theme which will be applied on top of the parent {@code context}'s theme is determined
- * by the primary color defined in the given {@code style}, or in the parent {@code context}.
+ static Context createThemedButtonContext(Context context) {
+ // Apply base Media Router theme.
+ context = new ContextThemeWrapper(context, getRouterThemeId(context));
+
+ // Apply custom Media Router theme.
+ int style = getThemeResource(context, R.attr.mediaRouteTheme);
+ if (style != 0) {
+ context = new ContextThemeWrapper(context, style);
+ }
+
+ return context;
+ }
+
+ /*
+ * The following two methods are to be used in conjunction. They should be used to prepare
+ * the context and theme for a super class constructor (the latter method relies on the
+ * former method to properly prepare the context):
+ * super(context = createThemedDialogContext(context, theme),
+ * createThemedDialogStyle(context));
*
- * @param context the parent context
- * @param style the resource ID of the style against which to inflate this context, or
- * {@code 0} to use the parent {@code context}'s default theme.
- * @return The themed context.
+ * It will apply theme in the following order (style lookups will be done in reverse):
+ * 1) Current theme
+ * 2) Supplied theme
+ * 3) Base Media Router theme
+ * 4) Custom Media Router theme, if provided
*/
- static Context createThemedContext(Context context, int style) {
- // First, apply dialog property overlay.
- Context themedContext =
- new ContextThemeWrapper(context, getStyledRouterThemeId(context, style));
- int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
- return customizedThemeId == 0 ? themedContext
- : new ContextThemeWrapper(themedContext, customizedThemeId);
- }
+ static Context createThemedDialogContext(Context context, int theme, boolean alertDialog) {
+ // 1) Current theme is already applied to the context
- /**
- * Creates the theme resource ID intended to be used by dialogs.
- */
- static int createThemeForDialog(Context context, int style) {
- int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
- return customizedThemeId != 0 ? customizedThemeId : getStyledRouterThemeId(context, style);
- }
+ // 2) If no theme is supplied, look it up from the context (dialogTheme/alertDialogTheme)
+ if (theme == 0) {
+ theme = getThemeResource(context, !alertDialog
+ ? android.support.v7.appcompat.R.attr.dialogTheme
+ : android.support.v7.appcompat.R.attr.alertDialogTheme);
+ }
+ // Apply it
+ context = new ContextThemeWrapper(context, theme);
- public static int getThemeResource(Context context, int attr) {
+ // 3) If a custom Media Router theme is provided then apply the base theme
+ if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) {
+ context = new ContextThemeWrapper(context, getRouterThemeId(context));
+ }
+
+ return context;
+ }
+ // This method should be used in conjunction with the previous method.
+ static int createThemedDialogStyle(Context context) {
+ // 4) Apply the custom Media Router theme
+ int theme = getThemeResource(context, R.attr.mediaRouteTheme);
+ if (theme == 0) {
+ // 3) No custom MediaRouther theme was provided so apply the base theme instead
+ theme = getRouterThemeId(context);
+ }
+
+ return theme;
+ }
+ // END. Previous two methods should be used in conjunction.
+
+ static int getThemeResource(Context context, int attr) {
TypedValue value = new TypedValue();
return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
}
- public static float getDisabledAlpha(Context context) {
+ static float getDisabledAlpha(Context context) {
TypedValue value = new TypedValue();
return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
? value.getFloat() : 0.5f;
}
- public static @ControllerColorType int getControllerColor(Context context, int style) {
+ static @ControllerColorType int getControllerColor(Context context, int style) {
int primaryColor = getThemeColor(context, style,
android.support.v7.appcompat.R.attr.colorPrimary);
if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
@@ -92,7 +121,7 @@
return COLOR_DARK_ON_LIGHT_BACKGROUND;
}
- public static int getButtonTextColor(Context context) {
+ static int getButtonTextColor(Context context) {
int primaryColor = getThemeColor(context, 0,
android.support.v7.appcompat.R.attr.colorPrimary);
int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
@@ -104,7 +133,7 @@
return primaryColor;
}
- public static void setMediaControlsBackgroundColor(
+ static void setMediaControlsBackgroundColor(
Context context, View mainControls, View groupControls, boolean hasGroup) {
int primaryColor = getThemeColor(context, 0,
android.support.v7.appcompat.R.attr.colorPrimary);
@@ -124,7 +153,7 @@
groupControls.setTag(primaryDarkColor);
}
- public static void setVolumeSliderColor(
+ static void setVolumeSliderColor(
Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
int controllerColor = getControllerColor(context, 0);
if (Color.alpha(controllerColor) != 0xFF) {
@@ -136,23 +165,10 @@
volumeSlider.setColor(controllerColor);
}
- // This is copied from {@link AlertDialog#resolveDialogTheme} to pre-evaluate theme in advance.
- public static int getAlertDialogResolvedTheme(Context context, int themeResId) {
- if (themeResId >= 0x01000000) { // start of real resource IDs.
- return themeResId;
- } else {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(
- android.support.v7.appcompat.R.attr.alertDialogTheme, outValue, true);
- return outValue.resourceId;
- }
- }
-
private static boolean isLightTheme(Context context) {
TypedValue value = new TypedValue();
- return context.getTheme().resolveAttribute(
- android.support.v7.appcompat.R.attr.isLightTheme, value, true)
- && value.data != 0;
+ return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme,
+ value, true) && value.data != 0;
}
private static int getThemeColor(Context context, int style, int attr) {
@@ -173,16 +189,16 @@
return value.data;
}
- private static int getStyledRouterThemeId(Context context, int style) {
+ private static int getRouterThemeId(Context context) {
int themeId;
if (isLightTheme(context)) {
- if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
themeId = R.style.Theme_MediaRouter_Light;
} else {
themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
}
} else {
- if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
themeId = R.style.Theme_MediaRouter_LightControlPanel;
} else {
themeId = R.style.Theme_MediaRouter;
diff --git a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
index 415ec1f..7288968 100644
--- a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
+++ b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
@@ -16,7 +16,6 @@
package android.support.v7.app;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -66,7 +65,7 @@
// No base theme, with a customized theme (has window title)
dialog = new MediaRouteChooserDialog(context, R.style.HasWindowTitle);
typedArray = dialog.getContext().obtainStyledAttributes(R.styleable.AppCompatTheme);
- assertFalse(typedArray.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false));
+ assertTrue(typedArray.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false));
typedArray.recycle();
// With base theme (has window title), no customized theme
@@ -79,7 +78,7 @@
// With base theme and a customized theme (both has window title)
dialog = new MediaRouteChooserDialog(context, R.style.HasWindowTitle);
typedArray = dialog.getContext().obtainStyledAttributes(R.styleable.AppCompatTheme);
- assertFalse(typedArray.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false));
+ assertTrue(typedArray.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false));
typedArray.recycle();
context.setTheme(0);
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
index 6da6914..2703f2c 100644
--- a/v7/palette/build.gradle
+++ b/v7/palette/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-compat')
@@ -14,8 +19,11 @@
}
supportLibrary {
- name 'Android Support Palette v7'
- publish true
- inceptionYear '2014'
- description 'Android Support Palette v7'
+ name = "Android Support Palette v7"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2014"
+ description = "Android Support Palette v7"
+ legacySourceLocation = true
}
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index 2b1e871..f5bc372 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -14,7 +14,12 @@
* limitations under the License
*/
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-v4')
@@ -50,8 +55,11 @@
}
supportLibrary {
- name 'Android Support Preference v7'
- publish true
- inceptionYear '2015'
- description 'Android Support Preference v7'
+ name = "Android Support Preference v7"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2015"
+ description = "Android Support Preference v7"
+ legacySourceLocation = true
}
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index ad1150f..daac4ab 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -26,18 +31,17 @@
main.res.srcDirs 'res', 'res-public'
}
- testOptions {
- unitTests.returnDefaultValues = true
- }
-
buildTypes.all {
consumerProguardFiles 'proguard-rules.pro'
}
}
supportLibrary {
- name 'Android Support RecyclerView v7'
- publish true
- inceptionYear '2014'
- description 'Android Support RecyclerView v7'
+ name = "Android Support RecyclerView v7"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2014"
+ description = "Android Support RecyclerView v7"
+ legacySourceLocation = true
}
diff --git a/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
index dea8546..cfa28e8 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
@@ -6339,16 +6339,16 @@
}
void markKnownViewsInvalid() {
- if (mAdapter != null && mAdapter.hasStableIds()) {
- final int cachedCount = mCachedViews.size();
- for (int i = 0; i < cachedCount; i++) {
- final ViewHolder holder = mCachedViews.get(i);
- if (holder != null) {
- holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
- holder.addChangePayload(null);
- }
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+ holder.addChangePayload(null);
}
- } else {
+ }
+
+ if (mAdapter == null || !mAdapter.hasStableIds()) {
// we cannot re-use cached views in this case. Recycle them all
recycleAndClearCachedViews();
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index d74c36c..157fb12 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -1083,13 +1083,18 @@
});
}
- public void clearOnUIThread() {
+ void changeAllItemsAndNotifyDataSetChanged(int count) {
assertEquals("clearOnUIThread called from a wrong thread",
Looper.getMainLooper(), Looper.myLooper());
- mItems = new ArrayList<Item>();
+ mItems = new ArrayList<>();
+ addItems(0, count, DEFAULT_ITEM_PREFIX);
notifyDataSetChanged();
}
+ public void clearOnUIThread() {
+ changeAllItemsAndNotifyDataSetChanged(0);
+ }
+
protected void moveInUIThread(int from, int to) {
Item item = mItems.remove(from);
offsetOriginalIndices(from, -1);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 42fde85..bdc450b 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -24,6 +24,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -298,6 +299,201 @@
}
@Test
+ public void onDataSetChanged_doesntHaveStableIds_cachedViewHasNoPosition() throws Throwable {
+ onDataSetChanged_handleCachedViews(false);
+ }
+
+ @Test
+ public void onDataSetChanged_hasStableIds_noCachedViewsAreRecycled() throws Throwable {
+ onDataSetChanged_handleCachedViews(true);
+ }
+
+ /**
+ * If Adapter#setHasStableIds(boolean) is false, cached ViewHolders should be recycled in
+ * response to RecyclerView.Adapter#notifyDataSetChanged() and should report a position of
+ * RecyclerView#NO_POSITION inside of
+ * RecyclerView.Adapter#onViewRecycled(RecyclerView.ViewHolder).
+ *
+ * If Adapter#setHasStableIds(boolean) is true, cached Views/ViewHolders should not be recycled.
+ */
+ public void onDataSetChanged_handleCachedViews(boolean hasStableIds) throws Throwable {
+ final AtomicInteger cachedRecycleCount = new AtomicInteger(0);
+
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setItemViewCacheSize(1);
+
+ final TestAdapter adapter = new TestAdapter(2) {
+ @Override
+ public void onViewRecycled(TestViewHolder holder) {
+ // If the recycled holder is currently in the cache, then it's position in the
+ // adapter should be RecyclerView.NO_POSITION.
+ if (mRecyclerView.mRecycler.mCachedViews.contains(holder)) {
+ assertThat("ViewHolder's getAdapterPosition should be "
+ + "RecyclerView.NO_POSITION",
+ holder.getAdapterPosition(),
+ is(RecyclerView.NO_POSITION));
+ cachedRecycleCount.incrementAndGet();
+ }
+ super.onViewRecycled(holder);
+ }
+ };
+ adapter.setHasStableIds(hasStableIds);
+ recyclerView.setAdapter(adapter);
+
+ final AtomicInteger numItemsToLayout = new AtomicInteger(2);
+
+ TestLayoutManager layoutManager = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ detachAndScrapAttachedViews(recycler);
+ layoutRange(recycler, 0, numItemsToLayout.get());
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ } finally {
+ this.layoutLatch.countDown();
+ }
+ }
+
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return false;
+ }
+ };
+ recyclerView.setLayoutManager(layoutManager);
+
+ // Layout 2 items and sanity check that no items are in the recycler's cache.
+ numItemsToLayout.set(2);
+ layoutManager.expectLayouts(1);
+ setRecyclerView(recyclerView, true, false);
+ layoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ assertThat("Sanity check, no views should be cached at this time",
+ mRecyclerView.mRecycler.mCachedViews.size(),
+ is(0));
+
+ // Now only layout 1 item and assert that 1 item is cached.
+ numItemsToLayout.set(1);
+ layoutManager.expectLayouts(1);
+ requestLayoutOnUIThread(mRecyclerView);
+ layoutManager.waitForLayout(1);
+ checkForMainThreadException();
+ assertThat("One view should be cached.",
+ mRecyclerView.mRecycler.mCachedViews.size(),
+ is(1));
+
+ // Notify data set has changed then final assert.
+ layoutManager.expectLayouts(1);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ adapter.notifyDataSetChanged();
+ }
+ });
+ layoutManager.waitForLayout(1);
+ checkForMainThreadException();
+ // If hasStableIds, then no cached views should be recycled, otherwise just 1 should have
+ // been recycled.
+ assertThat(cachedRecycleCount.get(), is(hasStableIds ? 0 : 1));
+ }
+
+ @Test
+ public void notifyDataSetChanged_hasStableIds_cachedViewsAreReusedForSamePositions()
+ throws Throwable {
+ final Map<Integer, TestViewHolder> positionToViewHolderMap = new HashMap<>();
+ final AtomicInteger layoutItemCount = new AtomicInteger();
+ final AtomicBoolean inFirstBindViewHolderPass = new AtomicBoolean();
+
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setItemViewCacheSize(5);
+
+ final TestAdapter adapter = new TestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder, int position) {
+ // Only track the top 5 positions that are going to be cached and then reused.
+ if (position >= 5) {
+ // If we are in the first phase, put the items in the map, if we are in the
+ // second phase, remove each one at the position and verify that it matches the
+ // provided ViewHolder.
+ if (inFirstBindViewHolderPass.get()) {
+ positionToViewHolderMap.put(position, holder);
+ } else {
+ TestViewHolder testViewHolder = positionToViewHolderMap.get(position);
+ assertThat(holder, is(testViewHolder));
+ positionToViewHolderMap.remove(position);
+ }
+ }
+ super.onBindViewHolder(holder, position);
+ }
+ };
+ adapter.setHasStableIds(true);
+ recyclerView.setAdapter(adapter);
+
+ TestLayoutManager testLayoutManager = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ detachAndScrapAttachedViews(recycler);
+ layoutRange(recycler, 0, layoutItemCount.get());
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ } finally {
+ layoutLatch.countDown();
+ }
+ }
+
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return false;
+ }
+ };
+ recyclerView.setLayoutManager(testLayoutManager);
+
+ // First layout 10 items, then verify that the map has all 5 ViewHolders in it that will
+ // be cached, and sanity check that the cache is empty.
+ inFirstBindViewHolderPass.set(true);
+ layoutItemCount.set(10);
+ testLayoutManager.expectLayouts(1);
+ setRecyclerView(recyclerView, true, false);
+ testLayoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ for (int i = 5; i < 10; i++) {
+ assertThat(positionToViewHolderMap.get(i), notNullValue());
+ }
+ assertThat(mRecyclerView.mRecycler.mCachedViews.size(), is(0));
+
+ // Now only layout the first 5 items and verify that the cache has 5 items in it.
+ layoutItemCount.set(5);
+ testLayoutManager.expectLayouts(1);
+ requestLayoutOnUIThread(mRecyclerView);
+ testLayoutManager.waitForLayout(1);
+ checkForMainThreadException();
+ assertThat(mRecyclerView.mRecycler.mCachedViews.size(), is(5));
+
+ // Trigger notifyDataSetChanged and wait for layout.
+ testLayoutManager.expectLayouts(1);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ adapter.notifyDataSetChanged();
+ }
+ });
+ testLayoutManager.waitForLayout(1);
+ checkForMainThreadException();
+
+ // Layout 10 items again, via the onBindViewholder method, check that each one of the views
+ // returned from the recycler for positions >= 5 was in our cache of views, and verify that
+ // all 5 cached views were returned.
+ inFirstBindViewHolderPass.set(false);
+ layoutItemCount.set(10);
+ testLayoutManager.expectLayouts(1);
+ requestLayoutOnUIThread(mRecyclerView);
+ testLayoutManager.waitForLayout(1);
+ checkForMainThreadException();
+ assertThat(positionToViewHolderMap.size(), is(0));
+ }
+
+ @Test
public void predictiveMeasuredCrashTest() throws Throwable {
final RecyclerView rv = new RecyclerView(getActivity());
final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true) {
@@ -4654,7 +4850,7 @@
@Override
public void onNestedPreScroll(@NonNull View target, int dx, int dy,
- @Nullable int[] consumed, @ViewCompat.NestedScrollType int type) {
+ @NonNull int[] consumed, @ViewCompat.NestedScrollType int type) {
// Consume everything!
consumed[0] = dx;
consumed[1] = dy;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
index cd5fa22..2d73fd0 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingParent2;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
@@ -201,7 +200,7 @@
}
@Override
- public void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed,
+ public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
@ViewCompat.NestedScrollType int type) {
if (mNestedScrollingDelegate != null) {
mNestedScrollingDelegate.onNestedPreScroll(target, dx, dy, consumed, type);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/test/NestedScrollingParent2Adapter.java b/v7/recyclerview/tests/src/android/support/v7/widget/test/NestedScrollingParent2Adapter.java
index da0d86f..356cffa 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/test/NestedScrollingParent2Adapter.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/test/NestedScrollingParent2Adapter.java
@@ -17,7 +17,6 @@
package android.support.v7.widget.test;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingParent2;
import android.support.v4.view.ViewCompat;
import android.view.View;
@@ -46,7 +45,7 @@
@Override
public void onNestedPreScroll(@NonNull View target, int dx, int dy,
- @Nullable int[] consumed, @ViewCompat.NestedScrollType int type) {
+ @NonNull int[] consumed, @ViewCompat.NestedScrollType int type) {
}
@Override
diff --git a/wear/build.gradle b/wear/build.gradle
index 814f5cf..458f722 100644
--- a/wear/build.gradle
+++ b/wear/build.gradle
@@ -1,4 +1,9 @@
-apply plugin: android.support.SupportAndroidLibraryPlugin
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+}
dependencies {
api project(':support-annotations')
@@ -33,13 +38,11 @@
}
supportLibrary {
- name 'Android Wear Support UI'
- publish true
- inceptionYear '2016'
- description 'Android Wear Support UI'
-
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- }
+ name = "Android Wear Support UI"
+ publish = true
+ mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+ mavenGroup = LibraryGroups.SUPPORT
+ inceptionYear = "2016"
+ description = "Android Wear Support UI"
+ legacySourceLocation = true
}