diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 9cea827df36..a70e1c40638 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -37,6 +37,7 @@ dependencies { implementation 'org.jsoup:jsoup:1.11.2' implementation 'digital.wup:android-maven-publish:3.6.2' implementation 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20' + implementation 'org.json:json:20180813' implementation 'io.opencensus:opencensus-api:0.18.0' implementation 'io.opencensus:opencensus-exporter-stats-stackdriver:0.18.0' @@ -44,7 +45,6 @@ dependencies { implementation 'com.android.tools.build:gradle:3.2.1' testImplementation 'junit:junit:4.12' - testImplementation 'org.json:json:20180813' testImplementation('org.spockframework:spock-core:1.1-groovy-2.4') { exclude group: 'org.codehaus.groovy' } diff --git a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.java b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.java index 5950a5477dd..522065eab14 100644 --- a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.java +++ b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.java @@ -20,7 +20,6 @@ import com.google.firebase.gradle.plugins.ci.device.FirebaseTestServer; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.tasks.bundling.Jar; import org.jetbrains.kotlin.gradle.tasks.KotlinCompile; public class FirebaseLibraryPlugin implements Plugin { @@ -33,6 +32,28 @@ public void apply(Project project) { LibraryExtension android = project.getExtensions().getByType(LibraryExtension.class); + // In the case of and android library signing config only affects instrumentation test APK. + // We need it signed with default debug credentials in order for FTL to accept the APK. + android.buildTypes( + types -> + types + .getByName("release") + .setSigningConfig(types.getByName("debug").getSigningConfig())); + + // skip debug tests in CI + // TODO(vkryachko): provide ability for teams to control this if needed + if (System.getenv().containsKey("FIREBASE_CI")) { + android.setTestBuildType("release"); + project + .getTasks() + .all( + task -> { + if ("testDebugUnitTest".equals(task.getName())) { + task.setEnabled(false); + } + }); + } + android.testServer(new FirebaseTestServer(project, firebaseLibrary.testLab)); // reduce the likelihood of kotlin module files colliding. diff --git a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.groovy b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.groovy index d04607189e9..ffe1e43099c 100644 --- a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.groovy +++ b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.groovy @@ -25,6 +25,10 @@ class AffectedProjectFinder { Set changedPaths; @Builder + AffectedProjectFinder(Project project, List ignorePaths) { + this(project, changedPaths(project.rootDir), ignorePaths) + } + AffectedProjectFinder(Project project, Set changedPaths, List ignorePaths) { @@ -49,6 +53,13 @@ class AffectedProjectFinder { return project.subprojects } + private static Set changedPaths(File workDir) { + return 'git diff --name-only --submodule=diff HEAD@{0} HEAD@{1}' + .execute([], workDir) + .text + .readLines() + } + /** * Performs a post-order project tree traversal and returns a set of projects that own the * 'changedPaths'. diff --git a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationPlugin.groovy b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationPlugin.groovy index 95334ba6dbd..7c44748e6be 100644 --- a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationPlugin.groovy +++ b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationPlugin.groovy @@ -97,7 +97,6 @@ class ContinuousIntegrationPlugin implements Plugin { def affectedProjects = AffectedProjectFinder.builder() .project(project) - .changedPaths(changedPaths(project.rootDir)) .ignorePaths(extension.ignorePaths) .build() .find() @@ -143,13 +142,6 @@ class ContinuousIntegrationPlugin implements Plugin { } } - private static Set changedPaths(File workDir) { - return 'git diff --name-only --submodule=diff HEAD@{0} HEAD@{1}' - .execute([], workDir) - .text - .readLines() - } - private static final ANDROID_PLUGINS = ["com.android.application", "com.android.library", "com.android.test"] diff --git a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.groovy b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.groovy new file mode 100644 index 00000000000..853e845418d --- /dev/null +++ b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.groovy @@ -0,0 +1,105 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.gradle.plugins.ci + +import com.google.firebase.gradle.plugins.FirebaseLibraryExtension +import com.google.firebase.gradle.plugins.ci.AffectedProjectFinder +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.ProjectDependency +import org.json.JSONArray +import org.json.JSONObject + +/** Builds Firebase libraries for consumption by the smoke tests. */ +class SmokeTestsPlugin implements Plugin { + @Override + public void apply(Project project) { + def assembleAllTask = project.task("assembleAllForSmokeTests") + + // Wait until after the projects have been evaluated or else we might skip projects. + project.gradle.projectsEvaluated { + def changedProjects = getChangedProjects(project) + def changedArtifacts = new HashSet() + def allArtifacts = new HashSet() + + // Visit each project and add the artifacts to the appropriate sets. + project.subprojects { + def firebaseLibrary = it.extensions.findByType(FirebaseLibraryExtension) + if (firebaseLibrary == null) { + return + } + + def groupId = firebaseLibrary.groupId.get() + def artifactId = firebaseLibrary.artifactId.get() + def artifact = "$groupId:$artifactId:$it.version-SNAPSHOT" + allArtifacts.add(artifact) + + if (changedProjects.contains(it)) { + changedArtifacts.add(artifact) + } + } + + // Reuse the publish task for building the libraries. + def publishAllTask = project.tasks.getByPath("publishAllToBuildDir") + assembleAllTask.dependsOn(publishAllTask) + + // Generate a JSON file listing the artifacts after everything is complete. + assembleAllTask.doLast { + def changed = new JSONArray() + changedArtifacts.each { changed.put(it) } + + def all = new JSONArray() + allArtifacts.each { all.put(it) } + + def json = new JSONObject() + json.put("all", all) + json.put("changed", changed) + + def path = project.buildDir.toPath() + path.resolve("m2repository/changed-artifacts.json").write(json.toString()) + } + } + } + + private static Set getChangedProjects(Project p) { + Set roots = new AffectedProjectFinder(p, []).find() + HashSet changed = new HashSet<>() + + getChangedProjectsLoop(roots, changed) + return changed + } + + private static void getChangedProjectsLoop(Collection projects, Set changed) { + for (Project p : projects) { + // Skip project if it is not a Firebase library. + if (p.extensions.findByType(FirebaseLibraryExtension) == null) { + continue; + } + + // Skip processing and recursion if this project has already been added to the set. + if (!changed.add(p)) { + continue; + } + + // Find all (head) dependencies to other projects in this respository. + def all = p.configurations.releaseRuntimeClasspath.allDependencies + def affected = + all.findAll { it instanceof ProjectDependency }.collect { it.getDependencyProject() } + + // Recurse with the new dependencies. + getChangedProjectsLoop(affected, changed) + } + } +} diff --git a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/publish/Publisher.groovy b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/publish/Publisher.groovy index 51036b38cc9..311e1626368 100644 --- a/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/publish/Publisher.groovy +++ b/buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/publish/Publisher.groovy @@ -74,7 +74,7 @@ class Publisher { pom.dependencies.dependency.each { // remove multidex as it is supposed to be added by final applications and is needed for // some libraries only for instrumentation tests to build. - if (it.groupId.text() in ['com.android.support', 'androidx'] && it.artifactId.text() == 'multidex') { + if (it.groupId.text() in ['com.android.support', 'androidx.multidex'] && it.artifactId.text() == 'multidex') { it.parent().remove(it) } it.appendNode('type', [:], deps["${it.groupId.text()}:${it.artifactId.text()}"]) diff --git a/fiamui-app/fiamui-app.gradle b/fiamui-app/fiamui-app.gradle index 4b3b3659e8e..3382bd3ce27 100644 --- a/fiamui-app/fiamui-app.gradle +++ b/fiamui-app/fiamui-app.gradle @@ -53,11 +53,11 @@ android { dependencies { implementation project(path: ":firebase-inappmessaging-display") - implementation "com.google.firebase:firebase-measurement-connector:17.0.1" + implementation "com.google.firebase:firebase-measurement-connector:18.0.0" implementation('com.google.firebase:firebase-inappmessaging:17.0.3') { exclude group: 'com.google.firebase', module: 'firebase-common' } - implementation('com.google.firebase:firebase-analytics:16.0.4') { + implementation('com.google.firebase:firebase-analytics:17.0.0') { exclude group: 'com.google.firebase', module: 'firebase-common' } @@ -67,7 +67,7 @@ dependencies { implementation "com.google.code.findbugs:jsr305:3.0.2" implementation "com.squareup.okhttp:okhttp:2.7.5" implementation "com.google.auto.value:auto-value-annotations:1.6.5" - implementation "com.google.android.gms:play-services-basement:16.2.0" + implementation "com.google.android.gms:play-services-basement:17.0.0" // The following dependencies are not required to use the FIAM UI library. // They are used to make some aspects of the demo app implementation simpler for diff --git a/firebase-common/firebase-common.gradle b/firebase-common/firebase-common.gradle index ed68cd80597..2a8c84ce1e6 100644 --- a/firebase-common/firebase-common.gradle +++ b/firebase-common/firebase-common.gradle @@ -58,8 +58,8 @@ android { } dependencies { - implementation 'com.google.android.gms:play-services-basement:16.2.0' - implementation "com.google.android.gms:play-services-tasks:16.0.1" + implementation 'com.google.android.gms:play-services-basement:17.0.0' + implementation "com.google.android.gms:play-services-tasks:17.0.0" api 'com.google.auto.value:auto-value-annotations:1.6.5' compileOnly 'com.google.code.findbugs:jsr305:3.0.2' diff --git a/firebase-common/gradle.properties b/firebase-common/gradle.properties index 5328ce212de..9b7be4891d1 100644 --- a/firebase-common/gradle.properties +++ b/firebase-common/gradle.properties @@ -1,2 +1,2 @@ -version=17.1.1 -latestReleasedVersion=17.1.0 +version=18.0.1 +latestReleasedVersion=18.0.0 diff --git a/firebase-database-collection/firebase-database-collection.gradle b/firebase-database-collection/firebase-database-collection.gradle index 1d29321dd24..a9d5af2f3c3 100644 --- a/firebase-database-collection/firebase-database-collection.gradle +++ b/firebase-database-collection/firebase-database-collection.gradle @@ -29,7 +29,7 @@ android { } dependencies { - implementation 'com.google.android.gms:play-services-base:16.1.0' + implementation 'com.google.android.gms:play-services-base:17.0.0' testImplementation 'junit:junit:4.12' testImplementation 'net.java:quickcheck:0.6' diff --git a/firebase-database-collection/gradle.properties b/firebase-database-collection/gradle.properties index c763f64467b..54be3eb478f 100644 --- a/firebase-database-collection/gradle.properties +++ b/firebase-database-collection/gradle.properties @@ -1,2 +1,2 @@ -version=16.0.2 -latestReleasedVersion=16.0.1 +version=17.0.1 +latestReleasedVersion=17.0.0 diff --git a/firebase-database/firebase-database.gradle b/firebase-database/firebase-database.gradle index b6ef29308c9..b68e911ad69 100644 --- a/firebase-database/firebase-database.gradle +++ b/firebase-database/firebase-database.gradle @@ -73,10 +73,10 @@ dependencies { implementation project(':firebase-common') implementation project(':firebase-database-collection') - implementation 'com.google.android.gms:play-services-basement:16.2.0' - implementation 'com.google.android.gms:play-services-base:16.1.0' - implementation 'com.google.android.gms:play-services-tasks:16.0.1' - implementation('com.google.firebase:firebase-auth-interop:17.0.0') { + implementation 'com.google.android.gms:play-services-basement:17.0.0' + implementation 'com.google.android.gms:play-services-base:17.0.0' + implementation 'com.google.android.gms:play-services-tasks:17.0.0' + implementation('com.google.firebase:firebase-auth-interop:18.0.0') { exclude group: "com.google.firebase", module: "firebase-common" } diff --git a/firebase-database/gradle.properties b/firebase-database/gradle.properties index f4ae1a57594..b2337aeb5ba 100644 --- a/firebase-database/gradle.properties +++ b/firebase-database/gradle.properties @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=17.0.0 -latestReleasedVersion=16.1.0 +version=18.0.1 +latestReleasedVersion=18.0.0 android.enableUnitTestBinaryResources=true diff --git a/firebase-datatransport/gradle.properties b/firebase-datatransport/gradle.properties index 03f6ea19074..a8dce55d4ea 100644 --- a/firebase-datatransport/gradle.properties +++ b/firebase-datatransport/gradle.properties @@ -1,3 +1,3 @@ -version=16.0.1 -latestReleasedVersion=16.0.0 +version=17.0.1 +latestReleasedVersion=17.0.0 android.enableUnitTestBinaryResources=true diff --git a/firebase-firestore/firebase-firestore.gradle b/firebase-firestore/firebase-firestore.gradle index f92916e98a1..9d8d86a686f 100644 --- a/firebase-firestore/firebase-firestore.gradle +++ b/firebase-firestore/firebase-firestore.gradle @@ -106,13 +106,13 @@ dependencies { implementation 'io.grpc:grpc-protobuf-lite:1.21.0' implementation 'io.grpc:grpc-okhttp:1.21.0' implementation 'io.grpc:grpc-android:1.21.0' - implementation 'com.google.android.gms:play-services-basement:16.2.0' - implementation 'com.google.android.gms:play-services-tasks:16.0.1' - implementation 'com.google.android.gms:play-services-base:16.1.0' + implementation 'com.google.android.gms:play-services-basement:17.0.0' + implementation 'com.google.android.gms:play-services-tasks:17.0.0' + implementation 'com.google.android.gms:play-services-base:17.0.0' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' implementation 'com.squareup.okhttp:okhttp:2.7.5' - implementation('com.google.firebase:firebase-auth-interop:17.0.0') { + implementation('com.google.firebase:firebase-auth-interop:18.0.0') { exclude group: "com.google.firebase", module: "firebase-common" } diff --git a/firebase-firestore/gradle.properties b/firebase-firestore/gradle.properties index 04d7bc444aa..346e8670d44 100644 --- a/firebase-firestore/gradle.properties +++ b/firebase-firestore/gradle.properties @@ -1,2 +1,2 @@ -version=19.0.2 -latestReleasedVersion=19.0.1 +version=20.1.0 +latestReleasedVersion=20.0.0 diff --git a/firebase-firestore/ktx/ktx.gradle b/firebase-firestore/ktx/ktx.gradle index cad7aece2d9..ffbfb8f652f 100644 --- a/firebase-firestore/ktx/ktx.gradle +++ b/firebase-firestore/ktx/ktx.gradle @@ -19,10 +19,11 @@ plugins { firebaseLibrary { releaseWith project(':firebase-firestore') + publishSources = true } android { - compileSdkVersion project.targetSdkVersion + compileSdkVersion 28 defaultConfig { minSdkVersion project.minSdkVersion multiDexEnabled true @@ -33,21 +34,22 @@ android { main.java.srcDirs += 'src/main/kotlin' test.java { srcDir 'src/test/kotlin' - srcDir '../src/testUtil/java' - srcDir '../src/roboUtil/java' + srcDir 'src/test/java' } } testOptions.unitTests.includeAndroidResources = true + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" - implementation project(':firebase-common') implementation project(':firebase-common:ktx') implementation project(':firebase-firestore') implementation 'androidx.annotation:annotation:1.1.0' - testImplementation project(':firebase-database-collection') testImplementation 'org.mockito:mockito-core:2.25.0' testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' diff --git a/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestAccessHelper.java b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestAccessHelper.java new file mode 100644 index 00000000000..bab88979493 --- /dev/null +++ b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestAccessHelper.java @@ -0,0 +1,31 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore; + +import com.google.firebase.firestore.model.DocumentKey; + +public final class TestAccessHelper { + + /** Makes the DocumentReference constructor accessible. */ + public static DocumentReference createDocumentReference(DocumentKey documentKey) { + // We can use null here because the tests only use this as a wrapper for documentKeys. + return new DocumentReference(documentKey, null); + } + + /** Makes the getKey() method accessible. */ + public static DocumentKey referenceKey(DocumentReference documentReference) { + return documentReference.getKey(); + } +} diff --git a/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestUtil.java b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestUtil.java new file mode 100644 index 00000000000..d2d032ea3ae --- /dev/null +++ b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestUtil.java @@ -0,0 +1,179 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore; + +import static com.google.firebase.firestore.testutil.TestUtil.doc; +import static com.google.firebase.firestore.testutil.TestUtil.docSet; +import static com.google.firebase.firestore.testutil.TestUtil.key; +import static org.mockito.Mockito.mock; + +import androidx.annotation.Nullable; +import com.google.android.gms.tasks.Task; +import com.google.firebase.database.collection.ImmutableSortedSet; +import com.google.firebase.firestore.core.DocumentViewChange; +import com.google.firebase.firestore.core.DocumentViewChange.Type; +import com.google.firebase.firestore.core.ViewSnapshot; +import com.google.firebase.firestore.local.QueryData; +import com.google.firebase.firestore.model.Document; +import com.google.firebase.firestore.model.DocumentKey; +import com.google.firebase.firestore.model.DocumentSet; +import com.google.firebase.firestore.model.ResourcePath; +import com.google.firebase.firestore.model.value.ObjectValue; +import com.google.firebase.firestore.remote.WatchChangeAggregator; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Assert; +import org.robolectric.Robolectric; + +public class TestUtil { + + private static final FirebaseFirestore FIRESTORE = mock(FirebaseFirestore.class); + + public static FirebaseFirestore firestore() { + return FIRESTORE; + } + + public static CollectionReference collectionReference(String path) { + return new CollectionReference(ResourcePath.fromString(path), FIRESTORE); + } + + public static DocumentReference documentReference(String path) { + return new DocumentReference(key(path), FIRESTORE); + } + + public static DocumentSnapshot documentSnapshot( + String path, Map data, boolean isFromCache) { + if (data == null) { + return DocumentSnapshot.fromNoDocument( + FIRESTORE, key(path), isFromCache, /*hasPendingWrites=*/ false); + } else { + return DocumentSnapshot.fromDocument( + FIRESTORE, doc(path, 1L, data), isFromCache, /*hasPendingWrites=*/ false); + } + } + + public static Query query(String path) { + return new Query(com.google.firebase.firestore.testutil.TestUtil.query(path), FIRESTORE); + } + + /** + * A convenience method for creating a particular query snapshot for tests. + * + * @param path To be used in constructing the query. + * @param oldDocs Provides the prior set of documents in the QuerySnapshot. Each entry maps to a + * document, with the key being the document id, and the value being the document contents. + * @param docsToAdd Specifies data to be added into the query snapshot as of now. Each entry maps + * to a document, with the key being the document id, and the value being the document + * contents. + * @param isFromCache Whether the query snapshot is cache result. + * @return A query snapshot that consists of both sets of documents. + */ + public static QuerySnapshot querySnapshot( + String path, + Map oldDocs, + Map docsToAdd, + boolean hasPendingWrites, + boolean isFromCache) { + DocumentSet oldDocuments = docSet(Document.keyComparator()); + ImmutableSortedSet mutatedKeys = DocumentKey.emptyKeySet(); + for (Map.Entry pair : oldDocs.entrySet()) { + String docKey = path + "/" + pair.getKey(); + oldDocuments = + oldDocuments.add( + doc( + docKey, + 1L, + pair.getValue(), + hasPendingWrites + ? Document.DocumentState.SYNCED + : Document.DocumentState.LOCAL_MUTATIONS)); + + if (hasPendingWrites) { + mutatedKeys = mutatedKeys.insert(key(docKey)); + } + } + DocumentSet newDocuments = docSet(Document.keyComparator()); + List documentChanges = new ArrayList<>(); + for (Map.Entry pair : docsToAdd.entrySet()) { + String docKey = path + "/" + pair.getKey(); + Document docToAdd = + doc( + docKey, + 1L, + pair.getValue(), + hasPendingWrites + ? Document.DocumentState.SYNCED + : Document.DocumentState.LOCAL_MUTATIONS); + newDocuments = newDocuments.add(docToAdd); + documentChanges.add(DocumentViewChange.create(Type.ADDED, docToAdd)); + + if (hasPendingWrites) { + mutatedKeys = mutatedKeys.insert(key(docKey)); + } + } + ViewSnapshot viewSnapshot = + new ViewSnapshot( + com.google.firebase.firestore.testutil.TestUtil.query(path), + newDocuments, + oldDocuments, + documentChanges, + isFromCache, + mutatedKeys, + true, + /* excludesMetadataChanges= */ false); + return new QuerySnapshot(query(path), viewSnapshot, FIRESTORE); + } + + /** + * An implementation of TargetMetadataProvider that provides controlled access to the + * `TargetMetadataProvider` callbacks. Any target accessed via these callbacks must be registered + * beforehand via `setSyncedKeys()`. + */ + public static class TestTargetMetadataProvider + implements WatchChangeAggregator.TargetMetadataProvider { + final Map> syncedKeys = new HashMap<>(); + final Map queryData = new HashMap<>(); + + @Override + public ImmutableSortedSet getRemoteKeysForTarget(int targetId) { + return syncedKeys.get(targetId) != null + ? syncedKeys.get(targetId) + : DocumentKey.emptyKeySet(); + } + + @Nullable + @Override + public QueryData getQueryDataForTarget(int targetId) { + return queryData.get(targetId); + } + + /** Sets or replaces the local state for the provided query data. */ + public void setSyncedKeys(QueryData queryData, ImmutableSortedSet keys) { + this.queryData.put(queryData.getTargetId(), queryData); + this.syncedKeys.put(queryData.getTargetId(), keys); + } + } + + public static T waitFor(Task task) { + if (!task.isComplete()) { + Robolectric.flushBackgroundThreadScheduler(); + } + Assert.assertTrue( + "Expected task to be completed after background thread flush", task.isComplete()); + return task.getResult(); + } +} diff --git a/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/testutil/TestUtil.java b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/testutil/TestUtil.java new file mode 100644 index 00000000000..c2ce41b0d8a --- /dev/null +++ b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/testutil/TestUtil.java @@ -0,0 +1,618 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore.testutil; + +import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; + +import androidx.annotation.NonNull; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Charsets; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.firebase.Timestamp; +import com.google.firebase.database.collection.ImmutableSortedMap; +import com.google.firebase.database.collection.ImmutableSortedSet; +import com.google.firebase.firestore.Blob; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.TestAccessHelper; +import com.google.firebase.firestore.UserDataConverter; +import com.google.firebase.firestore.core.Filter; +import com.google.firebase.firestore.core.Filter.Operator; +import com.google.firebase.firestore.core.OrderBy; +import com.google.firebase.firestore.core.OrderBy.Direction; +import com.google.firebase.firestore.core.Query; +import com.google.firebase.firestore.core.UserData.ParsedUpdateData; +import com.google.firebase.firestore.local.LocalViewChanges; +import com.google.firebase.firestore.local.QueryData; +import com.google.firebase.firestore.local.QueryPurpose; +import com.google.firebase.firestore.model.DatabaseId; +import com.google.firebase.firestore.model.Document; +import com.google.firebase.firestore.model.DocumentKey; +import com.google.firebase.firestore.model.DocumentSet; +import com.google.firebase.firestore.model.FieldPath; +import com.google.firebase.firestore.model.MaybeDocument; +import com.google.firebase.firestore.model.NoDocument; +import com.google.firebase.firestore.model.ResourcePath; +import com.google.firebase.firestore.model.SnapshotVersion; +import com.google.firebase.firestore.model.UnknownDocument; +import com.google.firebase.firestore.model.mutation.DeleteMutation; +import com.google.firebase.firestore.model.mutation.FieldMask; +import com.google.firebase.firestore.model.mutation.FieldTransform; +import com.google.firebase.firestore.model.mutation.MutationResult; +import com.google.firebase.firestore.model.mutation.PatchMutation; +import com.google.firebase.firestore.model.mutation.Precondition; +import com.google.firebase.firestore.model.mutation.SetMutation; +import com.google.firebase.firestore.model.mutation.TransformMutation; +import com.google.firebase.firestore.model.value.FieldValue; +import com.google.firebase.firestore.model.value.ObjectValue; +import com.google.firebase.firestore.remote.RemoteEvent; +import com.google.firebase.firestore.remote.TargetChange; +import com.google.firebase.firestore.remote.WatchChange.DocumentChange; +import com.google.firebase.firestore.remote.WatchChangeAggregator; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import javax.annotation.Nullable; + +/** A set of utilities for tests */ +public class TestUtil { + + /** A string sentinel that can be used with patchMutation() to mark a field for deletion. */ + public static final String DELETE_SENTINEL = ""; + + public static final long ARBITRARY_SEQUENCE_NUMBER = 2; + + @SuppressWarnings("unchecked") + public static Map map(Object... entries) { + Map res = new HashMap<>(); + for (int i = 0; i < entries.length; i += 2) { + res.put((String) entries[i], (T) entries[i + 1]); + } + return res; + } + + public static Blob blob(int... bytes) { + return Blob.fromByteString(byteString(bytes)); + } + + public static ByteString byteString(int... bytes) { + byte[] primitive = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + primitive[i] = (byte) bytes[i]; + } + return ByteString.copyFrom(primitive); + } + + public static FieldMask fieldMask(String... fields) { + FieldPath[] mask = new FieldPath[fields.length]; + for (int i = 0; i < fields.length; i++) { + mask[i] = field(fields[i]); + } + return FieldMask.fromSet(new HashSet<>(Arrays.asList(mask))); + } + + public static final Map EMPTY_MAP = new HashMap<>(); + + public static FieldValue wrap(Object value) { + DatabaseId databaseId = DatabaseId.forProject("project"); + UserDataConverter dataConverter = new UserDataConverter(databaseId); + // HACK: We use parseQueryValue() since it accepts scalars as well as arrays / objects, and + // our tests currently use wrap() pretty generically so we don't know the intent. + return dataConverter.parseQueryValue(value); + } + + public static ObjectValue wrapObject(Map value) { + // Cast is safe here because value passed in is a map + return (ObjectValue) wrap(value); + } + + public static ObjectValue wrapObject(Object... entries) { + return wrapObject(map(entries)); + } + + public static DocumentKey key(String key) { + return DocumentKey.fromPathString(key); + } + + public static ResourcePath path(String key) { + return ResourcePath.fromString(key); + } + + public static Query query(String path) { + return Query.atPath(path(path)); + } + + public static FieldPath field(String path) { + return FieldPath.fromSegments(Arrays.asList(path.split("\\."))); + } + + public static DocumentReference ref(String key) { + return TestAccessHelper.createDocumentReference(key(key)); + } + + public static DatabaseId dbId(String project, String database) { + return DatabaseId.forDatabase(project, database); + } + + public static DatabaseId dbId(String project) { + return DatabaseId.forProject(project); + } + + public static SnapshotVersion version(long versionMicros) { + long seconds = versionMicros / 1000000; + int nanos = (int) (versionMicros % 1000000L) * 1000; + return new SnapshotVersion(new Timestamp(seconds, nanos)); + } + + public static Document doc(String key, long version, Map data) { + return new Document( + key(key), version(version), wrapObject(data), Document.DocumentState.SYNCED); + } + + public static Document doc(DocumentKey key, long version, Map data) { + return new Document(key, version(version), wrapObject(data), Document.DocumentState.SYNCED); + } + + public static Document doc( + String key, long version, ObjectValue data, Document.DocumentState documentState) { + return new Document(key(key), version(version), data, documentState); + } + + public static Document doc( + String key, long version, Map data, Document.DocumentState documentState) { + return new Document(key(key), version(version), wrapObject(data), documentState); + } + + public static NoDocument deletedDoc(String key, long version) { + return deletedDoc(key, version, /*hasCommittedMutations=*/ false); + } + + public static NoDocument deletedDoc(String key, long version, boolean hasCommittedMutations) { + return new NoDocument(key(key), version(version), hasCommittedMutations); + } + + public static UnknownDocument unknownDoc(String key, long version) { + return new UnknownDocument(key(key), version(version)); + } + + public static DocumentSet docSet(Comparator comparator, Document... documents) { + DocumentSet set = DocumentSet.emptySet(comparator); + for (Document document : documents) { + set = set.add(document); + } + return set; + } + + public static ImmutableSortedSet keySet(DocumentKey... keys) { + ImmutableSortedSet keySet = DocumentKey.emptyKeySet(); + for (DocumentKey key : keys) { + keySet = keySet.insert(key); + } + return keySet; + } + + public static Filter filter(String key, String operator, Object value) { + return Filter.create(field(key), operatorFromString(operator), wrap(value)); + } + + public static Operator operatorFromString(String s) { + if (s.equals("<")) { + return Operator.LESS_THAN; + } else if (s.equals("<=")) { + return Operator.LESS_THAN_OR_EQUAL; + } else if (s.equals("==")) { + return Operator.EQUAL; + } else if (s.equals(">")) { + return Operator.GREATER_THAN; + } else if (s.equals(">=")) { + return Operator.GREATER_THAN_OR_EQUAL; + } else if (s.equals("array-contains")) { + return Operator.ARRAY_CONTAINS; + } else { + throw new IllegalStateException("Unknown operator: " + s); + } + } + + public static OrderBy orderBy(String key) { + return orderBy(key, "asc"); + } + + public static OrderBy orderBy(String key, String dir) { + Direction direction; + if (dir.equals("asc")) { + direction = Direction.ASCENDING; + } else if (dir.equals("desc")) { + direction = Direction.DESCENDING; + } else { + throw new IllegalArgumentException("Unknown direction: " + dir); + } + return OrderBy.getInstance(direction, field(key)); + } + + public static void testEquality(List> equalityGroups) { + for (int i = 0; i < equalityGroups.size(); i++) { + List group = equalityGroups.get(i); + for (Object value : group) { + for (List otherGroup : equalityGroups) { + for (Object otherValue : otherGroup) { + if (otherGroup == group) { + assertEquals(value, otherValue); + } else { + assertNotEquals(value, otherValue); + } + } + } + } + } + } + + public static QueryData queryData(int targetId, QueryPurpose queryPurpose, String path) { + return new QueryData(query(path), targetId, ARBITRARY_SEQUENCE_NUMBER, queryPurpose); + } + + public static ImmutableSortedMap docUpdates(MaybeDocument... docs) { + ImmutableSortedMap res = + ImmutableSortedMap.Builder.emptyMap(DocumentKey.comparator()); + for (MaybeDocument doc : docs) { + res = res.insert(doc.getKey(), doc); + } + return res; + } + + public static ImmutableSortedMap docUpdates(Document... docs) { + ImmutableSortedMap res = + ImmutableSortedMap.Builder.emptyMap(DocumentKey.comparator()); + for (Document doc : docs) { + res = res.insert(doc.getKey(), doc); + } + return res; + } + + public static TargetChange targetChange( + ByteString resumeToken, + boolean current, + @Nullable Collection addedDocuments, + @Nullable Collection modifiedDocuments, + @Nullable Collection removedDocuments) { + ImmutableSortedSet addedDocumentKeys = DocumentKey.emptyKeySet(); + ImmutableSortedSet modifiedDocumentKeys = DocumentKey.emptyKeySet(); + ImmutableSortedSet removedDocumentKeys = DocumentKey.emptyKeySet(); + + if (addedDocuments != null) { + for (Document document : addedDocuments) { + addedDocumentKeys = addedDocumentKeys.insert(document.getKey()); + } + } + + if (modifiedDocuments != null) { + for (Document document : modifiedDocuments) { + modifiedDocumentKeys = modifiedDocumentKeys.insert(document.getKey()); + } + } + + if (removedDocuments != null) { + for (MaybeDocument document : removedDocuments) { + removedDocumentKeys = removedDocumentKeys.insert(document.getKey()); + } + } + + return new TargetChange( + resumeToken, current, addedDocumentKeys, modifiedDocumentKeys, removedDocumentKeys); + } + + public static TargetChange ackTarget(Document... docs) { + return targetChange(ByteString.EMPTY, true, Arrays.asList(docs), null, null); + } + + public static Map activeQueries(Iterable targets) { + Query query = query("foo"); + Map listenMap = new HashMap<>(); + for (Integer targetId : targets) { + QueryData queryData = + new QueryData(query, targetId, ARBITRARY_SEQUENCE_NUMBER, QueryPurpose.LISTEN); + listenMap.put(targetId, queryData); + } + return listenMap; + } + + public static Map activeQueries(Integer... targets) { + return activeQueries(asList(targets)); + } + + public static Map activeLimboQueries( + String docKey, Iterable targets) { + Query query = query(docKey); + Map listenMap = new HashMap<>(); + for (Integer targetId : targets) { + QueryData queryData = + new QueryData(query, targetId, ARBITRARY_SEQUENCE_NUMBER, QueryPurpose.LIMBO_RESOLUTION); + listenMap.put(targetId, queryData); + } + return listenMap; + } + + public static Map activeLimboQueries(String docKey, Integer... targets) { + return activeLimboQueries(docKey, asList(targets)); + } + + public static RemoteEvent addedRemoteEvent( + MaybeDocument doc, List updatedInTargets, List removedFromTargets) { + DocumentChange change = + new DocumentChange(updatedInTargets, removedFromTargets, doc.getKey(), doc); + WatchChangeAggregator aggregator = + new WatchChangeAggregator( + new WatchChangeAggregator.TargetMetadataProvider() { + @Override + public ImmutableSortedSet getRemoteKeysForTarget(int targetId) { + return DocumentKey.emptyKeySet(); + } + + @Override + public QueryData getQueryDataForTarget(int targetId) { + return queryData(targetId, QueryPurpose.LISTEN, doc.getKey().toString()); + } + }); + aggregator.handleDocumentChange(change); + return aggregator.createRemoteEvent(doc.getVersion()); + } + + public static RemoteEvent updateRemoteEvent( + MaybeDocument doc, List updatedInTargets, List removedFromTargets) { + return updateRemoteEvent(doc, updatedInTargets, removedFromTargets, Collections.emptyList()); + } + + public static RemoteEvent updateRemoteEvent( + MaybeDocument doc, + List updatedInTargets, + List removedFromTargets, + List limboTargets) { + DocumentChange change = + new DocumentChange(updatedInTargets, removedFromTargets, doc.getKey(), doc); + WatchChangeAggregator aggregator = + new WatchChangeAggregator( + new WatchChangeAggregator.TargetMetadataProvider() { + @Override + public ImmutableSortedSet getRemoteKeysForTarget(int targetId) { + return DocumentKey.emptyKeySet().insert(doc.getKey()); + } + + @Override + public QueryData getQueryDataForTarget(int targetId) { + boolean isLimbo = + !(updatedInTargets.contains(targetId) || removedFromTargets.contains(targetId)); + QueryPurpose purpose = + isLimbo ? QueryPurpose.LIMBO_RESOLUTION : QueryPurpose.LISTEN; + return queryData(targetId, purpose, doc.getKey().toString()); + } + }); + aggregator.handleDocumentChange(change); + return aggregator.createRemoteEvent(doc.getVersion()); + } + + public static SetMutation setMutation(String path, Map values) { + return new SetMutation(key(path), wrapObject(values), Precondition.NONE); + } + + public static PatchMutation patchMutation(String path, Map values) { + return patchMutation(path, values, null); + } + + public static PatchMutation patchMutation( + String path, Map values, @Nullable List updateMask) { + ObjectValue objectValue = ObjectValue.emptyObject(); + ArrayList objectMask = new ArrayList<>(); + for (Entry entry : values.entrySet()) { + FieldPath fieldPath = field(entry.getKey()); + objectMask.add(fieldPath); + if (!entry.getValue().equals(DELETE_SENTINEL)) { + FieldValue parsedValue = wrap(entry.getValue()); + objectValue = objectValue.set(fieldPath, parsedValue); + } + } + + boolean merge = updateMask != null; + + // We sort the fieldMaskPaths to make the order deterministic in tests. (Otherwise, when we + // flatten a Set to a proto repeated field, we'll end up comparing in iterator order and + // possibly consider {foo,bar} != {bar,foo}.) + SortedSet fieldMaskPaths = new TreeSet<>(merge ? updateMask : objectMask); + + return new PatchMutation( + key(path), + objectValue, + FieldMask.fromSet(fieldMaskPaths), + merge ? Precondition.NONE : Precondition.exists(true)); + } + + public static DeleteMutation deleteMutation(String path) { + return new DeleteMutation(key(path), Precondition.NONE); + } + + /** + * Creates a TransformMutation by parsing any FieldValue sentinels in the provided data. The data + * is expected to use dotted-notation for nested fields (i.e. { "foo.bar": FieldValue.foo() } and + * must not contain any non-sentinel data. + */ + public static TransformMutation transformMutation(String path, Map data) { + UserDataConverter dataConverter = new UserDataConverter(DatabaseId.forProject("project")); + ParsedUpdateData result = dataConverter.parseUpdateData(data); + + // The order of the transforms doesn't matter, but we sort them so tests can assume a particular + // order. + ArrayList fieldTransforms = new ArrayList<>(result.getFieldTransforms()); + Collections.sort( + fieldTransforms, (ft1, ft2) -> ft1.getFieldPath().compareTo(ft2.getFieldPath())); + + return new TransformMutation(key(path), fieldTransforms); + } + + public static MutationResult mutationResult(long version) { + return new MutationResult(version(version), null); + } + + public static LocalViewChanges viewChanges( + int targetId, List addedKeys, List removedKeys) { + ImmutableSortedSet added = DocumentKey.emptyKeySet(); + for (String keyPath : addedKeys) { + added = added.insert(key(keyPath)); + } + ImmutableSortedSet removed = DocumentKey.emptyKeySet(); + for (String keyPath : removedKeys) { + removed = removed.insert(key(keyPath)); + } + return new LocalViewChanges(targetId, added, removed); + } + + /** Creates a resume token to match the given snapshot version. */ + @Nullable + public static ByteString resumeToken(long snapshotVersion) { + if (snapshotVersion == 0) { + return null; + } + + String snapshotString = "snapshot-" + snapshotVersion; + return ByteString.copyFrom(snapshotString, Charsets.UTF_8); + } + + @NonNull + private static ByteString resumeToken(SnapshotVersion snapshotVersion) { + if (snapshotVersion.equals(SnapshotVersion.NONE)) { + return ByteString.EMPTY; + } else { + return ByteString.copyFromUtf8(snapshotVersion.toString()); + } + } + + public static ByteString streamToken(String contents) { + return ByteString.copyFrom(contents, Charsets.UTF_8); + } + + private static Map fromJsonString(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(json, new TypeReference>() {}); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Map fromSingleQuotedString(String json) { + return fromJsonString(json.replace("'", "\"")); + } + + /** Converts the values of an ImmutableSortedMap into a list, preserving key order. */ + public static List values(ImmutableSortedMap map) { + List result = new ArrayList<>(); + for (Map.Entry entry : map) { + result.add(entry.getValue()); + } + return result; + } + + /** + * Asserts that the actual set is equal to the expected one. + * + * @param expected A list of the expected contents of the set, in order. + * @param actual The set to compare against. + * @param The type of the values of in common between the expected list and actual set. + */ + // PORTING NOTE: JUnit and XCTest use reversed conventions on expected and actual values :-(. + public static void assertSetEquals(List expected, ImmutableSortedSet actual) { + List actualList = Lists.newArrayList(actual); + assertEquals(expected, actualList); + } + + /** + * Asserts that the actual set is equal to the expected one. + * + * @param expected A list of the expected contents of the set, in order. + * @param actual The set to compare against. + * @param The type of the values of in common between the expected list and actual set. + */ + // PORTING NOTE: JUnit and XCTest use reversed conventions on expected and actual values :-(. + public static void assertSetEquals(List expected, Set actual) { + Set expectedSet = Sets.newHashSet(expected); + assertEquals(expectedSet, actual); + } + + /** Asserts that the given runnable block fails with an internal error. */ + public static void assertFails(Runnable block) { + try { + block.run(); + } catch (AssertionError e) { + assertThat(e).hasMessageThat().startsWith("INTERNAL ASSERTION FAILED:"); + // Otherwise success + return; + } + fail("Should have failed"); + } + + public static void assertDoesNotThrow(Runnable block) { + try { + block.run(); + } catch (Exception e) { + fail("Should not have thrown " + e); + } + } + + // TODO: We could probably do some de-duplication between assertFails / expectError. + /** Expects runnable to throw an exception with a specific error message. */ + public static void expectError(Runnable runnable, String exceptionMessage) { + expectError(runnable, exceptionMessage, /*context=*/ null); + } + + /** + * Expects runnable to throw an exception with a specific error message. An optional context (e.g. + * "for bad_data") can be provided which will be displayed in any resulting failure message. + */ + public static void expectError(Runnable runnable, String exceptionMessage, String context) { + boolean exceptionThrown = false; + try { + runnable.run(); + } catch (Throwable throwable) { + exceptionThrown = true; + String contextMessage = "Expected exception message was incorrect"; + if (context != null) { + contextMessage += " (" + context + ")"; + } + assertEquals(contextMessage, exceptionMessage, throwable.getMessage()); + } + if (!exceptionThrown) { + context = (context == null) ? "" : context; + fail( + "Expected exception with message '" + + exceptionMessage + + "' but no exception was thrown" + + context); + } + } +} diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/ValidationTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/ValidationTest.java index 1990f7ccf06..8ce172c91f7 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/ValidationTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/ValidationTest.java @@ -332,11 +332,7 @@ public void fieldPathsMustNotHaveInvalidSegments() { List badFieldPaths = asList("foo~bar", "foo*bar", "foo/bar", "foo[1", "foo]1", "foo[1]"); for (String fieldPath : badFieldPaths) { - String reason = - "Invalid field path (" - + fieldPath - + "). Paths must not contain '~', '*', '/', '[', or ']'"; - verifyFieldPathThrows(fieldPath, reason); + verifyFieldPathThrows(fieldPath, "Use FieldPath.of() for field names containing '~*/[]'."); } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/FieldPath.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/FieldPath.java index af3360926c3..ee8c1bc51ec 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/FieldPath.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/FieldPath.java @@ -84,8 +84,7 @@ public static FieldPath documentId() { static FieldPath fromDotSeparatedPath(@NonNull String path) { checkNotNull(path, "Provided field path must not be null."); checkArgument( - !RESERVED.matcher(path).find(), - "Invalid field path (" + path + "). Paths must not contain '~', '*', '/', '[', or ']'"); + !RESERVED.matcher(path).find(), "Use FieldPath.of() for field names containing '~*/[]'."); try { // By default, split() doesn't return empty leading and trailing segments. This can be enabled // by passing "-1" as the limit. diff --git a/firebase-functions/firebase-functions.gradle b/firebase-functions/firebase-functions.gradle index 64d888dd000..dce829719ba 100644 --- a/firebase-functions/firebase-functions.gradle +++ b/firebase-functions/firebase-functions.gradle @@ -29,7 +29,7 @@ android { compileSdkVersion project.targetSdkVersion defaultConfig { targetSdkVersion project.targetSdkVersion - minSdkVersion project.minSdkVersion + minSdkVersion 16 versionName version multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -50,16 +50,16 @@ android { dependencies { implementation project(':firebase-common') - implementation 'com.google.android.gms:play-services-basement:16.2.0' - implementation 'com.google.android.gms:play-services-base:16.1.0' - implementation 'com.google.android.gms:play-services-tasks:16.0.1' - implementation ('com.google.firebase:firebase-iid:17.0.3') { + implementation 'com.google.android.gms:play-services-basement:17.0.0' + implementation 'com.google.android.gms:play-services-base:17.0.0' + implementation 'com.google.android.gms:play-services-tasks:17.0.0' + implementation ('com.google.firebase:firebase-iid:19.0.0') { exclude group: 'com.google.firebase', module: 'firebase-common' } implementation ('com.google.firebase:firebase-auth-interop:17.0.0') { exclude group: 'com.google.firebase', module: 'firebase-common' } - implementation 'com.google.firebase:firebase-iid-interop:16.0.1' + implementation 'com.google.firebase:firebase-iid-interop:17.0.0' implementation 'com.squareup.okhttp3:okhttp:3.12.1' diff --git a/firebase-functions/gradle.properties b/firebase-functions/gradle.properties index 4c7da9c1258..904cf7b5e5a 100644 --- a/firebase-functions/gradle.properties +++ b/firebase-functions/gradle.properties @@ -1,3 +1,3 @@ -version=16.3.1 -latestReleasedVersion=16.3.0 +version=18.0.1 +latestReleasedVersion=18.0.0 android.enableUnitTestBinaryResources=true diff --git a/firebase-functions/ktx/ktx.gradle b/firebase-functions/ktx/ktx.gradle index 48a014c76ae..f2ce73b2641 100644 --- a/firebase-functions/ktx/ktx.gradle +++ b/firebase-functions/ktx/ktx.gradle @@ -27,7 +27,7 @@ firebaseLibrary { android { compileSdkVersion project.targetSdkVersion defaultConfig { - minSdkVersion project.minSdkVersion + minSdkVersion 16 multiDexEnabled true targetSdkVersion project.targetSdkVersion versionName version @@ -50,7 +50,7 @@ dependencies { implementation project(':firebase-common:ktx') implementation project(':firebase-functions') implementation 'androidx.annotation:annotation:1.1.0' - implementation 'com.google.android.gms:play-services-tasks:16.0.1' + implementation 'com.google.android.gms:play-services-tasks:17.0.0' androidTestImplementation 'junit:junit:4.12' androidTestImplementation "com.google.truth:truth:$googleTruthVersion" diff --git a/firebase-functions/ktx/src/androidTest/AndroidManifest.xml b/firebase-functions/ktx/src/androidTest/AndroidManifest.xml index 53cd9caa09e..e00dc0c7ec9 100644 --- a/firebase-functions/ktx/src/androidTest/AndroidManifest.xml +++ b/firebase-functions/ktx/src/androidTest/AndroidManifest.xml @@ -1,7 +1,7 @@ - + diff --git a/firebase-functions/ktx/src/main/AndroidManifest.xml b/firebase-functions/ktx/src/main/AndroidManifest.xml index 8eda8b99bae..bcdb806609a 100644 --- a/firebase-functions/ktx/src/main/AndroidManifest.xml +++ b/firebase-functions/ktx/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ - + - + diff --git a/firebase-segmentation/firebase-segmentation.gradle b/firebase-segmentation/firebase-segmentation.gradle index 88ea12a1ed0..dc4606715c5 100644 --- a/firebase-segmentation/firebase-segmentation.gradle +++ b/firebase-segmentation/firebase-segmentation.gradle @@ -91,12 +91,17 @@ dependencies { implementation 'androidx.multidex:multidex:2.0.0' implementation 'com.google.android.gms:play-services-tasks:16.0.1' + compileOnly "com.google.auto.value:auto-value-annotations:1.6.5" + annotationProcessor "com.google.auto.value:auto-value:1.6.2" + testImplementation 'androidx.test:core:1.2.0' testImplementation 'junit:junit:4.12' testImplementation "org.robolectric:robolectric:$robolectricVersion" - androidTestImplementation 'androidx.annotation:annotation:1.1.0' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation "androidx.annotation:annotation:1.1.0" androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation "com.google.truth:truth:$googleTruthVersion" + androidTestImplementation 'junit:junit:4.12' } diff --git a/firebase-segmentation/src/androidTest/java/com/google/firebase/segmentation/CustomInstallationIdCacheTest.java b/firebase-segmentation/src/androidTest/java/com/google/firebase/segmentation/CustomInstallationIdCacheTest.java new file mode 100644 index 00000000000..5783294cfa3 --- /dev/null +++ b/firebase-segmentation/src/androidTest/java/com/google/firebase/segmentation/CustomInstallationIdCacheTest.java @@ -0,0 +1,91 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.segmentation; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.gms.tasks.Tasks; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Instrumented tests for {@link CustomInstallationIdCache} */ +@RunWith(AndroidJUnit4.class) +public class CustomInstallationIdCacheTest { + + private FirebaseApp firebaseApp0; + private FirebaseApp firebaseApp1; + private CustomInstallationIdCache cache; + + @Before + public void setUp() { + FirebaseApp.clearInstancesForTest(); + firebaseApp0 = + FirebaseApp.initializeApp( + InstrumentationRegistry.getContext(), + new FirebaseOptions.Builder().setApplicationId("1:123456789:android:abcdef").build()); + firebaseApp1 = + FirebaseApp.initializeApp( + InstrumentationRegistry.getContext(), + new FirebaseOptions.Builder().setApplicationId("1:987654321:android:abcdef").build(), + "firebase_app_1"); + cache = CustomInstallationIdCache.getInstance(); + } + + @After + public void cleanUp() throws Exception { + Tasks.await(cache.clearAll()); + } + + @Test + public void testReadCacheEntry_Null() { + assertNull(cache.readCacheEntryValue(firebaseApp0)); + assertNull(cache.readCacheEntryValue(firebaseApp1)); + } + + @Test + public void testUpdateAndReadCacheEntry() throws Exception { + assertTrue( + Tasks.await( + cache.insertOrUpdateCacheEntry( + firebaseApp0, + CustomInstallationIdCacheEntryValue.create( + "123456", "cAAAAAAAAAA", CustomInstallationIdCache.CacheStatus.PENDING)))); + CustomInstallationIdCacheEntryValue entryValue = cache.readCacheEntryValue(firebaseApp0); + assertThat(entryValue.getCustomInstallationId()).isEqualTo("123456"); + assertThat(entryValue.getFirebaseInstanceId()).isEqualTo("cAAAAAAAAAA"); + assertThat(entryValue.getCacheStatus()) + .isEqualTo(CustomInstallationIdCache.CacheStatus.PENDING); + assertNull(cache.readCacheEntryValue(firebaseApp1)); + + assertTrue( + Tasks.await( + cache.insertOrUpdateCacheEntry( + firebaseApp0, + CustomInstallationIdCacheEntryValue.create( + "123456", "cAAAAAAAAAA", CustomInstallationIdCache.CacheStatus.SYNCED)))); + entryValue = cache.readCacheEntryValue(firebaseApp0); + assertThat(entryValue.getCustomInstallationId()).isEqualTo("123456"); + assertThat(entryValue.getFirebaseInstanceId()).isEqualTo("cAAAAAAAAAA"); + assertThat(entryValue.getCacheStatus()).isEqualTo(CustomInstallationIdCache.CacheStatus.SYNCED); + } +} diff --git a/firebase-segmentation/src/main/java/com/google/firebase/segmentation/CustomInstallationIdCache.java b/firebase-segmentation/src/main/java/com/google/firebase/segmentation/CustomInstallationIdCache.java new file mode 100644 index 00000000000..5096a265714 --- /dev/null +++ b/firebase-segmentation/src/main/java/com/google/firebase/segmentation/CustomInstallationIdCache.java @@ -0,0 +1,130 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.segmentation; + +import android.content.Context; +import android.content.SharedPreferences; +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; +import com.google.android.gms.common.util.Strings; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.firebase.FirebaseApp; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +class CustomInstallationIdCache { + + // Status of each cache entry + // NOTE: never change the ordinal of the enum values because the enum values are stored in cache + // as their ordinal numbers. + enum CacheStatus { + // Cache entry is synced to Firebase backend + SYNCED, + // Cache entry is waiting for Firebase backend response or pending internal retry for retryable + // errors. + PENDING, + // Cache entry is not accepted by Firebase backend. + ERROR, + } + + private static final String SHARED_PREFS_NAME = "CustomInstallationIdCache"; + + private static final String CUSTOM_INSTALLATION_ID_KEY = "Cid"; + private static final String INSTANCE_ID_KEY = "Iid"; + private static final String CACHE_STATUS_KEY = "Status"; + + private static CustomInstallationIdCache singleton = null; + private final Executor ioExecuter; + private final SharedPreferences prefs; + + static synchronized CustomInstallationIdCache getInstance() { + if (singleton == null) { + singleton = new CustomInstallationIdCache(); + } + return singleton; + } + + private CustomInstallationIdCache() { + // Since different FirebaseApp in the same Android application should have the same application + // context and same dir path, so that use the context of the default FirebaseApp to create the + // shared preferences. + prefs = + FirebaseApp.getInstance() + .getApplicationContext() + .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE); + + ioExecuter = Executors.newFixedThreadPool(2); + } + + @Nullable + synchronized CustomInstallationIdCacheEntryValue readCacheEntryValue(FirebaseApp firebaseApp) { + String cid = + prefs.getString(getSharedPreferencesKey(firebaseApp, CUSTOM_INSTALLATION_ID_KEY), null); + String iid = prefs.getString(getSharedPreferencesKey(firebaseApp, INSTANCE_ID_KEY), null); + int status = prefs.getInt(getSharedPreferencesKey(firebaseApp, CACHE_STATUS_KEY), -1); + + if (Strings.isEmptyOrWhitespace(cid) || Strings.isEmptyOrWhitespace(iid) || status == -1) { + return null; + } + + return CustomInstallationIdCacheEntryValue.create(cid, iid, CacheStatus.values()[status]); + } + + synchronized Task insertOrUpdateCacheEntry( + FirebaseApp firebaseApp, CustomInstallationIdCacheEntryValue entryValue) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putString( + getSharedPreferencesKey(firebaseApp, CUSTOM_INSTALLATION_ID_KEY), + entryValue.getCustomInstallationId()); + editor.putString( + getSharedPreferencesKey(firebaseApp, INSTANCE_ID_KEY), entryValue.getFirebaseInstanceId()); + editor.putInt( + getSharedPreferencesKey(firebaseApp, CACHE_STATUS_KEY), + entryValue.getCacheStatus().ordinal()); + return commitSharedPreferencesEditAsync(editor); + } + + synchronized Task clear(FirebaseApp firebaseApp) { + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(getSharedPreferencesKey(firebaseApp, CUSTOM_INSTALLATION_ID_KEY)); + editor.remove(getSharedPreferencesKey(firebaseApp, INSTANCE_ID_KEY)); + editor.remove(getSharedPreferencesKey(firebaseApp, CACHE_STATUS_KEY)); + return commitSharedPreferencesEditAsync(editor); + } + + @RestrictTo(RestrictTo.Scope.TESTS) + synchronized Task clearAll() { + SharedPreferences.Editor editor = prefs.edit(); + editor.clear(); + return commitSharedPreferencesEditAsync(editor); + } + + private static String getSharedPreferencesKey(FirebaseApp firebaseApp, String key) { + return String.format("%s|%s", firebaseApp.getPersistenceKey(), key); + } + + private Task commitSharedPreferencesEditAsync(SharedPreferences.Editor editor) { + TaskCompletionSource result = new TaskCompletionSource(); + ioExecuter.execute( + new Runnable() { + @Override + public void run() { + result.setResult(editor.commit()); + } + }); + return result.getTask(); + } +} diff --git a/firebase-segmentation/src/main/java/com/google/firebase/segmentation/CustomInstallationIdCacheEntryValue.java b/firebase-segmentation/src/main/java/com/google/firebase/segmentation/CustomInstallationIdCacheEntryValue.java new file mode 100644 index 00000000000..c79d5f091a9 --- /dev/null +++ b/firebase-segmentation/src/main/java/com/google/firebase/segmentation/CustomInstallationIdCacheEntryValue.java @@ -0,0 +1,37 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.segmentation; + +import com.google.auto.value.AutoValue; +import com.google.firebase.segmentation.CustomInstallationIdCache.CacheStatus; + +/** + * This class represents a cache entry value in {@link CustomInstallationIdCache}, which contains a + * Firebase instance id, a custom installation id and the cache status of this entry. + */ +@AutoValue +abstract class CustomInstallationIdCacheEntryValue { + abstract String getCustomInstallationId(); + + abstract String getFirebaseInstanceId(); + + abstract CacheStatus getCacheStatus(); + + static CustomInstallationIdCacheEntryValue create( + String customInstallationId, String firebaseInstanceId, CacheStatus cacheStatus) { + return new AutoValue_CustomInstallationIdCacheEntryValue( + customInstallationId, firebaseInstanceId, cacheStatus); + } +} diff --git a/firebase-storage/firebase-storage.gradle b/firebase-storage/firebase-storage.gradle index 4abc6369812..2fa2abb6542 100644 --- a/firebase-storage/firebase-storage.gradle +++ b/firebase-storage/firebase-storage.gradle @@ -20,6 +20,7 @@ plugins { firebaseLibrary { testLab.enabled = true publishJavadoc = true + publishSources = true } android { @@ -76,9 +77,9 @@ android { dependencies { implementation project(':firebase-common') - implementation 'com.google.android.gms:play-services-base:16.1.0' - implementation 'com.google.android.gms:play-services-tasks:16.0.1' - implementation('com.google.firebase:firebase-auth-interop:17.0.0') { + implementation 'com.google.android.gms:play-services-base:17.0.0' + implementation 'com.google.android.gms:play-services-tasks:17.0.0' + implementation('com.google.firebase:firebase-auth-interop:18.0.0') { exclude group: "com.google.firebase", module: "firebase-common" } diff --git a/firebase-storage/gradle.properties b/firebase-storage/gradle.properties index f4ae1a57594..b2337aeb5ba 100644 --- a/firebase-storage/gradle.properties +++ b/firebase-storage/gradle.properties @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=17.0.0 -latestReleasedVersion=16.1.0 +version=18.0.1 +latestReleasedVersion=18.0.0 android.enableUnitTestBinaryResources=true diff --git a/firebase-storage/src/main/java/com/google/firebase/storage/StorageTaskManager.java b/firebase-storage/src/main/java/com/google/firebase/storage/StorageTaskManager.java index f703a9d845f..8814afcfbe8 100644 --- a/firebase-storage/src/main/java/com/google/firebase/storage/StorageTaskManager.java +++ b/firebase-storage/src/main/java/com/google/firebase/storage/StorageTaskManager.java @@ -32,7 +32,7 @@ /*package*/ class StorageTaskManager { private static final StorageTaskManager _instance = new StorageTaskManager(); - private final Map> inProgressTasks = new HashMap<>(); + private final Map>> inProgressTasks = new HashMap<>(); private final Object syncObject = new Object(); @@ -44,7 +44,7 @@ public List getUploadTasksUnder(@NonNull StorageReference parent) { synchronized (syncObject) { ArrayList inProgressList = new ArrayList<>(); String parentPath = parent.toString(); - for (Map.Entry> entry : inProgressTasks.entrySet()) { + for (Map.Entry>> entry : inProgressTasks.entrySet()) { if (entry.getKey().startsWith(parentPath)) { StorageTask task = entry.getValue().get(); if (task instanceof UploadTask) { @@ -60,9 +60,9 @@ public List getDownloadTasksUnder(@NonNull StorageReference pa synchronized (syncObject) { ArrayList inProgressList = new ArrayList<>(); String parentPath = parent.toString(); - for (Map.Entry> entry : inProgressTasks.entrySet()) { + for (Map.Entry>> entry : inProgressTasks.entrySet()) { if (entry.getKey().startsWith(parentPath)) { - StorageTask task = entry.getValue().get(); + StorageTask task = entry.getValue().get(); if (task instanceof FileDownloadTask) { inProgressList.add((FileDownloadTask) task); } @@ -72,19 +72,19 @@ public List getDownloadTasksUnder(@NonNull StorageReference pa } } - public void ensureRegistered(StorageTask targetTask) { + public void ensureRegistered(StorageTask targetTask) { synchronized (syncObject) { // ensure *this* is added to the in progress list inProgressTasks.put(targetTask.getStorage().toString(), new WeakReference<>(targetTask)); } } - public void unRegister(StorageTask targetTask) { + public void unRegister(StorageTask targetTask) { synchronized (syncObject) { // ensure *this* is added to the in progress list String key = targetTask.getStorage().toString(); - WeakReference weakReference = inProgressTasks.get(key); - StorageTask task = weakReference != null ? weakReference.get() : null; + WeakReference> weakReference = inProgressTasks.get(key); + StorageTask task = weakReference != null ? weakReference.get() : null; if (task == null || task == targetTask) { inProgressTasks.remove(key); } diff --git a/firebase-storage/test-app/test-app.gradle b/firebase-storage/test-app/test-app.gradle index b45d4f1b021..ab9fadb4dfd 100644 --- a/firebase-storage/test-app/test-app.gradle +++ b/firebase-storage/test-app/test-app.gradle @@ -49,10 +49,10 @@ dependencies { // We intentionally use an open ended version to pick up any SNAPSHOT // versions published to the root project' s build/ directory. - implementation 'com.google.firebase:firebase-auth:17+' - implementation 'com.google.firebase:firebase-common:17+' - implementation 'com.google.android.gms:play-services-basement:16.2.0' - implementation 'com.google.android.gms:play-services-base:16.1.0' + implementation 'com.google.firebase:firebase-auth:18+' + implementation 'com.google.firebase:firebase-common:18+' + implementation 'com.google.android.gms:play-services-basement:17.0.0' + implementation 'com.google.android.gms:play-services-base:17.0.0' implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.appcompat:appcompat:1.0.2' diff --git a/protolite-well-known-types/gradle.properties b/protolite-well-known-types/gradle.properties index c763f64467b..54be3eb478f 100644 --- a/protolite-well-known-types/gradle.properties +++ b/protolite-well-known-types/gradle.properties @@ -1,2 +1,2 @@ -version=16.0.2 -latestReleasedVersion=16.0.1 +version=17.0.1 +latestReleasedVersion=17.0.0 diff --git a/root-project.gradle b/root-project.gradle index 1d519f9278f..664cce0efd9 100644 --- a/root-project.gradle +++ b/root-project.gradle @@ -52,6 +52,7 @@ ext { apply plugin: com.google.firebase.gradle.plugins.publish.PublishingPlugin apply plugin: com.google.firebase.gradle.plugins.ci.ContinuousIntegrationPlugin +apply plugin: com.google.firebase.gradle.plugins.ci.SmokeTestsPlugin apply plugin: com.google.firebase.gradle.plugins.ci.metrics.MetricsPlugin firebaseContinuousIntegration { @@ -139,38 +140,6 @@ configure(subprojects) { } } -/** - * Disable "debug" build type for all subprojects. - * - * They are identical to "release" and are not used in either release or smoke tests. Disabling them - * to reduce the number of tests we run on pre/post-submit. - */ -configure(subprojects) { - afterEvaluate { Project sub -> - if (!sub.plugins.hasPlugin('com.android.library') && !sub.plugins.hasPlugin('com.android.application')) { - return - } - - // skip debug unit tests in CI - // TODO(vkryachko): provide ability for teams to control this if needed - if (System.getenv().containsKey("FIREBASE_CI")) { - sub.tasks.all {Task task -> - if (task.name == 'testDebugUnitTest') { - task.enabled = false - } - } - } - sub.android { - testBuildType "release" - - buildTypes { - // In the case of and android library signing config only affects instrumentation test APK. - // We need it signed with default debug credentials in order for FTL to accept the APK. - release.signingConfig = debug.signingConfig - } - } - } -} /** * Configure "Preguarding" and Desugaring for the subprojects. diff --git a/smoke-tests/build.gradle b/smoke-tests/build.gradle index 55b7f1d3aac..2c7be0fe133 100644 --- a/smoke-tests/build.gradle +++ b/smoke-tests/build.gradle @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + buildscript { repositories { google() @@ -114,4 +118,12 @@ dependencies { storageImplementation "com.google.firebase:firebase-storage" } +clean.doLast { + def paths = Files.newDirectoryStream(Paths.get("."), "build-*") + + for (Path path : paths) { + project.delete "$path/" + } +} + apply plugin: "com.google.gms.google-services" diff --git a/tools/measurement/apksize/src/firestore/firestore.gradle b/tools/measurement/apksize/src/firestore/firestore.gradle index 6b78e0f5598..f8640c43e7c 100644 --- a/tools/measurement/apksize/src/firestore/firestore.gradle +++ b/tools/measurement/apksize/src/firestore/firestore.gradle @@ -31,6 +31,6 @@ android { dependencies { firestoreImplementation project(":firebase-firestore") firestoreImplementation "com.google.android.gms:play-services-auth:16.0.1" - firestoreImplementation "com.google.android.gms:play-services-base:16.1.0" + firestoreImplementation "com.google.android.gms:play-services-base:17.0.0" firestoreImplementation 'androidx.legacy:legacy-support-v4:1.0.0' } diff --git a/transport/transport-api/gradle.properties b/transport/transport-api/gradle.properties index a9aff5b2e2e..983642acd1f 100644 --- a/transport/transport-api/gradle.properties +++ b/transport/transport-api/gradle.properties @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=1.0.1 -latestReleasedVersion=1.0.0 +version=2.0.1 +latestReleasedVersion=2.0.0 diff --git a/transport/transport-backend-cct/gradle.properties b/transport/transport-backend-cct/gradle.properties index 4e6041d681d..7708203d64e 100644 --- a/transport/transport-backend-cct/gradle.properties +++ b/transport/transport-backend-cct/gradle.properties @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=1.0.1 -latestReleasedVersion=1.0.0 +version=2.0.1 +latestReleasedVersion=2.0.0 firebaseSkipPreguard=false diff --git a/transport/transport-runtime/gradle.properties b/transport/transport-runtime/gradle.properties index 0cbb89724a3..18a78760e82 100644 --- a/transport/transport-runtime/gradle.properties +++ b/transport/transport-runtime/gradle.properties @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=1.0.1 -latestReleasedVersion=1.0.0 +version=2.0.1 +latestReleasedVersion=2.0.0 android.enableUnitTestBinaryResources=true