Implement ListenableFuturePagedSource

Provides a Java-friendly API leveraging Guava's ListenableFuture
wrapping the suspending PagedSource API

Test: ./gradlew paging:paging-guava:cC
Change-Id: I0add2127d23223bbcc8f1f19ce5b0b52003a1153
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index a5b405e..80bdda1 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -104,6 +104,7 @@
     ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-generator")
     ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-gradle-plugin")
     prebuilts(LibraryGroups.NAVIGATION, "2.2.0-rc02")
+    ignore(LibraryGroups.PAGING.group, "paging-guava")
     prebuilts(LibraryGroups.PAGING, "2.1.0")
     prebuilts(LibraryGroups.PALETTE, "1.0.0")
     prebuilts(LibraryGroups.PERCENTLAYOUT, "1.0.0")
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index 3c6eb5e..ee45838 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -1071,6 +1071,10 @@
       "to": "androidx/paging/rxjava2"
     },
     {
+      "from": "android/arch/paging/guava",
+      "to": "androidx/paging/guava"
+    },
+    {
       "from": "android/arch/lifecycle/viewmodel/savedstate",
       "to": "androidx/lifecycle/viewmodel/savedstate"
     },
@@ -2338,6 +2342,18 @@
     },
     {
       "from": {
+        "groupId": "androidx.paging",
+        "artifactId": "paging-guava",
+        "version": "{newPagingVersion}"
+      },
+      "to": {
+        "groupId": "androidx.paging",
+        "artifactId": "paging-guava",
+        "version": "{newPagingVersion}"
+      }
+    },
+    {
+      "from": {
         "groupId": "android.arch.persistence.room",
         "artifactId": "room-ktx",
         "version": "{oldRoomVersion}"
diff --git a/paging/guava/api/3.0.0-alpha01.txt b/paging/guava/api/3.0.0-alpha01.txt
new file mode 100644
index 0000000..ccb1c62
--- /dev/null
+++ b/paging/guava/api/3.0.0-alpha01.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.paging {
+
+  public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> {
+    ctor public ListenableFuturePagedSource();
+    method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/guava/api/api_lint.ignore b/paging/guava/api/api_lint.ignore
new file mode 100644
index 0000000..810fe69
--- /dev/null
+++ b/paging/guava/api/api_lint.ignore
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+DocumentExceptions: androidx.paging.ListenableFutureKt#await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.Continuation<? super R>):
+    Method ListenableFutureKt.await appears to be throwing java.lang.Throwable; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
+
+
+MissingNullability: androidx.paging.ListenableFutureKt#await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.Continuation<? super R>):
+    Missing nullability on method `await` return
diff --git a/paging/guava/api/current.txt b/paging/guava/api/current.txt
new file mode 100644
index 0000000..ccb1c62
--- /dev/null
+++ b/paging/guava/api/current.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.paging {
+
+  public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> {
+    ctor public ListenableFuturePagedSource();
+    method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/guava/api/public_plus_experimental_3.0.0-alpha01.txt b/paging/guava/api/public_plus_experimental_3.0.0-alpha01.txt
new file mode 100644
index 0000000..ccb1c62
--- /dev/null
+++ b/paging/guava/api/public_plus_experimental_3.0.0-alpha01.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.paging {
+
+  public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> {
+    ctor public ListenableFuturePagedSource();
+    method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/guava/api/public_plus_experimental_current.txt b/paging/guava/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..ccb1c62
--- /dev/null
+++ b/paging/guava/api/public_plus_experimental_current.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.paging {
+
+  public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> {
+    ctor public ListenableFuturePagedSource();
+    method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/guava/api/res-3.0.0-alpha01.txt b/paging/guava/api/res-3.0.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/guava/api/res-3.0.0-alpha01.txt
diff --git a/paging/guava/api/restricted_3.0.0-alpha01.txt b/paging/guava/api/restricted_3.0.0-alpha01.txt
new file mode 100644
index 0000000..ccb1c62
--- /dev/null
+++ b/paging/guava/api/restricted_3.0.0-alpha01.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.paging {
+
+  public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> {
+    ctor public ListenableFuturePagedSource();
+    method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/guava/api/restricted_current.txt b/paging/guava/api/restricted_current.txt
new file mode 100644
index 0000000..ccb1c62
--- /dev/null
+++ b/paging/guava/api/restricted_current.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.paging {
+
+  public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> {
+    ctor public ListenableFuturePagedSource();
+    method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/guava/build.gradle b/paging/guava/build.gradle
new file mode 100644
index 0000000..d5360f9
--- /dev/null
+++ b/paging/guava/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.AndroidXExtension
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+dependencies {
+    api(project(":paging:paging-common"))
+    api(KOTLIN_STDLIB)
+    api(project(":concurrent:concurrent-futures-ktx"))
+
+    testImplementation project(':internal-testutils-common')
+    testImplementation(JUNIT)
+    testImplementation(KOTLIN_TEST)
+}
+
+androidx {
+    name = "Android Paging Guava"
+    publish = Publish.SNAPSHOT_AND_RELEASE
+    mavenVersion = LibraryVersions.PAGING
+    mavenGroup = LibraryGroups.PAGING
+    inceptionYear = "2019"
+    description = "Android Paging Guava"
+    url = AndroidXExtension.ARCHITECTURE_URL
+}
diff --git a/paging/guava/src/main/AndroidManifest.xml b/paging/guava/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ac2ee69
--- /dev/null
+++ b/paging/guava/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.paging.guava">
+</manifest>
diff --git a/paging/guava/src/main/java/androidx/paging/ListenableFuturePagedSource.kt b/paging/guava/src/main/java/androidx/paging/ListenableFuturePagedSource.kt
new file mode 100644
index 0000000..0db9e0d
--- /dev/null
+++ b/paging/guava/src/main/java/androidx/paging/ListenableFuturePagedSource.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging
+
+import androidx.concurrent.futures.await
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * [ListenableFuture]-based compatibility wrapper around [PagedSource]'s suspending APIs.
+ */
+abstract class ListenableFuturePagedSource<Key : Any, Value : Any> : PagedSource<Key, Value>() {
+    /**
+     * Loading API for [PagedSource].
+     *
+     * Implement this method to trigger your async load (e.g. from database or network).
+     */
+    abstract fun loadFuture(params: LoadParams<Key>): ListenableFuture<LoadResult<Key, Value>>
+
+    override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
+        return loadFuture(params).await()
+    }
+}
diff --git a/paging/guava/src/test/java/androidx/paging/ListenableFuturePagedSourceTest.kt b/paging/guava/src/test/java/androidx/paging/ListenableFuturePagedSourceTest.kt
new file mode 100644
index 0000000..dc59b05
--- /dev/null
+++ b/paging/guava/src/test/java/androidx/paging/ListenableFuturePagedSourceTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging
+
+import androidx.concurrent.futures.ResolvableFuture
+import androidx.paging.PagedSource.LoadParams
+import androidx.paging.PagedSource.LoadResult.Page
+import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+@RunWith(JUnit4::class)
+class ListenableFuturePagedSourceTest {
+    private fun loadInternal(params: LoadParams<Int>): Page<Int, Int> {
+        val key = params.key!! // Intentionally fail on null keys
+        require(key >= 0) // Intentionally throw on negative key
+
+        return Page(
+            List(params.loadSize) { it + key },
+            prevKey = key - params.loadSize,
+            nextKey = key + params.loadSize
+        )
+    }
+
+    private val pagedSource = object : PagedSource<Int, Int>() {
+        override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+            return loadInternal(params)
+        }
+    }
+
+    private val listenableFuturePagedSource = object : ListenableFuturePagedSource<Int, Int>() {
+        override fun loadFuture(params: LoadParams<Int>): ListenableFuture<LoadResult<Int, Int>> {
+            val future = ResolvableFuture.create<LoadResult<Int, Int>>()
+            try {
+                future.set(loadInternal(params))
+            } catch (e: IllegalArgumentException) {
+                future.setException(e)
+            }
+            return future
+        }
+    }
+
+    @Test
+    fun basic() = runBlocking {
+        val params = LoadParams(LoadType.REFRESH, 0, 2, false, 2)
+        assertEquals(pagedSource.load(params), listenableFuturePagedSource.load(params))
+    }
+
+    @Test
+    fun error() {
+        runBlocking {
+            val params = LoadParams<Int>(LoadType.REFRESH, null, 2, false, 2)
+            assertFailsWith<NullPointerException> { pagedSource.load(params) }
+            assertFailsWith<NullPointerException> { listenableFuturePagedSource.load(params) }
+        }
+    }
+
+    @Test
+    fun errorWrapped() {
+        runBlocking {
+            val params = LoadParams(LoadType.REFRESH, -1, 2, false, 2)
+            assertFailsWith<IllegalArgumentException> { pagedSource.load(params) }
+            assertFailsWith<IllegalArgumentException> { listenableFuturePagedSource.load(params) }
+        }
+    }
+}
diff --git a/paging/rxjava2/src/main/java/androidx/paging/RxPagedSource.kt b/paging/rxjava2/src/main/java/androidx/paging/RxPagedSource.kt
index 9ed9c4c..5df0053 100644
--- a/paging/rxjava2/src/main/java/androidx/paging/RxPagedSource.kt
+++ b/paging/rxjava2/src/main/java/androidx/paging/RxPagedSource.kt
@@ -20,9 +20,14 @@
 import kotlinx.coroutines.rx2.await
 
 /**
- * Rx-version of [PagedSource].
+ * Rx-based compatibility wrapper around [PagedSource]'s suspending APIs.
  */
 abstract class RxPagedSource<Key : Any, Value : Any> : PagedSource<Key, Value>() {
+    /**
+     * Loading API for [PagedSource].
+     *
+     * Implement this method to trigger your async load (e.g. from database or network).
+     */
     abstract fun loadSingle(params: LoadParams<Key>): Single<LoadResult<Key, Value>>
 
     final override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
diff --git a/settings.gradle b/settings.gradle
index a01c8e4..9a878fb0 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -196,6 +196,7 @@
 includeProject(":paging:paging-runtime-ktx", "paging/runtime/ktx")
 includeProject(":paging:paging-rxjava2", "paging/rxjava2")
 includeProject(":paging:paging-rxjava2-ktx", "paging/rxjava2/ktx")
+includeProject(":paging:paging-guava", "paging/guava")
 includeProject(":palette:palette", "palette/palette")
 includeProject(":palette:palette-ktx", "palette/palette-ktx")
 includeProject(":percentlayout:percentlayout", "percentlayout/percentlayout")