diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 52c45abc18b..c706aa614bd 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -119,6 +119,7 @@ jobs: module: ${{ fromJSON(needs.determine_changed.outputs.modules) }} exclude: - module: :firebase-firestore + - module: :firebase-functions:ktx steps: - uses: actions/checkout@v4.1.1 diff --git a/.github/workflows/create_releases.yml b/.github/workflows/create_releases.yml index 8920ee50d09..c47cfac9713 100644 --- a/.github/workflows/create_releases.yml +++ b/.github/workflows/create_releases.yml @@ -40,7 +40,7 @@ jobs: ./gradlew generateReleaseConfig -PcurrentRelease=${{ inputs.name }} -PpastRelease=${{ inputs.past-name }} -PprintOutput=true - name: Create Pull Request - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f with: base: 'releases/${{ inputs.name }}' branch: 'releases/${{ inputs.name }}.release' diff --git a/.github/workflows/dataconnect.yml b/.github/workflows/dataconnect.yml new file mode 100644 index 00000000000..3a0b9aa4b93 --- /dev/null +++ b/.github/workflows/dataconnect.yml @@ -0,0 +1,238 @@ +name: Data Connect Integration Tests + +on: + workflow_dispatch: + inputs: + javaVersion: + androidEmulatorApiLevel: + nodeJsVersion: + firebaseToolsVersion: + gradleInfoLog: + type: boolean + pull_request: + paths: + - .github/workflows/dataconnect.yml + - 'firebase-dataconnect/**' + - '!firebase-dataconnect/demo/**' + - '!firebase-dataconnect/scripts/**' + - '!firebase-dataconnect/**/*.md' + - '!firebase-dataconnect/**/*.txt' + schedule: + - cron: '0 11 * * *' # Run nightly at 11am UTC (3am Pacific, 6am Eastern) + +env: + FDC_JAVA_VERSION: ${{ inputs.javaVersion || '17' }} + FDC_ANDROID_EMULATOR_API_LEVEL: ${{ inputs.androidEmulatorApiLevel || '34' }} + FDC_NODEJS_VERSION: ${{ inputs.nodeJsVersion || '20' }} + FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '13.29.1' }} + FDC_FIREBASE_TOOLS_DIR: /tmp/firebase-tools + FDC_FIREBASE_COMMAND: /tmp/firebase-tools/node_modules/.bin/firebase + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + integration-test: + continue-on-error: false + runs-on: ubuntu-latest + + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: password + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - uses: actions/checkout@v4 + with: + show-progress: false + + - uses: actions/setup-java@v4 + with: + java-version: ${{ env.FDC_JAVA_VERSION }} + distribution: temurin + + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.FDC_NODEJS_VERSION }} + + - name: install firebase-tools + run: | + set -v + mkdir -p ${{ env.FDC_FIREBASE_TOOLS_DIR }} + cd ${{ env.FDC_FIREBASE_TOOLS_DIR }} + echo '{}' > package.json + npm install --fund=false --audit=false --save --save-exact firebase-tools@${{ env.FDC_FIREBASE_TOOLS_VERSION }} + + - name: Restore Gradle cache + id: restore-gradle-cache + uses: actions/cache/restore@v4 + if: github.event_name != 'schedule' + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-cache-jqnvfzw6w7-${{ github.run_id }} + restore-keys: | + gradle-cache-jqnvfzw6w7- + + - name: tool versions + continue-on-error: true + run: | + function run_cmd { + echo "===============================================================================" + echo "Running Command: $*" + ("$@" 2>&1) || echo "WARNING: command failed with non-zero exit code $?: $*" + } + + run_cmd uname -a + run_cmd which java + run_cmd java -version + run_cmd which javac + run_cmd javac -version + run_cmd which node + run_cmd node --version + run_cmd ${{ env.FDC_FIREBASE_COMMAND }} --version + run_cmd ./gradlew --version + + - name: Gradle assembleDebugAndroidTest + run: | + set -v + + # Speed up build times and also avoid configuring firebase-crashlytics-ndk + # which is finicky integrating with the Android NDK. + echo >> gradle.properties + echo "org.gradle.configureondemand=true" >> gradle.properties + + ./gradlew \ + --profile \ + ${{ (inputs.gradleInfoLog && '--info') || '' }} \ + :firebase-dataconnect:assembleDebugAndroidTest + + - name: Save Gradle cache + uses: actions/cache/save@v4 + if: github.event_name == 'schedule' + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ steps.restore-gradle-cache.outputs.cache-primary-key }} + + - name: Enable KVM group permissions for Android Emulator + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Restore AVD cache + uses: actions/cache/restore@v4 + if: github.event_name != 'schedule' + id: restore-avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-cache-zhdsn586je-api${{ env.FDC_ANDROID_EMULATOR_API_LEVEL }}-${{ github.run_id }} + restore-keys: | + avd-cache-zhdsn586je-api${{ env.FDC_ANDROID_EMULATOR_API_LEVEL }}- + + - name: Create AVD + if: github.event_name == 'schedule' || steps.restore-avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ env.FDC_ANDROID_EMULATOR_API_LEVEL }} + arch: x86_64 + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: echo "Generated AVD snapshot for caching." + + - name: Save AVD cache + uses: actions/cache/save@v4 + if: github.event_name == 'schedule' + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: ${{ steps.restore-avd-cache.outputs.cache-primary-key }} + + - name: Data Connect Emulator + run: | + set -x + + echo 'emulator.postgresConnectionUrl=postgresql://postgres:password@127.0.0.1:5432?sslmode=disable' > firebase-dataconnect/dataconnect.local.properties + + ./gradlew \ + ${{ (inputs.gradleInfoLog && '--info') || '' }} \ + :firebase-dataconnect:connectors:runDebugDataConnectEmulator \ + >firebase.emulator.dataconnect.log 2>&1 & + + - name: Firebase Auth Emulator + run: | + set -x + cd firebase-dataconnect/emulator + ${{ env.FDC_FIREBASE_COMMAND }} emulators:start --only=auth >firebase.emulator.auth.log 2>&1 & + + - name: Capture Logcat Logs + run: adb logcat >logcat.log & + + - name: Gradle connectedCheck + id: connectedCheck + uses: reactivecircus/android-emulator-runner@v2 + # Allow this GitHub Actions "job" to continue even if the tests fail so that logs from a + # failed test run get uploaded as "artifacts" and are available to investigate failed runs. + # A later step in this "job" will fail the job if this step fails + continue-on-error: true + with: + api-level: ${{ env.FDC_ANDROID_EMULATOR_API_LEVEL }} + arch: x86_64 + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: | + set -eux && ./gradlew ${{ (inputs.gradleInfoLog && '--info') || '' }} :firebase-dataconnect:connectedCheck :firebase-dataconnect:connectors:connectedCheck + + - name: Upload log file artifacts + uses: actions/upload-artifact@v4 + with: + name: integration_test_logs + path: "**/*.log" + if-no-files-found: warn + compression-level: 9 + + - name: Upload Gradle build report artifacts + uses: actions/upload-artifact@v4 + with: + name: integration_test_gradle_build_reports + path: firebase-dataconnect/**/build/reports/ + if-no-files-found: warn + compression-level: 9 + + - name: Check test result + if: steps.connectedCheck.outcome != 'success' + run: | + echo "Failing the job since the connectedCheck step failed" + exit 1 + + # Check this yml file with "actionlint": https://github.com/rhysd/actionlint + # To run actionlint yourself, run `brew install actionlint` followed by + # `actionlint .github/workflows/dataconnect.yml` + actionlint-dataconnect-yml: + continue-on-error: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + show-progress: false + - uses: docker://rhysd/actionlint:1.7.7 + with: + args: -color /github/workspace/.github/workflows/dataconnect.yml diff --git a/.github/workflows/dataconnect_demo_app.yml b/.github/workflows/dataconnect_demo_app.yml index 7ce51814b4d..c401f296b71 100644 --- a/.github/workflows/dataconnect_demo_app.yml +++ b/.github/workflows/dataconnect_demo_app.yml @@ -89,15 +89,20 @@ jobs: - name: tool versions continue-on-error: true run: | - set +e -v - which java - java -version - which javac - javac -version - which node - node --version - ${{ env.FDC_FIREBASE_COMMAND }} --version - ./gradlew --version + function run_cmd { + echo "===============================================================================" + echo "Running Command: $*" + ("$@" 2>&1) || echo "WARNING: command failed with non-zero exit code $?: $*" + } + + run_cmd which java + run_cmd java -version + run_cmd which javac + run_cmd javac -version + run_cmd which node + run_cmd node --version + run_cmd ${{ env.FDC_FIREBASE_COMMAND }} --version + run_cmd ./gradlew --version - name: ./gradlew assemble test run: | diff --git a/.github/workflows/make-bom.yml b/.github/workflows/make-bom.yml index 0ad2ecf4add..0e7d63f5c96 100644 --- a/.github/workflows/make-bom.yml +++ b/.github/workflows/make-bom.yml @@ -11,7 +11,9 @@ jobs: uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: '3.10' + - uses: actions/checkout@v4.1.1 + - name: Set up JDK 17 uses: actions/setup-java@v4.1.0 with: @@ -21,19 +23,25 @@ jobs: - name: Build run: | - ./ci/run.sh \ - --artifact-target-dir=./logs/artifacts \ - --artifact-patterns=bom.zip \ - --artifact-patterns=bomReleaseNotes.md \ - --artifact-patterns=recipeVersionUpdate.txt \ - gradle \ - -- \ - --build-cache \ - buildBomZip - - - name: Upload generated artifacts + ./gradlew buildBomBundleZip + + - name: Upload bom + uses: actions/upload-artifact@v4.3.3 + with: + name: bom + path: build/bom/ + retention-days: 15 + + - name: Upload release notes + uses: actions/upload-artifact@v4.3.3 + with: + name: bom_release_notes + path: build/bomReleaseNotes.md + retention-days: 15 + + - name: Upload recipe version update uses: actions/upload-artifact@v4.3.3 with: - name: artifacts - path: ./logs/artifacts/ - retention-days: 5 + name: recipe_version + path: build/recipeVersionUpdate.txt + retention-days: 15 diff --git a/.github/workflows/build-src-check.yml b/.github/workflows/plugins-check.yml similarity index 75% rename from .github/workflows/build-src-check.yml rename to .github/workflows/plugins-check.yml index 129f89f63d5..fa482c36d35 100644 --- a/.github/workflows/build-src-check.yml +++ b/.github/workflows/plugins-check.yml @@ -1,16 +1,16 @@ -name: Check buildSrc +name: Check plugins on: pull_request: paths: - - 'buildSrc/**' + - 'plugins/**' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - build-src-check: + plugins-check: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4.1.1 @@ -20,13 +20,13 @@ jobs: java-version: 17 distribution: temurin cache: gradle - - name: buildSrc Tests + - name: plugins tests env: FIREBASE_CI: 1 run: | - ./gradlew -b buildSrc/build.gradle.kts -PenablePluginTests=true check + ./gradlew plugins:check - name: Publish Test Results uses: EnricoMi/publish-unit-test-result-action@82082dac68ad6a19d980f8ce817e108b9f496c2a with: files: "**/build/test-results/**/*.xml" - check_name: "buildSrc Test Results" + check_name: "plugins test results" diff --git a/.github/workflows/release-note-changes.yml b/.github/workflows/release-note-changes.yml index a2b5002e985..06d42153ea4 100644 --- a/.github/workflows/release-note-changes.yml +++ b/.github/workflows/release-note-changes.yml @@ -9,59 +9,59 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 - with: - fetch-depth: 0 + - uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 - - name: Create output file - run: touch changelog_comment.md + - name: Create output file + run: touch changelog_comment.md - - name: Get changed changelog files - id: changed-files - uses: tj-actions/changed-files@v41.0.0 - with: - files_ignore: | - buildSrc/** - files: | - **/CHANGELOG.md + - name: Get changed changelog files + id: changed-files + uses: tj-actions/changed-files@v41.0.0 + with: + files_ignore: | + plugins/** + files: | + **/CHANGELOG.md - - name: Set up JDK 17 - uses: actions/setup-java@v4.1.0 - with: - java-version: 17 - distribution: temurin - cache: gradle + - name: Set up JDK 17 + uses: actions/setup-java@v4.1.0 + with: + java-version: 17 + distribution: temurin + cache: gradle - - name: Set up Python 3.10 - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 - if: ${{ steps.changed-files.outputs.any_changed == 'true' }} - with: - python-version: '3.10' + - name: Set up Python 3.10 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + if: ${{ steps.changed-files.outputs.any_changed == 'true' }} + with: + python-version: '3.10' - - name: Set up fireci - id: install-fireci - if: ${{ steps.changed-files.outputs.any_changed == 'true' }} - run: pip3 install -e ci/fireci + - name: Set up fireci + id: install-fireci + if: ${{ steps.changed-files.outputs.any_changed == 'true' }} + run: pip3 install -e ci/fireci - - name: Generate comment - id: generate-comment - if: ${{ steps.install-fireci.outcome == 'success' }} - run: | - fireci changelog_comment -c "${{ steps.changed-files.outputs.all_changed_files }}" -o ./changelog_comment.md + - name: Generate comment + id: generate-comment + if: ${{ steps.install-fireci.outcome == 'success' }} + run: | + fireci changelog_comment -c "${{ steps.changed-files.outputs.all_changed_files }}" -o ./changelog_comment.md - - name: Add PR Comment - uses: mshick/add-pr-comment@v2.8.1 - continue-on-error: true - with: - status: ${{ steps.generate-comment.outcome }} - message-path: ./changelog_comment.md - message-skipped: | - ## Release note changes - No release note changes were detected. If you made changes that should be - present in the next release, ensure you've added an entry in the appropriate - `CHANGELOG.md` file(s). - message-failure: | - ## Release note changes - A `CHANGELOG.md` file seems to not match the expected format. - Please ensure your changelog files are following the format as - defined in [our documentation](#). + - name: Add PR Comment + uses: mshick/add-pr-comment@v2.8.1 + continue-on-error: true + with: + status: ${{ steps.generate-comment.outcome }} + message-path: ./changelog_comment.md + message-skipped: | + ## Release note changes + No release note changes were detected. If you made changes that should be + present in the next release, ensure you've added an entry in the appropriate + `CHANGELOG.md` file(s). + message-failure: | + ## Release note changes + A `CHANGELOG.md` file seems to not match the expected format. + Please ensure your changelog files are following the format as + defined in [our documentation](#). diff --git a/appcheck/firebase-appcheck-debug-testing/api.txt b/appcheck/firebase-appcheck-debug-testing/api.txt index 24535cd8bfb..07a0c2e9a51 100644 --- a/appcheck/firebase-appcheck-debug-testing/api.txt +++ b/appcheck/firebase-appcheck-debug-testing/api.txt @@ -1,10 +1,10 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.appcheck.debug.testing { public final class DebugAppCheckTestHelper { - method @NonNull public static com.google.firebase.appcheck.debug.testing.DebugAppCheckTestHelper fromInstrumentationArgs(); - method public void withDebugProvider(@NonNull com.google.firebase.appcheck.debug.testing.DebugAppCheckTestHelper.MaybeThrowingRunnable) throws E; - method public void withDebugProvider(@NonNull com.google.firebase.FirebaseApp, @NonNull com.google.firebase.appcheck.debug.testing.DebugAppCheckTestHelper.MaybeThrowingRunnable) throws E; + method public static com.google.firebase.appcheck.debug.testing.DebugAppCheckTestHelper fromInstrumentationArgs(); + method public void withDebugProvider(com.google.firebase.appcheck.debug.testing.DebugAppCheckTestHelper.MaybeThrowingRunnable) throws E; + method public void withDebugProvider(com.google.firebase.FirebaseApp, com.google.firebase.appcheck.debug.testing.DebugAppCheckTestHelper.MaybeThrowingRunnable) throws E; } public static interface DebugAppCheckTestHelper.MaybeThrowingRunnable { diff --git a/appcheck/firebase-appcheck-debug/api.txt b/appcheck/firebase-appcheck-debug/api.txt index a98578ea9da..a94d32b7de9 100644 --- a/appcheck/firebase-appcheck-debug/api.txt +++ b/appcheck/firebase-appcheck-debug/api.txt @@ -1,9 +1,9 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.appcheck.debug { public class DebugAppCheckProviderFactory implements com.google.firebase.appcheck.AppCheckProviderFactory { - method @NonNull public com.google.firebase.appcheck.AppCheckProvider create(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public static com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory getInstance(); + method public com.google.firebase.appcheck.AppCheckProvider create(com.google.firebase.FirebaseApp); + method public static com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory getInstance(); } } diff --git a/appcheck/firebase-appcheck-interop/api.txt b/appcheck/firebase-appcheck-interop/api.txt index 4ad12343fcf..4cdf8e9869a 100644 --- a/appcheck/firebase-appcheck-interop/api.txt +++ b/appcheck/firebase-appcheck-interop/api.txt @@ -1,10 +1,10 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.appcheck { public abstract class AppCheckTokenResult { ctor public AppCheckTokenResult(); - method @Nullable public abstract Exception getError(); - method @NonNull public abstract String getToken(); + method public abstract Exception? getError(); + method public abstract String getToken(); } } @@ -12,7 +12,7 @@ package com.google.firebase.appcheck { package com.google.firebase.appcheck.interop { public interface AppCheckTokenListener { - method public void onAppCheckTokenChanged(@NonNull com.google.firebase.appcheck.AppCheckTokenResult); + method public void onAppCheckTokenChanged(com.google.firebase.appcheck.AppCheckTokenResult); } } diff --git a/appcheck/firebase-appcheck-playintegrity/api.txt b/appcheck/firebase-appcheck-playintegrity/api.txt index 41646391f7b..89558ce3209 100644 --- a/appcheck/firebase-appcheck-playintegrity/api.txt +++ b/appcheck/firebase-appcheck-playintegrity/api.txt @@ -1,10 +1,10 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.appcheck.playintegrity { public class PlayIntegrityAppCheckProviderFactory implements com.google.firebase.appcheck.AppCheckProviderFactory { ctor public PlayIntegrityAppCheckProviderFactory(); - method @NonNull public com.google.firebase.appcheck.AppCheckProvider create(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public static com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory getInstance(); + method public com.google.firebase.appcheck.AppCheckProvider create(com.google.firebase.FirebaseApp); + method public static com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory getInstance(); } } diff --git a/appcheck/firebase-appcheck/api.txt b/appcheck/firebase-appcheck/api.txt index e818b1ecd65..fe214cd0b66 100644 --- a/appcheck/firebase-appcheck/api.txt +++ b/appcheck/firebase-appcheck/api.txt @@ -1,42 +1,42 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.appcheck { public interface AppCheckProvider { - method @NonNull public com.google.android.gms.tasks.Task getToken(); + method public com.google.android.gms.tasks.Task getToken(); } public interface AppCheckProviderFactory { - method @NonNull public com.google.firebase.appcheck.AppCheckProvider create(@NonNull com.google.firebase.FirebaseApp); + method public com.google.firebase.appcheck.AppCheckProvider create(com.google.firebase.FirebaseApp); } public abstract class AppCheckToken { ctor public AppCheckToken(); method public abstract long getExpireTimeMillis(); - method @NonNull public abstract String getToken(); + method public abstract String getToken(); } public abstract class FirebaseAppCheck implements com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider { ctor public FirebaseAppCheck(); - method public abstract void addAppCheckListener(@NonNull com.google.firebase.appcheck.FirebaseAppCheck.AppCheckListener); - method @NonNull public abstract com.google.android.gms.tasks.Task getAppCheckToken(boolean); - method @NonNull public static com.google.firebase.appcheck.FirebaseAppCheck getInstance(); - method @NonNull public static com.google.firebase.appcheck.FirebaseAppCheck getInstance(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public abstract com.google.android.gms.tasks.Task getLimitedUseAppCheckToken(); - method public abstract void installAppCheckProviderFactory(@NonNull com.google.firebase.appcheck.AppCheckProviderFactory); - method public abstract void installAppCheckProviderFactory(@NonNull com.google.firebase.appcheck.AppCheckProviderFactory, boolean); - method public abstract void removeAppCheckListener(@NonNull com.google.firebase.appcheck.FirebaseAppCheck.AppCheckListener); + method public abstract void addAppCheckListener(com.google.firebase.appcheck.FirebaseAppCheck.AppCheckListener); + method public abstract com.google.android.gms.tasks.Task getAppCheckToken(boolean); + method public static com.google.firebase.appcheck.FirebaseAppCheck getInstance(); + method public static com.google.firebase.appcheck.FirebaseAppCheck getInstance(com.google.firebase.FirebaseApp); + method public abstract com.google.android.gms.tasks.Task getLimitedUseAppCheckToken(); + method public abstract void installAppCheckProviderFactory(com.google.firebase.appcheck.AppCheckProviderFactory); + method public abstract void installAppCheckProviderFactory(com.google.firebase.appcheck.AppCheckProviderFactory, boolean); + method public abstract void removeAppCheckListener(com.google.firebase.appcheck.FirebaseAppCheck.AppCheckListener); method public abstract void setTokenAutoRefreshEnabled(boolean); } public static interface FirebaseAppCheck.AppCheckListener { - method public void onAppCheckTokenChanged(@NonNull com.google.firebase.appcheck.AppCheckToken); + method public void onAppCheckTokenChanged(com.google.firebase.appcheck.AppCheckToken); } public final class FirebaseAppCheckKt { - method @NonNull public static com.google.firebase.appcheck.FirebaseAppCheck appCheck(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static operator String component1(@NonNull com.google.firebase.appcheck.AppCheckToken); - method public static operator long component2(@NonNull com.google.firebase.appcheck.AppCheckToken); - method @NonNull public static com.google.firebase.appcheck.FirebaseAppCheck getAppCheck(@NonNull com.google.firebase.Firebase); + method public static com.google.firebase.appcheck.FirebaseAppCheck appCheck(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); + method public static operator String component1(com.google.firebase.appcheck.AppCheckToken); + method public static operator long component2(com.google.firebase.appcheck.AppCheckToken); + method public static com.google.firebase.appcheck.FirebaseAppCheck getAppCheck(com.google.firebase.Firebase); } } @@ -44,10 +44,10 @@ package com.google.firebase.appcheck { package com.google.firebase.appcheck.ktx { public final class FirebaseAppCheckKt { - method @Deprecated @NonNull public static com.google.firebase.appcheck.FirebaseAppCheck appCheck(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @Deprecated @NonNull public static operator String component1(@NonNull com.google.firebase.appcheck.AppCheckToken); - method @Deprecated public static operator long component2(@NonNull com.google.firebase.appcheck.AppCheckToken); - method @Deprecated @NonNull public static com.google.firebase.appcheck.FirebaseAppCheck getAppCheck(@NonNull com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.appcheck.FirebaseAppCheck appCheck(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); + method @Deprecated public static operator String component1(com.google.firebase.appcheck.AppCheckToken); + method @Deprecated public static operator long component2(com.google.firebase.appcheck.AppCheckToken); + method @Deprecated public static com.google.firebase.appcheck.FirebaseAppCheck getAppCheck(com.google.firebase.ktx.Firebase); } } diff --git a/appcheck/firebase-appcheck/ktx/api.txt b/appcheck/firebase-appcheck/ktx/api.txt index c951388dd72..da4f6cc18fe 100644 --- a/appcheck/firebase-appcheck/ktx/api.txt +++ b/appcheck/firebase-appcheck/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.appcheck.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 4b79d4d987e..00000000000 --- a/build.gradle +++ /dev/null @@ -1,93 +0,0 @@ -// 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. - -buildscript { - repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } - maven { - url 'https://storage.googleapis.com/android-ci/mvn/' - metadataSources { - artifact() - } - } - maven { - url "https://plugins.gradle.org/m2/" - } - } - - dependencies { - classpath libs.protobuf.gradle.plugin - classpath libs.gradle.errorprone.plugin - classpath libs.google.services - classpath libs.firebase.appdistribution.gradle - classpath libs.firebase.crashlytics.gradle - classpath libs.spotless.plugin.gradle - } -} - -apply from: 'sdkProperties.gradle' -apply from: "gradle/errorProne.gradle" - -apply plugin: com.google.firebase.gradle.plugins.PublishingPlugin - -apply plugin: com.google.firebase.gradle.plugins.ci.ContinuousIntegrationPlugin -apply plugin: com.google.firebase.gradle.plugins.ci.SmokeTestsPlugin - -firebaseContinuousIntegration { - ignorePaths = [ - /.*\.gitignore$/, - /.*\/.*.md$/, - /.*\.github.*/, - ] -} - -configure(subprojects) { - repositories { - google() - mavenLocal() - mavenCentral() - maven { - url 'https://storage.googleapis.com/android-ci/mvn/' - metadataSources { - artifact() - } - } - } - - apply plugin: "com.diffplug.spotless" - - spotless { - java { - target 'src/**/*.java' - targetExclude '**/test/resources/**' - googleJavaFormat('1.22.0').reorderImports(true).skipJavadocFormatting() - } - kotlin { - target 'src/**/*.kt' - ktfmt('0.41').googleStyle() - } - kotlinGradle { - target('*.gradle.kts') // default target for kotlinGradle - ktfmt('0.41').googleStyle() - } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000000..a15ce611215 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,71 @@ +/* + * Copyright 2024 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. + */ + +import com.diffplug.gradle.spotless.SpotlessPlugin +import java.util.regex.Pattern + +plugins { + alias(libs.plugins.spotless) + alias(libs.plugins.protobuf) apply false + alias(libs.plugins.errorprone) + alias(libs.plugins.crashlytics) apply false + id("PublishingPlugin") + id("firebase-ci") + id("smoke-tests") + alias(libs.plugins.google.services) +} + +extra["targetSdkVersion"] = 34 + +extra["compileSdkVersion"] = 34 + +extra["minSdkVersion"] = 21 + +firebaseContinuousIntegration { + ignorePaths = + listOf( + Pattern.compile(".*\\.gitignore$"), + Pattern.compile(".*\\/.*.md$"), + Pattern.compile(".*\\.gitignore$"), + ) +} + +fun Project.applySpotless() { + apply() + spotless { + java { + target("src/**/*.java") + targetExclude("**/test/resources/**") + googleJavaFormat("1.22.0").reorderImports(true).skipJavadocFormatting() + } + kotlin { + target("src/**/*.kt") + ktfmt("0.41").googleStyle() + } + kotlinGradle { + target("*.gradle.kts") // default target for kotlinGradle + ktfmt("0.41").googleStyle() + } + } +} + +applySpotless() + +configure(subprojects) { applySpotless() } + +tasks.named("clean") { delete(rootProject.layout.buildDirectory) } + +apply(from = "gradle/errorProne.gradle") diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 72ee8bf108d..00000000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,111 +0,0 @@ -// 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. - -plugins { - alias(libs.plugins.kotlinx.serialization) - alias(libs.plugins.spotless) - `kotlin-dsl` -} - -repositories { - mavenLocal() - maven(url = "https://maven.google.com/") - mavenCentral() - maven(url = "https://storage.googleapis.com/android-ci/mvn/") - maven(url = "https://plugins.gradle.org/m2/") -} - -spotless { - java { - target("src/**/*.java") - targetExclude("**/test/resources/**") - googleJavaFormat("1.22.0").reorderImports(true).skipJavadocFormatting() - } - kotlin { - target("src/**/*.kt") - ktfmt("0.52").googleStyle() - } -} - -// Refer latest "perf-plugin" released version on https://maven.google.com/web/index.html?q=perf-plugin#com.google.firebase:perf-plugin -// The System property allows us to integrate with an unreleased version from https://bityl.co/3oYt. -// Refer go/fireperf-plugin-test-on-head for more details. -val perfPluginVersion = System.getenv("FIREBASE_PERF_PLUGIN_VERSION") ?: "1.4.1" - -dependencies { - // Firebase performance plugin, it should be added here because of how gradle dependency - // resolution works, otherwise it breaks Fireperf Test Apps. - // See https://github.com/gradle/gradle/issues/12286 - implementation("com.google.firebase:perf-plugin:$perfPluginVersion") - implementation("com.google.auto.value:auto-value-annotations:1.8.1") - annotationProcessor("com.google.auto.value:auto-value:1.6.5") - implementation(kotlin("gradle-plugin", "1.8.22")) - implementation(libs.org.json) - implementation(libs.bundles.maven.resolver) - - implementation("com.google.guava:guava:31.1-jre") - implementation("org.ow2.asm:asm-tree:9.5") - implementation("org.eclipse.jgit:org.eclipse.jgit:6.3.0.202209071007-r") - implementation(libs.kotlinx.serialization.json) - implementation("com.google.code.gson:gson:2.8.9") - implementation(libs.android.gradlePlugin.gradle) - implementation(libs.android.gradlePlugin.builder.test.api) - - testImplementation(libs.bundles.kotest) - testImplementation(libs.junit) - testImplementation(libs.truth) - testImplementation("commons-io:commons-io:2.15.1") -} - -gradlePlugin { - plugins { - register("licensePlugin") { - id = "LicenseResolverPlugin" - implementationClass = "com.google.firebase.gradle.plugins.license.LicenseResolverPlugin" - } - register("multiProjectReleasePlugin") { - id = "MultiProjectReleasePlugin" - implementationClass = "com.google.firebase.gradle.MultiProjectReleasePlugin" - } - register("publishingPlugin") { - id = "PublishingPlugin" - implementationClass = "com.google.firebase.gradle.plugins.PublishingPlugin" - } - register("firebaseLibraryPlugin") { - id = "firebase-library" - implementationClass = "com.google.firebase.gradle.plugins.FirebaseAndroidLibraryPlugin" - } - register("firebaseJavaLibraryPlugin") { - id = "firebase-java-library" - implementationClass = "com.google.firebase.gradle.plugins.FirebaseJavaLibraryPlugin" - } - register("firebaseVendorPlugin") { - id = "firebase-vendor" - implementationClass = "com.google.firebase.gradle.plugins.VendorPlugin" - } - register("copyGoogleServicesPlugin") { - id = "copy-google-services" - implementationClass = "com.google.firebase.gradle.plugins.CopyGoogleServicesPlugin" - } - } -} - -tasks.withType { - testLogging { - // Make sure output from standard out or error is shown in Gradle output. - showStandardStreams = true - } - val enablePluginTests: String? by rootProject - enabled = enablePluginTests == "true" -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/BomGeneratorTask.java b/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/BomGeneratorTask.java deleted file mode 100644 index 433b9132e36..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/BomGeneratorTask.java +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2021 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.bomgenerator; - -import static java.util.stream.Collectors.toList; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Sets; -import com.google.firebase.gradle.bomgenerator.model.Dependency; -import com.google.firebase.gradle.bomgenerator.model.VersionBump; -import com.google.firebase.gradle.bomgenerator.tagging.GitClient; -import com.google.firebase.gradle.bomgenerator.tagging.ShellExecutor; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.eclipse.aether.resolution.VersionRangeResolutionException; -import org.gradle.api.DefaultTask; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.logging.Logger; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.TaskAction; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -public abstract class BomGeneratorTask extends DefaultTask { - private static final List BOM_ARTIFACTS = - List.of( - "com.google.firebase:firebase-analytics", - "com.google.firebase:firebase-analytics-ktx", - "com.google.firebase:firebase-appcheck-debug", - "com.google.firebase:firebase-appcheck-debug-testing", - "com.google.firebase:firebase-appcheck-ktx", - "com.google.firebase:firebase-appcheck-playintegrity", - "com.google.firebase:firebase-appcheck", - "com.google.firebase:firebase-auth", - "com.google.firebase:firebase-auth-ktx", - "com.google.firebase:firebase-common", - "com.google.firebase:firebase-common-ktx", - "com.google.firebase:firebase-config", - "com.google.firebase:firebase-config-ktx", - "com.google.firebase:firebase-crashlytics", - "com.google.firebase:firebase-crashlytics-ktx", - "com.google.firebase:firebase-crashlytics-ndk", - "com.google.firebase:firebase-database", - "com.google.firebase:firebase-database-ktx", - "com.google.firebase:firebase-dynamic-links", - "com.google.firebase:firebase-dynamic-links-ktx", - "com.google.firebase:firebase-encoders", - "com.google.firebase:firebase-firestore", - "com.google.firebase:firebase-firestore-ktx", - "com.google.firebase:firebase-functions", - "com.google.firebase:firebase-functions-ktx", - "com.google.firebase:firebase-inappmessaging", - "com.google.firebase:firebase-inappmessaging-display", - "com.google.firebase:firebase-inappmessaging-display-ktx", - "com.google.firebase:firebase-inappmessaging-ktx", - "com.google.firebase:firebase-installations", - "com.google.firebase:firebase-installations-ktx", - "com.google.firebase:firebase-messaging", - "com.google.firebase:firebase-messaging-directboot", - "com.google.firebase:firebase-messaging-ktx", - "com.google.firebase:firebase-ml-modeldownloader", - "com.google.firebase:firebase-ml-modeldownloader-ktx", - "com.google.firebase:firebase-perf", - "com.google.firebase:firebase-perf-ktx", - "com.google.firebase:firebase-storage", - "com.google.firebase:firebase-storage-ktx", - "com.google.firebase:firebase-vertexai"); - private static final List IGNORED_ARTIFACTS = - List.of( - "crash-plugin", - "firebase-ml-vision", - "crashlytics", - "firebase-ads", - "firebase-ads-lite", - "firebase-abt", - "firebase-analytics-impl", - "firebase-analytics-impl-license", - "firebase-analytics-license", - "firebase-annotations", - "firebase-appcheck-interop", - "firebase-appcheck-safetynet", - "firebase-appdistribution-gradle", - "firebase-appindexing-license", - "firebase-appindexing", - "firebase-iid", - "firebase-core", - "firebase-auth-common", - "firebase-auth-impl", - "firebase-auth-interop", - "firebase-auth-license", - "firebase-encoders-json", - "firebase-encoders-proto", - "firebase-auth-module", - "firebase-bom", - "firebase-common-license", - "firebase-components", - "firebase-config-license", - "firebase-config-interop", - "firebase-crash", - "firebase-crash-license", - "firebase-crashlytics-buildtools", - "firebase-crashlytics-gradle", - "firebase-database-collection", - "firebase-database-connection", - "firebase-database-connection-license", - "firebase-database-license", - "firebase-dataconnect", - "firebase-datatransport", - "firebase-appdistribution-ktx", - "firebase-appdistribution", - "firebase-appdistribution-api", - "firebase-appdistribution-api-ktx", - "firebase-dynamic-module-support", - "firebase-dynamic-links-license", - "firebase-functions-license", - "firebase-iid-interop", - "firebase-iid-license", - "firebase-invites", - "firebase-measurement-connector", - "firebase-measurement-connector-impl", - "firebase-messaging-license", - "firebase-ml-common", - "firebase-ml-vision-internal-vkp", - "firebase-ml-model-interpreter", - "firebase-perf-license", - "firebase-plugins", - "firebase-sessions", - "firebase-storage-common", - "firebase-storage-common-license", - "firebase-storage-license", - "perf-plugin", - "play-services-ads", - "protolite-well-known-types", - "testlab-instr-lib", - "firebase-installations-interop", - "google-services", - "gradle", - "firebase-ml-vision-automl", - "firebase-ml-vision-barcode-model", - "firebase-ml-vision-face-model", - "firebase-ml-vision-image-label-model", - "firebase-ml-vision-object-detection-model", - "firebase-ml-natural-language", - "firebase-ml-natural-language-language-id-model", - "firebase-ml-natural-language-smart-reply", - "firebase-ml-natural-language-smart-reply-model", - "firebase-ml-natural-language-translate", - "firebase-ml-natural-language-translate-model"); - private static final List IMPORTANT_NON_FIREBASE_LIBRARIES = - List.of( - "com.google.android.gms:play-services-ads", - "com.google.gms:google-services", - "com.android.tools.build:gradle", - "com.google.firebase:perf-plugin", - "com.google.firebase:firebase-crashlytics-gradle", - "com.google.firebase:firebase-appdistribution-gradle"); - - private Set ignoredFirebaseArtifacts; - private Set bomArtifacts; - private Set allFirebaseArtifacts; - - public Map versionOverrides = new HashMap<>(); - - @OutputDirectory - public abstract DirectoryProperty getBomDirectory(); - - /** - * This task generates a current Bill of Materials (BoM) based on the latest versions of - * everything in gMaven. This is meant to be a post-release task so that the BoM contains the most - * recent versions of all artifacts. - * - *

This task also tags the release candidate commit with the BoM version, the new version of - * releasing products, and the M version of the current release. - * - *

Version overrides may be given to this task in a map like so: versionOverrides = - * ["com.google.firebase:firebase-firestore": "17.0.1"] - */ - @TaskAction - // TODO(yifany): needs a more accurate name - public void generateBom() throws Exception { - // Repo Access Setup - RepositoryClient depPopulator = new RepositoryClient(); - - // Prepare script by pulling the state of the world (checking configuration files and gMaven - // artifacts) - bomArtifacts = new HashSet(BOM_ARTIFACTS); - ignoredFirebaseArtifacts = new HashSet(IGNORED_ARTIFACTS); - allFirebaseArtifacts = depPopulator.getAllFirebaseArtifacts(); - allFirebaseArtifacts.addAll(IMPORTANT_NON_FIREBASE_LIBRARIES); - - // Find version for BoM artifact. First version released should be 15.0.0 - String currentVersion = - depPopulator - .getLastPublishedVersion(Dependency.create("com.google.firebase", "firebase-bom")) - .orElse("15.0.0"); - - // We need to get the content of the current BoM to compute version bumps. - Map previousBomVersions = getBomMap(currentVersion); - - // Generate list of firebase libraries, ping gmaven for current versions, and override as needed - // from local settings - List allFirebaseDependencies = - buildVersionedDependencyList(depPopulator, previousBomVersions); - - List bomDependencies = - allFirebaseDependencies.stream() - .filter(dep -> bomArtifacts.contains(dep.fullArtifactId())) - .collect(toList()); - - // Sanity check that there are no unaccounted for artifacts that we might want in the BoM - Set bomArtifactIds = - bomArtifacts.stream().map(x -> x.split(":")[1]).collect(Collectors.toSet()); - Set allFirebaseArtifactIds = - allFirebaseArtifacts.stream().map(x -> x.split(":")[1]).collect(Collectors.toSet()); - Set invalidArtifacts = - Sets.difference( - Sets.difference(allFirebaseArtifactIds, bomArtifactIds), ignoredFirebaseArtifacts); - - if (!invalidArtifacts.isEmpty()) { - throw new RuntimeException( - "Some dependencies are unaccounted for, add to BomGeneratorTask#IGNORED_ARTIFACTS or " - + "BomGeneratorTask#BOM_ARTIFACTS. Unaccounted for dependencies: " - + invalidArtifacts.toString()); - } - String version = findArtifactVersion(bomDependencies, currentVersion, previousBomVersions); - - // Surface generated pom for sanity checking and testing, and then write it. - Path bomDir = getBomDirectory().getAsFile().get().toPath(); - PomXmlWriter xmlWriter = new PomXmlWriter(bomDependencies, version, bomDir); - MarkdownDocumentationWriter documentationWriter = - new MarkdownDocumentationWriter( - bomDependencies, version, previousBomVersions, currentVersion); - RecipeVersionWriter recipeWriter = new RecipeVersionWriter(allFirebaseDependencies); - Document outputXmlDoc = xmlWriter.generatePomXml(); - String outputDocumentation = documentationWriter.generateDocumentation(); - String outputRecipe = recipeWriter.generateVersionUpdate(); - xmlWriter.writeXmlDocument(outputXmlDoc); - documentationWriter.writeDocumentation(outputDocumentation); - recipeWriter.writeVersionUpdate(outputRecipe); - - tagVersions(version, bomDependencies); - } - - // Finds the version for the BoM artifact. - private String findArtifactVersion( - List firebaseDependencies, - String currentVersion, - Map previousBomVersions) - throws VersionRangeResolutionException { - Optional bump = - firebaseDependencies.stream().map(Dependency::versionBump).distinct().sorted().findFirst(); - - if (firebaseDependencies.size() < previousBomVersions.size()) { - bump = Optional.of(VersionBump.MAJOR); - } - - return bump.map(x -> VersionBump.bumpVersionBy(currentVersion, x)) - .orElseThrow(() -> new RuntimeException("Could not figure out how to bump version")); - } - - private Dependency overrideVersion(Dependency dep) { - if (versionOverrides.containsKey(dep.fullArtifactId())) { - return Dependency.create( - dep.groupId(), - dep.artifactId(), - versionOverrides.get(dep.fullArtifactId()), - VersionBump.PATCH); - } else { - return dep; - } - } - - private List buildVersionedDependencyList( - RepositoryClient depPopulator, Map previousBomVersions) { - return allFirebaseArtifacts.stream() - .map( - dep -> { - String[] splitDep = dep.split(":"); - return Dependency.create(splitDep[0], splitDep[1]); - }) - .map(dep -> depPopulator.populateDependencyVersion(dep, previousBomVersions)) - .map(this::overrideVersion) - .collect(toList()); - } - - private Map getBomMap(String bomVersion) { - String bomUrl = - "https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-bom/" - + bomVersion - + "/firebase-bom-" - + bomVersion - + ".pom"; - try (InputStream index = new URL(bomUrl).openStream()) { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setValidating(true); - factory.setIgnoringElementContentWhitespace(true); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document doc = builder.parse(index); - NodeList dependencyList = doc.getElementsByTagName("dependency"); - ImmutableMap.Builder outputBuilder = ImmutableMap.builder(); - for (int i = 0; i < dependencyList.getLength(); i++) { - Element artifact = (Element) dependencyList.item(i); - String groupId = artifact.getElementsByTagName("groupId").item(0).getTextContent(); - String artifactId = artifact.getElementsByTagName("artifactId").item(0).getTextContent(); - String version = artifact.getElementsByTagName("version").item(0).getTextContent(); - outputBuilder.put(groupId + ":" + artifactId, version); - } - return outputBuilder.build(); - } catch (SAXException | IOException | ParserConfigurationException e) { - throw new RuntimeException("Failed to get contents of BoM version " + bomVersion, e); - } - } - - private void tagVersions(String bomVersion, List firebaseDependencies) { - Logger logger = this.getProject().getLogger(); - if (!System.getenv().containsKey("FIREBASE_CI")) { - logger.warn("Tagging versions is skipped for non-CI environments."); - return; - } - - String mRelease = System.getenv("PULL_BASE_REF"); - String rcCommit = System.getenv("PULL_BASE_SHA"); - ShellExecutor executor = new ShellExecutor(Paths.get(".").toFile(), logger::lifecycle); - GitClient git = new GitClient(mRelease, rcCommit, executor, logger::lifecycle); - git.tagReleaseVersion(); - git.tagBomVersion(bomVersion); - firebaseDependencies.stream() - .filter(d -> d.versionBump() != VersionBump.NONE) - .forEach(d -> git.tagProductVersion(d.artifactId(), d.version())); - git.pushCreatedTags(); - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/MarkdownDocumentationWriter.java b/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/MarkdownDocumentationWriter.java deleted file mode 100644 index 530ecf9c977..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/MarkdownDocumentationWriter.java +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2021 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.bomgenerator; - -import com.google.firebase.gradle.bomgenerator.model.Dependency; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - -public class MarkdownDocumentationWriter { - private final List firebaseDependencies; - private final Map previousBomVersions; - private final String version; - private final String previousVersion; - - public MarkdownDocumentationWriter( - List firebaseDependencies, - String version, - Map previousBomVersions, - String previousVersion) { - this.firebaseDependencies = firebaseDependencies; - this.previousBomVersions = previousBomVersions; - this.version = version; - this.previousVersion = previousVersion; - } - - public String generateDocumentation() { - StringBuilder docBuilder = new StringBuilder(); - docBuilder.append(generateHeader(version)); - firebaseDependencies.stream() - .sorted(Comparator.comparing(Dependency::toGradleString)) - .map(this::generateListEntry) - .forEach(docBuilder::append); - docBuilder.append(generateFooter()); - return docBuilder.toString(); - } - - public void writeDocumentation(String document) throws IOException { - Files.write( - new File("bomReleaseNotes.md").toPath(), - Collections.singleton(document), - StandardCharsets.UTF_8); - } - - public String getVersion() { - return version; - } - - private String generateHeader(String version) { - return "### {{firebase_bom_long}} ({{bill_of_materials}}) version " - + version - + " " - + headingId() - + "\n" - + "{% comment %}\n" - + "These library versions must be flat-typed, do not use variables.\n" - + "The release note for this BoM version is a library-version snapshot.\n" - + "{% endcomment %}\n" - + "\n" - + "

\n" - + "

\n" - + " Firebase Android SDKs mapped to this {{bom}} version

\n" - + "

Libraries that were versioned with this release are in highlighted rows.\n" - + "
Refer to a library's release notes (on this page) for details about its\n" - + " changes.\n" - + "

\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n"; - } - - private String generateListEntry(Dependency dep) { - String previousDepVersion = - previousBomVersions.containsKey(dep.fullArtifactId()) - ? previousBomVersions.get(dep.fullArtifactId()) - : "N/A"; - boolean depChanged = !dep.version().equals(previousDepVersion); - String boldOpenTag = depChanged ? "" : ""; - String boldClosedTag = depChanged ? "" : ""; - String tableStyle = depChanged ? " class=\"alt\"" : ""; - return " \n" - + " \n" - + " \n" - + " \n" - + " \n"; - } - - private String generateFooter() { - return " \n
Artifact nameVersion mapped
to previous {{bom}} v" - + previousVersion - + "
Version mapped
to this {{bom}} v" - + version - + "
" - + boldOpenTag - + dep.fullArtifactId() - + boldClosedTag - + "" - + previousDepVersion - + "" - + boldOpenTag - + dep.version() - + boldClosedTag - + "
\n
\n"; - } - - private String headingId() { - return "{: #bom_v" + version.replaceAll("\\.", "-") + "}"; - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/PomXmlWriter.java b/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/PomXmlWriter.java deleted file mode 100644 index bac4b8d9c85..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/PomXmlWriter.java +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2021 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.bomgenerator; - -import com.google.firebase.gradle.bomgenerator.model.Dependency; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import org.eclipse.aether.resolution.VersionRangeResolutionException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -public class PomXmlWriter { - private static final String ARTIFACT_GROUP_ID = "com.google.firebase"; - private static final String ARTIFACT_ARTIFACT_ID = "firebase-bom"; - private final List firebaseDependencies; - private final String version; - private final Path rootDir; - - public PomXmlWriter(List firebaseDependencies, String version, Path rootDir) { - this.firebaseDependencies = firebaseDependencies; - this.version = version; - this.rootDir = rootDir; - } - - public Document generatePomXml() - throws ParserConfigurationException, VersionRangeResolutionException { - Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - - Element project = doc.createElement("project"); - project.setAttribute("xmlns", "http://maven.apache.org/POM/4.0.0"); - project.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); - project.setAttribute( - "xsi:schemaLocation", - "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"); - doc.appendChild(project); - - createAndAppendSimpleElement("modelVersion", "4.0.0", project, doc); - createAndAppendSimpleElement("groupId", ARTIFACT_GROUP_ID, project, doc); - createAndAppendSimpleElement("artifactId", ARTIFACT_ARTIFACT_ID, project, doc); - createAndAppendSimpleElement("version", getVersion(), project, doc); - createAndAppendSimpleElement("packaging", "pom", project, doc); - - Element licenses = createLicense(doc); - project.appendChild(licenses); - - Element dependencyManagement = doc.createElement("dependencyManagement"); - project.appendChild(dependencyManagement); - - Element dependencies = doc.createElement("dependencies"); - dependencyManagement.appendChild(dependencies); - - for (Dependency dep : firebaseDependencies) { - Element depXml = dependencyToMavenXmlElement(dep, doc); - dependencies.appendChild(depXml); - } - - return doc; - } - - public void writeXmlDocument(Document document) throws IOException, TransformerException { - - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - - DOMSource source = new DOMSource(document); - Path outputDir = rootDir.resolve("com/google/firebase/firebase-bom/" + version + "/"); - Files.createDirectories(outputDir); - Path pom = outputDir.resolve("firebase-bom-" + version + ".pom"); - StreamResult file = new StreamResult(pom.toFile()); - transformer.transform(source, file); - } - - public String getVersion() { - return version; - } - - private static void createAndAppendSimpleElement( - String key, String value, Element toAppendTo, Document doc) { - Element element = doc.createElement(key); - element.appendChild(doc.createTextNode(value)); - toAppendTo.appendChild(element); - } - - private static Element createLicense(Document doc) { - Element licenses = doc.createElement("licenses"); - Element license = doc.createElement("license"); - createAndAppendSimpleElement("name", "The Apache Software License, Version 2.0", license, doc); - createAndAppendSimpleElement( - "url", "http://www.apache.org/licenses/LICENSE-2.0.txt", license, doc); - createAndAppendSimpleElement("distribution", "repo", license, doc); - licenses.appendChild(license); - return licenses; - } - - public Element dependencyToMavenXmlElement(Dependency dep, Document doc) { - Element dependencyElement = doc.createElement("dependency"); - - Element groupId = doc.createElement("groupId"); - groupId.appendChild(doc.createTextNode(dep.groupId())); - - Element artifactId = doc.createElement("artifactId"); - artifactId.appendChild(doc.createTextNode(dep.artifactId())); - - Element version = doc.createElement("version"); - version.appendChild(doc.createTextNode(dep.version())); - - dependencyElement.appendChild(groupId); - dependencyElement.appendChild(artifactId); - dependencyElement.appendChild(version); - - return dependencyElement; - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/RecipeVersionWriter.java b/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/RecipeVersionWriter.java deleted file mode 100644 index 5ef8c9207fe..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/RecipeVersionWriter.java +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2021 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.bomgenerator; - -import com.google.firebase.gradle.bomgenerator.model.Dependency; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class RecipeVersionWriter { - private final List firebaseDependencies; - - public RecipeVersionWriter(List firebaseDependencies) { - this.firebaseDependencies = firebaseDependencies; - } - - public String generateVersionUpdate() { - Map depsByArtifactId = - firebaseDependencies.stream().collect(Collectors.toMap(Dependency::fullArtifactId, x -> x)); - StringBuilder outputBuilder = new StringBuilder(); - outputBuilder.append("\n"); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Google Services Plugin", - "google-services-plugin-class", - "com.google.gms:google-services")); - outputBuilder.append( - " \n" - + " \n"); - outputBuilder.append("\n"); - outputBuilder.append(" \n"); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Analytics", - "analytics-dependency", - "com.google.firebase:firebase-analytics")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Crashlytics", - "crashlytics-dependency", - "com.google.firebase:firebase-crashlytics")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Performance Monitoring", - "perf-dependency", - "com.google.firebase:firebase-perf")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Vertex AI in Firebase", - "vertex-dependency", - "com.google.firebase:firebase-vertexai")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Cloud Messaging", - "messaging-dependency", - "com.google.firebase:firebase-messaging")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Authentication", - "auth-dependency", - "com.google.firebase:firebase-auth")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Realtime Database", - "database-dependency", - "com.google.firebase:firebase-database")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Cloud Storage", - "storage-dependency", - "com.google.firebase:firebase-storage")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Remote Config", - "remote-config-dependency", - "com.google.firebase:firebase-config")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Admob", - "ads-dependency", - "com.google.android.gms:play-services-ads")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Cloud Firestore", - "firestore-dependency", - "com.google.firebase:firebase-firestore")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Firebase Functions", - "functions-dependency", - "com.google.firebase:firebase-functions")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "FIAM Display", - "fiamd-dependency", - "com.google.firebase:firebase-inappmessaging-display")); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Firebase MLKit Vision", - "ml-vision-dependency", - "com.google.firebase:firebase-ml-vision")); - outputBuilder.append("\n"); - outputBuilder.append(" \n"); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "App Distribution", - "appdistribution-plugin-class", - "com.google.firebase:firebase-appdistribution-gradle")); - outputBuilder.append( - " \n\n"); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Crashlytics", - "crashlytics-plugin-class", - "com.google.firebase:firebase-crashlytics-gradle")); - outputBuilder.append(" \n\n"); - outputBuilder.append(" \n"); - outputBuilder.append( - generateVersionVariable( - depsByArtifactId, - "Perf Plugin", - "perf-plugin-class", - "com.google.firebase:perf-plugin")); - outputBuilder.append(" \n"); - outputBuilder.append("]>\n"); - return outputBuilder.toString(); - } - - private static String generateVersionVariable( - Map depsByArtifactId, String comment, String alias, String artifactId) { - if (!depsByArtifactId.containsKey(artifactId)) { - throw new RuntimeException("Error fetching newest version for " + artifactId); - } - return " \n" - + " \n"; - } - - public void writeVersionUpdate(String document) throws IOException { - Files.write( - new File("recipeVersionUpdate.txt").toPath(), - Collections.singleton(document), - StandardCharsets.UTF_8); - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/RepositoryClient.java b/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/RepositoryClient.java deleted file mode 100644 index 5fbfd83ec3f..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/RepositoryClient.java +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2021 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.bomgenerator; - -import com.google.firebase.gradle.bomgenerator.model.Dependency; -import com.google.firebase.gradle.bomgenerator.model.VersionBump; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.apache.maven.repository.internal.MavenRepositorySystemUtils; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositorySystem; -import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; -import org.eclipse.aether.impl.DefaultServiceLocator; -import org.eclipse.aether.repository.LocalRepository; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.resolution.VersionRangeRequest; -import org.eclipse.aether.resolution.VersionRangeResolutionException; -import org.eclipse.aether.resolution.VersionRangeResult; -import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; -import org.eclipse.aether.spi.connector.transport.TransporterFactory; -import org.eclipse.aether.transport.file.FileTransporterFactory; -import org.eclipse.aether.transport.http.HttpTransporterFactory; -import org.eclipse.aether.version.Version; -import org.gradle.api.GradleException; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -public class RepositoryClient { - private static final RemoteRepository GMAVEN = - new RemoteRepository.Builder("central", "default", "https://maven.google.com").build(); - - private final RepositorySystem system; - private final RepositorySystemSession session; - - public RepositoryClient() { - system = newRepositorySystem(); - session = newRepositorySystemSession(system); - } - - public Dependency populateDependencyVersion( - Dependency firebaseDep, Map versionsFromPreviousBomByArtifact) { - try { - List rangeResult = getVersionsForDependency(firebaseDep).getVersions(); - String version = rangeResult.get(rangeResult.size() - 1).toString(); - String versionFromPreviousBom = - versionsFromPreviousBomByArtifact.get(firebaseDep.fullArtifactId()); - - VersionBump versionBump = - versionFromPreviousBom == null - ? VersionBump.MINOR - : VersionBump.getBumpBetweenVersion(version, versionFromPreviousBom); - return Dependency.create( - firebaseDep.groupId(), firebaseDep.artifactId(), version, versionBump); - } catch (VersionRangeResolutionException e) { - throw new GradleException("Failed to resolve dependency: " + firebaseDep.toGradleString(), e); - } - } - - public Optional getLastPublishedVersion(Dependency dependency) - throws VersionRangeResolutionException { - Version version = getVersionsForDependency(dependency).getHighestVersion(); - return Optional.ofNullable(version).map(Version::toString); - } - - public Set getAllFirebaseArtifacts() { - try (InputStream index = - new URL("https://dl.google.com/dl/android/maven2/com/google/firebase/group-index.xml") - .openStream()) { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setValidating(true); - factory.setIgnoringElementContentWhitespace(true); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document doc = builder.parse(index); - NodeList artifactList = doc.getFirstChild().getChildNodes(); - Set outputArtifactIds = new HashSet<>(); - for (int i = 0; i < artifactList.getLength(); i++) { - Node artifact = artifactList.item(i); - if (artifact.getNodeName().contains("#")) { - continue; - } - outputArtifactIds.add("com.google.firebase:" + artifact.getNodeName()); - } - return outputArtifactIds; - } catch (SAXException | IOException | ParserConfigurationException e) { - throw new RuntimeException("Failed to get Firebase Artifact Ids", e); - } - } - - // Dependency string must be in the format : - // for example: "com.google.firebase:firebase-bom" - private VersionRangeResult getVersionsForDependency(Dependency dep) - throws VersionRangeResolutionException { - Artifact requestArtifact = new DefaultArtifact(dep.fullArtifactId() + ":[0,)"); - - VersionRangeRequest rangeRequest = new VersionRangeRequest(); - rangeRequest.setArtifact(requestArtifact); - rangeRequest.setRepositories(Arrays.asList(GMAVEN)); - - return system.resolveVersionRange(session, rangeRequest); - } - - private static RepositorySystem newRepositorySystem() { - /* - * Aether's components implement org.eclipse.aether.spi.locator.Service to ease - * manual wiring and using the prepopulated DefaultServiceLocator, we only need - * to register the repository connector and transporter factories. - */ - DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); - locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); - locator.addService(TransporterFactory.class, FileTransporterFactory.class); - locator.addService(TransporterFactory.class, HttpTransporterFactory.class); - - locator.setErrorHandler( - new DefaultServiceLocator.ErrorHandler() { - @Override - public void serviceCreationFailed(Class type, Class impl, Throwable exception) { - exception.printStackTrace(); - } - }); - - return locator.getService(RepositorySystem.class); - } - - private static DefaultRepositorySystemSession newRepositorySystemSession( - RepositorySystem system) { - DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); - - LocalRepository localRepo = new LocalRepository("target/local-repo"); - session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo)); - - return session; - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/model/Dependency.java b/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/model/Dependency.java deleted file mode 100644 index 3dd36e95158..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/model/Dependency.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2021 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.bomgenerator.model; - -import com.google.auto.value.AutoValue; - -@AutoValue -public abstract class Dependency { - - public abstract String groupId(); - - public abstract String artifactId(); - - public abstract String version(); - - public abstract VersionBump versionBump(); - - public static Dependency create( - String groupId, String artifactId, String version, VersionBump versionBump) { - return new AutoValue_Dependency(groupId, artifactId, version, versionBump); - } - - // Null safe default constructor. Represents dependencies that have not yet been looked up in - // repos. - public static Dependency create(String groupId, String artifactId) { - return new AutoValue_Dependency(groupId, artifactId, "0.0.0", VersionBump.NONE); - } - - public String fullArtifactId() { - return groupId() + ":" + artifactId(); - } - - public String toGradleString() { - return groupId() + ":" + artifactId() + (version() == null ? "" : (":" + version())); - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/model/VersionBump.java b/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/model/VersionBump.java deleted file mode 100644 index e8345fae2bb..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/model/VersionBump.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2021 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.bomgenerator.model; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public enum VersionBump { - MAJOR, - MINOR, - PATCH, - NONE; - - private static final Pattern SEMVER_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+).*"); - - // Assumes list of versions passed in is sorted, newest versions last. - public static VersionBump getBumpBetweenVersion( - String newestVersion, String secondNewestVersion) { - Matcher newestVersionMatcher = SEMVER_PATTERN.matcher(newestVersion); - Matcher secondNewestVersionMatcher = SEMVER_PATTERN.matcher(secondNewestVersion); - if (!(newestVersionMatcher.matches() && secondNewestVersionMatcher.matches())) { - throw new RuntimeException( - "Could not figure out version bump between " - + secondNewestVersion - + " and " - + newestVersion - + "."); - } - if (Integer.parseInt(newestVersionMatcher.group(1)) - > Integer.parseInt(secondNewestVersionMatcher.group(1))) { - return MAJOR; - } - if (Integer.parseInt(newestVersionMatcher.group(2)) - > Integer.parseInt(secondNewestVersionMatcher.group(2))) { - return MINOR; - } - if (Integer.parseInt(newestVersionMatcher.group(3)) - > Integer.parseInt(secondNewestVersionMatcher.group(3))) { - return PATCH; - } - return NONE; - } - - public static String bumpVersionBy(String version, VersionBump bump) { - Matcher versionMatcher = SEMVER_PATTERN.matcher(version); - if (!versionMatcher.matches()) { - throw new RuntimeException("Could not bump " + version + " as it is not a valid version."); - } - switch (bump) { - case NONE: - return version; - case MAJOR: - return Integer.toString(Integer.parseInt(versionMatcher.group(1)) + 1).toString() + ".0.0"; - case MINOR: - return versionMatcher.group(1) - + "." - + Integer.toString(Integer.parseInt(versionMatcher.group(2)) + 1) - + ".0"; - case PATCH: - return versionMatcher.group(1) - + "." - + versionMatcher.group(2) - + "." - + Integer.toString(Integer.parseInt(versionMatcher.group(3)) + 1); - default: - throw new RuntimeException("Should be impossible"); - } - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ClosureUtil.java b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ClosureUtil.java deleted file mode 100644 index 5a22a81520d..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ClosureUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 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; - -import groovy.lang.Closure; -import java.util.function.Consumer; - -public final class ClosureUtil { - - private static final Object FAKE_THIS = new Object(); - - private ClosureUtil() {} - - /** Create a groovy closure backed by a lambda. */ - public static Closure closureOf(Consumer action) { - return new Closure(FAKE_THIS) { - void doCall(T t) { - action.accept(t); - } - }; - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt deleted file mode 100644 index 11552396078..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright 2020 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 - -import com.android.build.api.artifact.ScopedArtifact -import com.android.build.api.variant.LibraryAndroidComponentsExtension -import com.android.build.api.variant.ScopedArtifacts -import com.android.build.gradle.LibraryPlugin -import java.io.BufferedInputStream -import java.io.BufferedOutputStream -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream -import javax.inject.Inject -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.file.Directory -import org.gradle.api.file.RegularFile -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Classpath -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.getByType -import org.gradle.process.ExecOperations - -class VendorPlugin : Plugin { - override fun apply(project: Project) { - project.plugins.all { - when (this) { - is LibraryPlugin -> configureAndroid(project) - } - } - } - - fun configureAndroid(project: Project) { - project.apply(plugin = "LicenseResolverPlugin") - - val vendor = project.configurations.create("vendor") - project.configurations.all { - when (name) { - "releaseCompileOnly", - "debugImplementation", - "testImplementation", - "androidTestImplementation" -> extendsFrom(vendor) - } - } - - val jarJar = project.configurations.create("firebaseJarJarArtifact") - project.dependencies.add("firebaseJarJarArtifact", "org.pantsbuild:jarjar:1.7.2") - - val androidComponents = project.extensions.getByType() - - androidComponents.onVariants(androidComponents.selector().withBuildType("release")) { variant -> - val vendorTask = - project.tasks.register("${variant.name}VendorTransform", VendorTask::class.java) { - vendorDependencies.set(vendor) - packageName.set(variant.namespace) - this.jarJar.set(jarJar) - } - variant.artifacts - .forScope(ScopedArtifacts.Scope.PROJECT) - .use(vendorTask) - .toTransform( - ScopedArtifact.CLASSES, - VendorTask::inputJars, - VendorTask::inputDirs, - VendorTask::outputJar, - ) - } - } -} - -abstract class VendorTask @Inject constructor(private val execOperations: ExecOperations) : - DefaultTask() { - @get:[InputFiles Classpath] - abstract val vendorDependencies: Property - - @get:[InputFiles Classpath] - abstract val jarJar: Property - - @get:Input abstract val packageName: Property - - @get:InputFiles abstract val inputJars: ListProperty - - @get:InputFiles abstract val inputDirs: ListProperty - - @get:OutputFile abstract val outputJar: RegularFileProperty - - @TaskAction - fun taskAction() { - val workDir = File.createTempFile("vendorTmp", null) - workDir.mkdirs() - workDir.deleteRecursively() - - val unzippedDir = File(workDir, "unzipped") - val externalCodeDir = unzippedDir - - for (directory in inputDirs.get()) { - directory.asFile.copyRecursively(unzippedDir) - } - for (jar in inputJars.get()) { - unzipJar(jar.asFile, unzippedDir) - } - - val ownPackageNames = inferPackages(unzippedDir) - - for (jar in vendorDependencies.get()) { - unzipJar(jar, externalCodeDir) - } - val externalPackageNames = inferPackages(externalCodeDir) subtract ownPackageNames - val java = File(externalCodeDir, "java") - val javax = File(externalCodeDir, "javax") - if (java.exists() || javax.exists()) { - // JarJar unconditionally skips any classes whose package name starts with "java" or "javax". - throw GradleException( - "Vendoring java or javax packages is not supported. " + - "Please exclude one of the direct or transitive dependencies: \n" + - vendorDependencies - .get() - .resolvedConfiguration - .resolvedArtifacts - .joinToString(separator = "\n") - ) - } - - val jar = File(workDir, "intermediate.jar") - zipAll(unzippedDir, jar) - transform(jar, ownPackageNames, externalPackageNames) - } - - fun transform(inputJar: File, ownPackages: Set, packagesToVendor: Set) { - val parentPackage = packageName.get() - val rulesFile = File.createTempFile(parentPackage, ".jarjar") - rulesFile.printWriter().use { - for (packageName in ownPackages) { - it.println("keep $packageName.**") - } - for (externalPackageName in packagesToVendor) { - it.println("rule $externalPackageName.** $parentPackage.@0") - } - } - logger.info("The following JarJar configuration will be used:\n ${rulesFile.readText()}") - - execOperations - .javaexec { - mainClass.set("org.pantsbuild.jarjar.Main") - classpath = project.files(jarJar.get()) - args = - listOf( - "process", - rulesFile.absolutePath, - inputJar.absolutePath, - outputJar.asFile.get().absolutePath, - ) - systemProperties = mapOf("verbose" to "true", "misplacedClassStrategy" to "FATAL") - } - .assertNormalExitValue() - } -} - -fun inferPackages(dir: File): Set { - return dir - .walk() - .filter { it.name.endsWith(".class") } - .map { it.parentFile.toRelativeString(dir).replace('/', '.') } - .toSet() -} - -fun unzipJar(jar: File, directory: File) { - ZipFile(jar).use { zip -> - zip - .entries() - .asSequence() - .filter { !it.isDirectory && !it.name.startsWith("META-INF") } - .forEach { entry -> - zip.getInputStream(entry).use { input -> - val entryFile = File(directory, entry.name) - entryFile.parentFile.mkdirs() - entryFile.outputStream().use { output -> input.copyTo(output) } - } - } - } -} - -fun zipAll(directory: File, zipFile: File) { - - ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use { - zipFiles(it, directory, "") - } -} - -private fun zipFiles(zipOut: ZipOutputStream, sourceFile: File, parentDirPath: String) { - val data = ByteArray(2048) - sourceFile.listFiles()?.forEach { f -> - if (f.isDirectory) { - val path = - if (parentDirPath == "") { - f.name - } else { - parentDirPath + File.separator + f.name - } - // Call recursively to add files within this directory - zipFiles(zipOut, f, path) - } else { - FileInputStream(f).use { fi -> - BufferedInputStream(fi).use { origin -> - val path = parentDirPath + File.separator + f.name - val entry = ZipEntry(path) - entry.time = f.lastModified() - entry.isDirectory - entry.size = f.length() - zipOut.putNextEntry(entry) - while (true) { - val readBytes = origin.read(data) - if (readBytes == -1) { - break - } - zipOut.write(data, 0, readBytes) - } - } - } - } - } -} diff --git a/ci/danger/Dangerfile b/ci/danger/Dangerfile index 0efe1369e0b..9d1b9ef9b29 100644 --- a/ci/danger/Dangerfile +++ b/ci/danger/Dangerfile @@ -45,7 +45,7 @@ has_changelog_changes = hasChangesIn(["CHANGELOG"]) # Ignore changes in these directories $exclude_directories = [ '.github/', - 'buildSrc/', + 'plugins/', 'ci/', 'encoders/', 'firebase-annotations/', diff --git a/contributor-docs/onboarding/new_sdk.md b/contributor-docs/onboarding/new_sdk.md index 52ba25eeaff..2d39b001d62 100644 --- a/contributor-docs/onboarding/new_sdk.md +++ b/contributor-docs/onboarding/new_sdk.md @@ -23,7 +23,7 @@ subdirectory with its respective build file(s). ```bash firebase-android-sdk -├── buildSrc +├── plugins ├── appcheck │ └── firebase-appcheck │ └── firebase-appcheck-playintegrity @@ -45,7 +45,7 @@ Note that the build file name for any given SDK is not `build.gradle` or `build. but rather mirrors the name of the sdk, e.g. `firebase-common/firebase-common.gradle` or `firebase-common/firebase-common.gradle.kts`. -All of the core Gradle build logic lives in `buildSrc` and is used by all +All of the core Gradle build logic lives in `plugins` and is used by all SDKs. SDKs can be grouped together for convenience by placing them in a directory of diff --git a/encoders/firebase-decoders-json/api.txt b/encoders/firebase-decoders-json/api.txt index bc7de499eb3..701151202ed 100644 --- a/encoders/firebase-decoders-json/api.txt +++ b/encoders/firebase-decoders-json/api.txt @@ -1,4 +1,4 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.decoders { public abstract class Safe { @@ -6,23 +6,23 @@ package com.google.firebase.decoders { } public abstract class TypeToken { - method @NonNull public static com.google.firebase.decoders.TypeToken of(@NonNull com.google.firebase.decoders.Safe); - method @NonNull public static com.google.firebase.decoders.TypeToken of(@NonNull Class); + method public static com.google.firebase.decoders.TypeToken of(com.google.firebase.decoders.Safe); + method public static com.google.firebase.decoders.TypeToken of(Class); } - public static class TypeToken.ArrayToken extends com.google.firebase.decoders.TypeToken { - method @NonNull public com.google.firebase.decoders.TypeToken getComponentType(); + public static class TypeToken.ArrayToken extends com.google.firebase.decoders.TypeToken { + method public com.google.firebase.decoders.TypeToken getComponentType(); } - public static class TypeToken.ClassToken extends com.google.firebase.decoders.TypeToken { - method @NonNull public Class getRawType(); - method @NonNull public com.google.firebase.decoders.TypeTokenContainer getTypeArguments(); + public static class TypeToken.ClassToken extends com.google.firebase.decoders.TypeToken { + method public Class getRawType(); + method public com.google.firebase.decoders.TypeTokenContainer getTypeArguments(); } public final class TypeTokenContainer { - ctor public TypeTokenContainer(@NonNull com.google.firebase.decoders.TypeToken[]); - method @NonNull public com.google.firebase.decoders.TypeToken at(int); - field @NonNull public static final com.google.firebase.decoders.TypeTokenContainer EMPTY; + ctor public TypeTokenContainer(com.google.firebase.decoders.TypeToken![]); + method public com.google.firebase.decoders.TypeToken at(int); + field public static final com.google.firebase.decoders.TypeTokenContainer EMPTY; } } diff --git a/encoders/firebase-encoders-json/api.txt b/encoders/firebase-encoders-json/api.txt index 06c4d48139c..03ebcdb5d3c 100644 --- a/encoders/firebase-encoders-json/api.txt +++ b/encoders/firebase-encoders-json/api.txt @@ -1,14 +1,19 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.encoders.json { - public final class JsonDataEncoderBuilder implements com.google.firebase.encoders.config.EncoderConfig { + public final class JsonDataEncoderBuilder implements com.google.firebase.encoders.config.EncoderConfig { ctor public JsonDataEncoderBuilder(); - method @NonNull public com.google.firebase.encoders.DataEncoder build(); - method @NonNull public com.google.firebase.encoders.json.JsonDataEncoderBuilder configureWith(@NonNull com.google.firebase.encoders.config.Configurator); - method @NonNull public com.google.firebase.encoders.json.JsonDataEncoderBuilder ignoreNullValues(boolean); - method @NonNull public com.google.firebase.encoders.json.JsonDataEncoderBuilder registerEncoder(@NonNull Class, @NonNull com.google.firebase.encoders.ObjectEncoder); - method @NonNull public com.google.firebase.encoders.json.JsonDataEncoderBuilder registerEncoder(@NonNull Class, @NonNull com.google.firebase.encoders.ValueEncoder); - method @NonNull public com.google.firebase.encoders.json.JsonDataEncoderBuilder registerFallbackEncoder(@NonNull com.google.firebase.encoders.ObjectEncoder); + method public com.google.firebase.encoders.DataEncoder build(); + method public com.google.firebase.encoders.json.JsonDataEncoderBuilder configureWith(com.google.firebase.encoders.config.Configurator); + method public com.google.firebase.encoders.json.JsonDataEncoderBuilder ignoreNullValues(boolean); + method public com.google.firebase.encoders.json.JsonDataEncoderBuilder registerEncoder(Class, com.google.firebase.encoders.ObjectEncoder); + method public com.google.firebase.encoders.json.JsonDataEncoderBuilder registerEncoder(Class, com.google.firebase.encoders.ValueEncoder); + method public com.google.firebase.encoders.json.JsonDataEncoderBuilder registerFallbackEncoder(com.google.firebase.encoders.ObjectEncoder); + } + + public interface NumberedEnum { + method public int getNumber(); + property public abstract int number; } } diff --git a/encoders/firebase-encoders-proto/api.txt b/encoders/firebase-encoders-proto/api.txt index a669c677061..4f5593c0f28 100644 --- a/encoders/firebase-encoders-proto/api.txt +++ b/encoders/firebase-encoders-proto/api.txt @@ -1,4 +1,4 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.encoders.proto { public interface ProtoEnum { @@ -17,18 +17,18 @@ package com.google.firebase.encoders.proto { } public class ProtobufEncoder { - method public static com.google.firebase.encoders.proto.ProtobufEncoder.Builder builder(); - method public void encode(@NonNull Object, @NonNull OutputStream); - method @NonNull public byte[] encode(@NonNull Object); + method public static com.google.firebase.encoders.proto.ProtobufEncoder.Builder! builder(); + method public byte[] encode(Object); + method public void encode(Object, OutputStream); } - public static final class ProtobufEncoder.Builder implements com.google.firebase.encoders.config.EncoderConfig { + public static final class ProtobufEncoder.Builder implements com.google.firebase.encoders.config.EncoderConfig { ctor public ProtobufEncoder.Builder(); - method public com.google.firebase.encoders.proto.ProtobufEncoder build(); - method @NonNull public com.google.firebase.encoders.proto.ProtobufEncoder.Builder configureWith(@NonNull com.google.firebase.encoders.config.Configurator); - method @NonNull public com.google.firebase.encoders.proto.ProtobufEncoder.Builder registerEncoder(@NonNull Class, @NonNull com.google.firebase.encoders.ObjectEncoder); - method @NonNull public com.google.firebase.encoders.proto.ProtobufEncoder.Builder registerEncoder(@NonNull Class, @NonNull com.google.firebase.encoders.ValueEncoder); - method @NonNull public com.google.firebase.encoders.proto.ProtobufEncoder.Builder registerFallbackEncoder(@NonNull com.google.firebase.encoders.ObjectEncoder); + method public com.google.firebase.encoders.proto.ProtobufEncoder! build(); + method public com.google.firebase.encoders.proto.ProtobufEncoder.Builder configureWith(com.google.firebase.encoders.config.Configurator); + method public com.google.firebase.encoders.proto.ProtobufEncoder.Builder registerEncoder(Class, com.google.firebase.encoders.ObjectEncoder); + method public com.google.firebase.encoders.proto.ProtobufEncoder.Builder registerEncoder(Class, com.google.firebase.encoders.ValueEncoder); + method public com.google.firebase.encoders.proto.ProtobufEncoder.Builder registerFallbackEncoder(com.google.firebase.encoders.ObjectEncoder); } } diff --git a/encoders/firebase-encoders-reflective/api.txt b/encoders/firebase-encoders-reflective/api.txt index 75240473fd7..442af452bbb 100644 --- a/encoders/firebase-encoders-reflective/api.txt +++ b/encoders/firebase-encoders-reflective/api.txt @@ -1,10 +1,10 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.encoders.reflective { - public class ReflectiveObjectEncoder implements com.google.firebase.encoders.ObjectEncoder { + public class ReflectiveObjectEncoder implements com.google.firebase.encoders.ObjectEncoder { ctor public ReflectiveObjectEncoder(boolean); - method public void encode(@NonNull Object, @NonNull com.google.firebase.encoders.ObjectEncoderContext) throws java.io.IOException; - field @NonNull public static final com.google.firebase.encoders.reflective.ReflectiveObjectEncoder DEFAULT; + method public void encode(Object, com.google.firebase.encoders.ObjectEncoderContext) throws java.io.IOException; + field public static final com.google.firebase.encoders.reflective.ReflectiveObjectEncoder DEFAULT; } } diff --git a/encoders/firebase-encoders/api.txt b/encoders/firebase-encoders/api.txt index af05111422f..28153197b42 100644 --- a/encoders/firebase-encoders/api.txt +++ b/encoders/firebase-encoders/api.txt @@ -1,62 +1,62 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.encoders { public interface DataEncoder { - method public void encode(@NonNull Object, @NonNull Writer); - method @NonNull public String encode(@NonNull Object); + method public String encode(Object); + method public void encode(Object, Writer); } public final class EncodingException { - ctor public EncodingException(@NonNull String); - ctor public EncodingException(@NonNull String, @NonNull Exception); + ctor public EncodingException(String); + ctor public EncodingException(String, Exception); } public final class FieldDescriptor { - method @NonNull public static com.google.firebase.encoders.FieldDescriptor.Builder builder(@NonNull String); - method public boolean equals(Object); - method @NonNull public String getName(); - method @Nullable public T getProperty(@NonNull Class); + method public static com.google.firebase.encoders.FieldDescriptor.Builder builder(String); + method public boolean equals(Object?); + method public String getName(); + method public T? getProperty(Class); method public int hashCode(); - method @NonNull public static com.google.firebase.encoders.FieldDescriptor of(@NonNull String); - method @NonNull public String toString(); + method public static com.google.firebase.encoders.FieldDescriptor of(String); + method public String toString(); } public static final class FieldDescriptor.Builder { - method @NonNull public com.google.firebase.encoders.FieldDescriptor build(); - method @NonNull public com.google.firebase.encoders.FieldDescriptor.Builder withProperty(@NonNull T); + method public com.google.firebase.encoders.FieldDescriptor build(); + method public com.google.firebase.encoders.FieldDescriptor.Builder withProperty(T); } public interface ObjectEncoder { } public interface ObjectEncoderContext { - method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, @Nullable Object); - method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, double); - method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, int); - method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, long); - method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, boolean); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, @Nullable Object); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, float); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, double); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, int); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, long); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, boolean); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext inline(@Nullable Object); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext nested(@NonNull String); - method @NonNull public com.google.firebase.encoders.ObjectEncoderContext nested(@NonNull com.google.firebase.encoders.FieldDescriptor); + method public com.google.firebase.encoders.ObjectEncoderContext add(com.google.firebase.encoders.FieldDescriptor, boolean); + method public com.google.firebase.encoders.ObjectEncoderContext add(com.google.firebase.encoders.FieldDescriptor, double); + method public com.google.firebase.encoders.ObjectEncoderContext add(com.google.firebase.encoders.FieldDescriptor, float); + method public com.google.firebase.encoders.ObjectEncoderContext add(com.google.firebase.encoders.FieldDescriptor, int); + method public com.google.firebase.encoders.ObjectEncoderContext add(com.google.firebase.encoders.FieldDescriptor, long); + method public com.google.firebase.encoders.ObjectEncoderContext add(com.google.firebase.encoders.FieldDescriptor, Object?); + method @Deprecated public com.google.firebase.encoders.ObjectEncoderContext add(String, boolean); + method @Deprecated public com.google.firebase.encoders.ObjectEncoderContext add(String, double); + method @Deprecated public com.google.firebase.encoders.ObjectEncoderContext add(String, int); + method @Deprecated public com.google.firebase.encoders.ObjectEncoderContext add(String, long); + method @Deprecated public com.google.firebase.encoders.ObjectEncoderContext add(String, Object?); + method public com.google.firebase.encoders.ObjectEncoderContext inline(Object?); + method public com.google.firebase.encoders.ObjectEncoderContext nested(com.google.firebase.encoders.FieldDescriptor); + method public com.google.firebase.encoders.ObjectEncoderContext nested(String); } public interface ValueEncoder { } public interface ValueEncoderContext { - method @NonNull public com.google.firebase.encoders.ValueEncoderContext add(@Nullable String); - method @NonNull public com.google.firebase.encoders.ValueEncoderContext add(float); - method @NonNull public com.google.firebase.encoders.ValueEncoderContext add(double); - method @NonNull public com.google.firebase.encoders.ValueEncoderContext add(int); - method @NonNull public com.google.firebase.encoders.ValueEncoderContext add(long); - method @NonNull public com.google.firebase.encoders.ValueEncoderContext add(boolean); - method @NonNull public com.google.firebase.encoders.ValueEncoderContext add(@NonNull byte[]); + method public com.google.firebase.encoders.ValueEncoderContext add(boolean); + method public com.google.firebase.encoders.ValueEncoderContext add(byte[]); + method public com.google.firebase.encoders.ValueEncoderContext add(double); + method public com.google.firebase.encoders.ValueEncoderContext add(float); + method public com.google.firebase.encoders.ValueEncoderContext add(int); + method public com.google.firebase.encoders.ValueEncoderContext add(long); + method public com.google.firebase.encoders.ValueEncoderContext add(String?); } } @@ -75,7 +75,7 @@ package com.google.firebase.encoders.annotations { } @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExtraProperty { - method public abstract Class[] allowedTypes() default {}; + method public abstract Class[] allowedTypes() default {}; } } @@ -83,12 +83,12 @@ package com.google.firebase.encoders.annotations { package com.google.firebase.encoders.config { public interface Configurator { - method public void configure(@NonNull com.google.firebase.encoders.config.EncoderConfig); + method public void configure(com.google.firebase.encoders.config.EncoderConfig); } public interface EncoderConfig> { - method @NonNull public T registerEncoder(@NonNull Class, @NonNull com.google.firebase.encoders.ObjectEncoder); - method @NonNull public T registerEncoder(@NonNull Class, @NonNull com.google.firebase.encoders.ValueEncoder); + method public T registerEncoder(Class, com.google.firebase.encoders.ObjectEncoder); + method public T registerEncoder(Class, com.google.firebase.encoders.ValueEncoder); } } diff --git a/firebase-abt/api.txt b/firebase-abt/api.txt index d802177e249..da4f6cc18fe 100644 --- a/firebase-abt/api.txt +++ b/firebase-abt/api.txt @@ -1 +1 @@ -// Signature format: 2.0 +// Signature format: 3.0 diff --git a/firebase-annotations/api.txt b/firebase-annotations/api.txt index 8a3d66957cd..471139bae7e 100644 --- a/firebase-annotations/api.txt +++ b/firebase-annotations/api.txt @@ -1,4 +1,4 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.annotations { @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface PreviewApi { diff --git a/firebase-appdistribution-api/api.txt b/firebase-appdistribution-api/api.txt index 7fd9a1c89aa..4e823a730f8 100644 --- a/firebase-appdistribution-api/api.txt +++ b/firebase-appdistribution-api/api.txt @@ -1,11 +1,11 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.appdistribution { public interface AppDistributionRelease { - method @NonNull public com.google.firebase.appdistribution.BinaryType getBinaryType(); - method @NonNull public String getDisplayVersion(); - method @Nullable public String getReleaseNotes(); - method @NonNull public long getVersionCode(); + method public com.google.firebase.appdistribution.BinaryType getBinaryType(); + method public String getDisplayVersion(); + method public String? getReleaseNotes(); + method public long getVersionCode(); } public enum BinaryType { @@ -15,24 +15,24 @@ package com.google.firebase.appdistribution { public interface FirebaseAppDistribution { method public void cancelFeedbackNotification(); - method @NonNull public com.google.android.gms.tasks.Task checkForNewRelease(); - method @NonNull public static com.google.firebase.appdistribution.FirebaseAppDistribution getInstance(); + method public com.google.android.gms.tasks.Task checkForNewRelease(); + method public static com.google.firebase.appdistribution.FirebaseAppDistribution getInstance(); method public boolean isTesterSignedIn(); - method public void showFeedbackNotification(@StringRes int, @NonNull com.google.firebase.appdistribution.InterruptionLevel); - method public void showFeedbackNotification(@NonNull CharSequence, @NonNull com.google.firebase.appdistribution.InterruptionLevel); - method @NonNull public com.google.android.gms.tasks.Task signInTester(); + method public void showFeedbackNotification(@StringRes int, com.google.firebase.appdistribution.InterruptionLevel); + method public void showFeedbackNotification(CharSequence, com.google.firebase.appdistribution.InterruptionLevel); + method public com.google.android.gms.tasks.Task signInTester(); method public void signOutTester(); method public void startFeedback(@StringRes int); - method public void startFeedback(@NonNull CharSequence); - method public void startFeedback(@StringRes int, @Nullable android.net.Uri); - method public void startFeedback(@NonNull CharSequence, @Nullable android.net.Uri); - method @NonNull public com.google.firebase.appdistribution.UpdateTask updateApp(); - method @NonNull public com.google.firebase.appdistribution.UpdateTask updateIfNewReleaseAvailable(); + method public void startFeedback(@StringRes int, android.net.Uri?); + method public void startFeedback(CharSequence); + method public void startFeedback(CharSequence, android.net.Uri?); + method public com.google.firebase.appdistribution.UpdateTask updateApp(); + method public com.google.firebase.appdistribution.UpdateTask updateIfNewReleaseAvailable(); } public class FirebaseAppDistributionException extends com.google.firebase.FirebaseException { - method @NonNull public com.google.firebase.appdistribution.FirebaseAppDistributionException.Status getErrorCode(); - method @Nullable public com.google.firebase.appdistribution.AppDistributionRelease getRelease(); + method public com.google.firebase.appdistribution.FirebaseAppDistributionException.Status getErrorCode(); + method public com.google.firebase.appdistribution.AppDistributionRelease? getRelease(); } public enum FirebaseAppDistributionException.Status { @@ -50,14 +50,14 @@ package com.google.firebase.appdistribution { } public final class FirebaseAppDistributionKt { - method @NonNull public static operator com.google.firebase.appdistribution.BinaryType component1(@NonNull com.google.firebase.appdistribution.AppDistributionRelease); - method public static operator long component1(@NonNull com.google.firebase.appdistribution.UpdateProgress); - method @NonNull public static operator String component2(@NonNull com.google.firebase.appdistribution.AppDistributionRelease); - method public static operator long component2(@NonNull com.google.firebase.appdistribution.UpdateProgress); - method public static operator long component3(@NonNull com.google.firebase.appdistribution.AppDistributionRelease); - method @NonNull public static operator com.google.firebase.appdistribution.UpdateStatus component3(@NonNull com.google.firebase.appdistribution.UpdateProgress); - method @Nullable public static operator String component4(@NonNull com.google.firebase.appdistribution.AppDistributionRelease); - method @NonNull public static com.google.firebase.appdistribution.FirebaseAppDistribution getAppDistribution(@NonNull com.google.firebase.Firebase); + method public static operator com.google.firebase.appdistribution.BinaryType component1(com.google.firebase.appdistribution.AppDistributionRelease); + method public static operator long component1(com.google.firebase.appdistribution.UpdateProgress); + method public static operator String component2(com.google.firebase.appdistribution.AppDistributionRelease); + method public static operator long component2(com.google.firebase.appdistribution.UpdateProgress); + method public static operator long component3(com.google.firebase.appdistribution.AppDistributionRelease); + method public static operator com.google.firebase.appdistribution.UpdateStatus component3(com.google.firebase.appdistribution.UpdateProgress); + method public static operator String? component4(com.google.firebase.appdistribution.AppDistributionRelease); + method public static com.google.firebase.appdistribution.FirebaseAppDistribution getAppDistribution(com.google.firebase.Firebase); } public enum InterruptionLevel { @@ -69,13 +69,13 @@ package com.google.firebase.appdistribution { } public interface OnProgressListener { - method public void onProgressUpdate(@NonNull com.google.firebase.appdistribution.UpdateProgress); + method public void onProgressUpdate(com.google.firebase.appdistribution.UpdateProgress); } public interface UpdateProgress { method public long getApkBytesDownloaded(); method public long getApkFileTotalBytes(); - method @NonNull public com.google.firebase.appdistribution.UpdateStatus getUpdateStatus(); + method public com.google.firebase.appdistribution.UpdateStatus getUpdateStatus(); } public enum UpdateStatus { @@ -91,10 +91,10 @@ package com.google.firebase.appdistribution { enum_constant public static final com.google.firebase.appdistribution.UpdateStatus UPDATE_CANCELED; } - public abstract class UpdateTask extends com.google.android.gms.tasks.Task { + public abstract class UpdateTask extends com.google.android.gms.tasks.Task { ctor public UpdateTask(); - method @NonNull public abstract com.google.firebase.appdistribution.UpdateTask addOnProgressListener(@NonNull com.google.firebase.appdistribution.OnProgressListener); - method @NonNull public abstract com.google.firebase.appdistribution.UpdateTask addOnProgressListener(@Nullable java.util.concurrent.Executor, @NonNull com.google.firebase.appdistribution.OnProgressListener); + method public abstract com.google.firebase.appdistribution.UpdateTask addOnProgressListener(com.google.firebase.appdistribution.OnProgressListener); + method public abstract com.google.firebase.appdistribution.UpdateTask addOnProgressListener(java.util.concurrent.Executor?, com.google.firebase.appdistribution.OnProgressListener); } } @@ -102,14 +102,14 @@ package com.google.firebase.appdistribution { package com.google.firebase.appdistribution.ktx { public final class FirebaseAppDistributionKt { - method @Deprecated @NonNull public static operator com.google.firebase.appdistribution.BinaryType component1(@NonNull com.google.firebase.appdistribution.AppDistributionRelease); - method @Deprecated public static operator long component1(@NonNull com.google.firebase.appdistribution.UpdateProgress); - method @Deprecated @NonNull public static operator String component2(@NonNull com.google.firebase.appdistribution.AppDistributionRelease); - method @Deprecated public static operator long component2(@NonNull com.google.firebase.appdistribution.UpdateProgress); - method @Deprecated public static operator long component3(@NonNull com.google.firebase.appdistribution.AppDistributionRelease); - method @Deprecated @NonNull public static operator com.google.firebase.appdistribution.UpdateStatus component3(@NonNull com.google.firebase.appdistribution.UpdateProgress); - method @Deprecated @Nullable public static operator String component4(@NonNull com.google.firebase.appdistribution.AppDistributionRelease); - method @Deprecated @NonNull public static com.google.firebase.appdistribution.FirebaseAppDistribution getAppDistribution(@NonNull com.google.firebase.ktx.Firebase); + method @Deprecated public static operator com.google.firebase.appdistribution.BinaryType component1(com.google.firebase.appdistribution.AppDistributionRelease); + method @Deprecated public static operator long component1(com.google.firebase.appdistribution.UpdateProgress); + method @Deprecated public static operator String component2(com.google.firebase.appdistribution.AppDistributionRelease); + method @Deprecated public static operator long component2(com.google.firebase.appdistribution.UpdateProgress); + method @Deprecated public static operator long component3(com.google.firebase.appdistribution.AppDistributionRelease); + method @Deprecated public static operator com.google.firebase.appdistribution.UpdateStatus component3(com.google.firebase.appdistribution.UpdateProgress); + method @Deprecated public static operator String? component4(com.google.firebase.appdistribution.AppDistributionRelease); + method @Deprecated public static com.google.firebase.appdistribution.FirebaseAppDistribution getAppDistribution(com.google.firebase.ktx.Firebase); } } diff --git a/firebase-appdistribution-api/ktx/api.txt b/firebase-appdistribution-api/ktx/api.txt index f7139bec523..da4f6cc18fe 100644 --- a/firebase-appdistribution-api/ktx/api.txt +++ b/firebase-appdistribution-api/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.appdistribution.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-appdistribution/api.txt b/firebase-appdistribution/api.txt index d802177e249..da4f6cc18fe 100644 --- a/firebase-appdistribution/api.txt +++ b/firebase-appdistribution/api.txt @@ -1 +1 @@ -// Signature format: 2.0 +// Signature format: 3.0 diff --git a/firebase-common/api.txt b/firebase-common/api.txt index 841f5c4dda4..57f4f898a25 100644 --- a/firebase-common/api.txt +++ b/firebase-common/api.txt @@ -1,83 +1,83 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase { public final class Firebase { - field @NonNull public static final com.google.firebase.Firebase INSTANCE; + field public static final com.google.firebase.Firebase INSTANCE; } public class FirebaseApp { - method @NonNull public android.content.Context getApplicationContext(); - method @NonNull public static java.util.List getApps(@NonNull android.content.Context); - method @NonNull public static com.google.firebase.FirebaseApp getInstance(); - method @NonNull public static com.google.firebase.FirebaseApp getInstance(@NonNull String); - method @NonNull public String getName(); - method @NonNull public com.google.firebase.FirebaseOptions getOptions(); - method @Nullable public static com.google.firebase.FirebaseApp initializeApp(@NonNull android.content.Context); - method @NonNull public static com.google.firebase.FirebaseApp initializeApp(@NonNull android.content.Context, @NonNull com.google.firebase.FirebaseOptions); - method @NonNull public static com.google.firebase.FirebaseApp initializeApp(@NonNull android.content.Context, @NonNull com.google.firebase.FirebaseOptions, @NonNull String); + method public android.content.Context getApplicationContext(); + method public static java.util.List getApps(android.content.Context); + method public static com.google.firebase.FirebaseApp getInstance(); + method public static com.google.firebase.FirebaseApp getInstance(String); + method public String getName(); + method public com.google.firebase.FirebaseOptions getOptions(); + method public static com.google.firebase.FirebaseApp? initializeApp(android.content.Context); + method public static com.google.firebase.FirebaseApp initializeApp(android.content.Context, com.google.firebase.FirebaseOptions); + method public static com.google.firebase.FirebaseApp initializeApp(android.content.Context, com.google.firebase.FirebaseOptions, String); method public void setAutomaticResourceManagementEnabled(boolean); - field @NonNull public static final String DEFAULT_APP_NAME = "[DEFAULT]"; + field public static final String DEFAULT_APP_NAME = "[DEFAULT]"; } public final class FirebaseKt { - method @NonNull public static com.google.firebase.FirebaseApp app(@NonNull com.google.firebase.Firebase, @NonNull String name); - method @NonNull public static com.google.firebase.FirebaseApp getApp(@NonNull com.google.firebase.Firebase); - method @NonNull public static com.google.firebase.FirebaseOptions getOptions(@NonNull com.google.firebase.Firebase); - method @Nullable public static com.google.firebase.FirebaseApp initialize(@NonNull com.google.firebase.Firebase, @NonNull android.content.Context context); - method @NonNull public static com.google.firebase.FirebaseApp initialize(@NonNull com.google.firebase.Firebase, @NonNull android.content.Context context, @NonNull com.google.firebase.FirebaseOptions options); - method @NonNull public static com.google.firebase.FirebaseApp initialize(@NonNull com.google.firebase.Firebase, @NonNull android.content.Context context, @NonNull com.google.firebase.FirebaseOptions options, @NonNull String name); + method public static com.google.firebase.FirebaseApp app(com.google.firebase.Firebase, String name); + method public static com.google.firebase.FirebaseApp getApp(com.google.firebase.Firebase); + method public static com.google.firebase.FirebaseOptions getOptions(com.google.firebase.Firebase); + method public static com.google.firebase.FirebaseApp? initialize(com.google.firebase.Firebase, android.content.Context context); + method public static com.google.firebase.FirebaseApp initialize(com.google.firebase.Firebase, android.content.Context context, com.google.firebase.FirebaseOptions options); + method public static com.google.firebase.FirebaseApp initialize(com.google.firebase.Firebase, android.content.Context context, com.google.firebase.FirebaseOptions options, String name); } public class FirebaseNetworkException extends com.google.firebase.FirebaseException { - ctor public FirebaseNetworkException(@NonNull String); + ctor public FirebaseNetworkException(String); } public final class FirebaseOptions { - method @Nullable public static com.google.firebase.FirebaseOptions fromResource(@NonNull android.content.Context); - method @NonNull public String getApiKey(); - method @NonNull public String getApplicationId(); - method @Nullable public String getDatabaseUrl(); - method @Nullable public String getGcmSenderId(); - method @Nullable public String getProjectId(); - method @Nullable public String getStorageBucket(); + method public static com.google.firebase.FirebaseOptions? fromResource(android.content.Context); + method public String getApiKey(); + method public String getApplicationId(); + method public String? getDatabaseUrl(); + method public String? getGcmSenderId(); + method public String? getProjectId(); + method public String? getStorageBucket(); } public static final class FirebaseOptions.Builder { ctor public FirebaseOptions.Builder(); - ctor public FirebaseOptions.Builder(@NonNull com.google.firebase.FirebaseOptions); - method @NonNull public com.google.firebase.FirebaseOptions build(); - method @NonNull public com.google.firebase.FirebaseOptions.Builder setApiKey(@NonNull String); - method @NonNull public com.google.firebase.FirebaseOptions.Builder setApplicationId(@NonNull String); - method @NonNull public com.google.firebase.FirebaseOptions.Builder setDatabaseUrl(@Nullable String); - method @NonNull public com.google.firebase.FirebaseOptions.Builder setGcmSenderId(@Nullable String); - method @NonNull public com.google.firebase.FirebaseOptions.Builder setProjectId(@Nullable String); - method @NonNull public com.google.firebase.FirebaseOptions.Builder setStorageBucket(@Nullable String); + ctor public FirebaseOptions.Builder(com.google.firebase.FirebaseOptions); + method public com.google.firebase.FirebaseOptions build(); + method public com.google.firebase.FirebaseOptions.Builder setApiKey(String); + method public com.google.firebase.FirebaseOptions.Builder setApplicationId(String); + method public com.google.firebase.FirebaseOptions.Builder setDatabaseUrl(String?); + method public com.google.firebase.FirebaseOptions.Builder setGcmSenderId(String?); + method public com.google.firebase.FirebaseOptions.Builder setProjectId(String?); + method public com.google.firebase.FirebaseOptions.Builder setStorageBucket(String?); } public class FirebaseTooManyRequestsException extends com.google.firebase.FirebaseException { - ctor public FirebaseTooManyRequestsException(@NonNull String); + ctor public FirebaseTooManyRequestsException(String); } public final class Timestamp implements java.lang.Comparable android.os.Parcelable { + ctor @RequiresApi(android.os.Build.VERSION_CODES.O) public Timestamp(java.time.Instant time); + ctor public Timestamp(java.util.Date date); ctor public Timestamp(long seconds, int nanoseconds); - ctor public Timestamp(@NonNull java.util.Date date); - ctor @RequiresApi(android.os.Build.VERSION_CODES.O) public Timestamp(@NonNull java.time.Instant time); - method public int compareTo(@NonNull com.google.firebase.Timestamp other); + method public int compareTo(com.google.firebase.Timestamp other); method public int describeContents(); method public int getNanoseconds(); method public long getSeconds(); - method @NonNull public static com.google.firebase.Timestamp now(); - method @NonNull public java.util.Date toDate(); - method @NonNull @RequiresApi(android.os.Build.VERSION_CODES.O) public java.time.Instant toInstant(); - method public void writeToParcel(@NonNull android.os.Parcel dest, int flags); + method public static com.google.firebase.Timestamp now(); + method public java.util.Date toDate(); + method @RequiresApi(android.os.Build.VERSION_CODES.O) public java.time.Instant toInstant(); + method public void writeToParcel(android.os.Parcel dest, int flags); property public final int nanoseconds; property public final long seconds; - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field @NonNull public static final com.google.firebase.Timestamp.Companion Companion; + field public static final android.os.Parcelable.Creator CREATOR; + field public static final com.google.firebase.Timestamp.Companion Companion; } public static final class Timestamp.Companion { - method @NonNull public com.google.firebase.Timestamp now(); + method public com.google.firebase.Timestamp now(); } } @@ -85,16 +85,16 @@ package com.google.firebase { package com.google.firebase.ktx { @Deprecated public final class Firebase { - field @Deprecated @NonNull public static final com.google.firebase.ktx.Firebase INSTANCE; + field @Deprecated public static final com.google.firebase.ktx.Firebase INSTANCE; } public final class FirebaseKt { - method @Deprecated @NonNull public static com.google.firebase.FirebaseApp app(@NonNull com.google.firebase.ktx.Firebase, @NonNull String name); - method @Deprecated @NonNull public static com.google.firebase.FirebaseApp getApp(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @NonNull public static com.google.firebase.FirebaseOptions getOptions(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @Nullable public static com.google.firebase.FirebaseApp initialize(@NonNull com.google.firebase.ktx.Firebase, @NonNull android.content.Context context); - method @Deprecated @NonNull public static com.google.firebase.FirebaseApp initialize(@NonNull com.google.firebase.ktx.Firebase, @NonNull android.content.Context context, @NonNull com.google.firebase.FirebaseOptions options); - method @Deprecated @NonNull public static com.google.firebase.FirebaseApp initialize(@NonNull com.google.firebase.ktx.Firebase, @NonNull android.content.Context context, @NonNull com.google.firebase.FirebaseOptions options, @NonNull String name); + method @Deprecated public static com.google.firebase.FirebaseApp app(com.google.firebase.ktx.Firebase, String name); + method @Deprecated public static com.google.firebase.FirebaseApp getApp(com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.FirebaseOptions getOptions(com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.FirebaseApp? initialize(com.google.firebase.ktx.Firebase, android.content.Context context); + method @Deprecated public static com.google.firebase.FirebaseApp initialize(com.google.firebase.ktx.Firebase, android.content.Context context, com.google.firebase.FirebaseOptions options); + method @Deprecated public static com.google.firebase.FirebaseApp initialize(com.google.firebase.ktx.Firebase, android.content.Context context, com.google.firebase.FirebaseOptions options, String name); } } @@ -103,12 +103,12 @@ package com.google.firebase.provider { public class FirebaseInitProvider extends android.content.ContentProvider { ctor public FirebaseInitProvider(); - method public int delete(@NonNull android.net.Uri, @Nullable String, @Nullable String[]); - method @Nullable public String getType(@NonNull android.net.Uri); - method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues); + method public int delete(android.net.Uri, String?, String![]?); + method public String? getType(android.net.Uri); + method public android.net.Uri? insert(android.net.Uri, android.content.ContentValues?); method public boolean onCreate(); - method @Nullable public android.database.Cursor query(@NonNull android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String); - method public int update(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable String, @Nullable String[]); + method public android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?); + method public int update(android.net.Uri, android.content.ContentValues?, String?, String![]?); } } diff --git a/firebase-common/ktx/api.txt b/firebase-common/ktx/api.txt index 13900ee70e6..da4f6cc18fe 100644 --- a/firebase-common/ktx/api.txt +++ b/firebase-common/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-components/api.txt b/firebase-components/api.txt index d802177e249..da4f6cc18fe 100644 --- a/firebase-components/api.txt +++ b/firebase-components/api.txt @@ -1 +1 @@ -// Signature format: 2.0 +// Signature format: 3.0 diff --git a/firebase-components/firebase-dynamic-module-support/api.txt b/firebase-components/firebase-dynamic-module-support/api.txt index d802177e249..da4f6cc18fe 100644 --- a/firebase-components/firebase-dynamic-module-support/api.txt +++ b/firebase-components/firebase-dynamic-module-support/api.txt @@ -1 +1 @@ -// Signature format: 2.0 +// Signature format: 3.0 diff --git a/firebase-config-interop/api.txt b/firebase-config-interop/api.txt index f332dd309d1..2f25c2e99d4 100644 --- a/firebase-config-interop/api.txt +++ b/firebase-config-interop/api.txt @@ -1,8 +1,8 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.remoteconfig.interop { public interface FirebaseRemoteConfigInterop { - method public void registerRolloutsStateSubscriber(@NonNull String, @NonNull com.google.firebase.remoteconfig.interop.rollouts.RolloutsStateSubscriber); + method public void registerRolloutsStateSubscriber(String, com.google.firebase.remoteconfig.interop.rollouts.RolloutsStateSubscriber); } } @@ -11,35 +11,35 @@ package com.google.firebase.remoteconfig.interop.rollouts { @com.google.auto.value.AutoValue @com.google.firebase.encoders.annotations.Encodable public abstract class RolloutAssignment { ctor public RolloutAssignment(); - method @NonNull public static com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder builder(); - method @NonNull public static com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment create(@NonNull org.json.JSONObject) throws org.json.JSONException; - method @NonNull public static com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment create(@NonNull String) throws org.json.JSONException; - method @NonNull public abstract String getParameterKey(); - method @NonNull public abstract String getParameterValue(); - method @NonNull public abstract String getRolloutId(); + method public static com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder builder(); + method public static com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment create(String) throws org.json.JSONException; + method public static com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment create(org.json.JSONObject) throws org.json.JSONException; + method public abstract String getParameterKey(); + method public abstract String getParameterValue(); + method public abstract String getRolloutId(); method public abstract long getTemplateVersion(); - method @NonNull public abstract String getVariantId(); + method public abstract String getVariantId(); field public static final com.google.firebase.encoders.DataEncoder ROLLOUT_ASSIGNMENT_JSON_ENCODER; } @com.google.auto.value.AutoValue.Builder public abstract static class RolloutAssignment.Builder { ctor public RolloutAssignment.Builder(); - method @NonNull public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment build(); - method @NonNull public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setParameterKey(@NonNull String); - method @NonNull public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setParameterValue(@NonNull String); - method @NonNull public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setRolloutId(@NonNull String); - method @NonNull public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setTemplateVersion(long); - method @NonNull public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setVariantId(@NonNull String); + method public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment build(); + method public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setParameterKey(String); + method public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setParameterValue(String); + method public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setRolloutId(String); + method public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setTemplateVersion(long); + method public abstract com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.Builder setVariantId(String); } @com.google.auto.value.AutoValue public abstract class RolloutsState { ctor public RolloutsState(); - method @NonNull public static com.google.firebase.remoteconfig.interop.rollouts.RolloutsState create(@NonNull java.util.Set); - method @NonNull public abstract java.util.Set getRolloutAssignments(); + method public static com.google.firebase.remoteconfig.interop.rollouts.RolloutsState create(java.util.Set); + method public abstract java.util.Set getRolloutAssignments(); } public interface RolloutsStateSubscriber { - method public void onRolloutsStateChanged(@NonNull com.google.firebase.remoteconfig.interop.rollouts.RolloutsState); + method public void onRolloutsStateChanged(com.google.firebase.remoteconfig.interop.rollouts.RolloutsState); } } diff --git a/firebase-config/CHANGELOG.md b/firebase-config/CHANGELOG.md index edf65709ccd..93a7da1867e 100644 --- a/firebase-config/CHANGELOG.md +++ b/firebase-config/CHANGELOG.md @@ -1,6 +1,15 @@ # Unreleased +# 22.1.0 +* [feature] Added support for custom signal targeting in Remote Config. Use `setCustomSignals` API for setting custom signals and use them to build custom targeting conditions in Remote Config. + + +## Kotlin +The Kotlin extensions library transitively includes the updated +`firebase-config` library. The Kotlin extensions library has no additional +updates. + # 22.0.1 * [changed] Updated protobuf dependency to `3.25.5` to fix [CVE-2024-7254](https://nvd.nist.gov/vuln/detail/CVE-2024-7254). diff --git a/firebase-config/api.txt b/firebase-config/api.txt index 61528ec5d11..77efe522e60 100644 --- a/firebase-config/api.txt +++ b/firebase-config/api.txt @@ -1,44 +1,56 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.remoteconfig { @com.google.auto.value.AutoValue public abstract class ConfigUpdate { ctor public ConfigUpdate(); - method @NonNull public static com.google.firebase.remoteconfig.ConfigUpdate create(@NonNull java.util.Set); - method @NonNull public abstract java.util.Set getUpdatedKeys(); + method public static com.google.firebase.remoteconfig.ConfigUpdate create(java.util.Set); + method public abstract java.util.Set getUpdatedKeys(); } public interface ConfigUpdateListener { - method public void onError(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException); - method public void onUpdate(@NonNull com.google.firebase.remoteconfig.ConfigUpdate); + method public void onError(com.google.firebase.remoteconfig.FirebaseRemoteConfigException); + method public void onUpdate(com.google.firebase.remoteconfig.ConfigUpdate); } public interface ConfigUpdateListenerRegistration { method public void remove(); } + public class CustomSignals { + } + + public static class CustomSignals.Builder { + ctor public CustomSignals.Builder(); + method public com.google.firebase.remoteconfig.CustomSignals build(); + method public com.google.firebase.remoteconfig.CustomSignals.Builder put(String, double); + method public com.google.firebase.remoteconfig.CustomSignals.Builder put(String, String?); + method public com.google.firebase.remoteconfig.CustomSignals.Builder put(String, long); + } + public class FirebaseRemoteConfig { - method @NonNull public com.google.android.gms.tasks.Task activate(); - method @NonNull public com.google.firebase.remoteconfig.ConfigUpdateListenerRegistration addOnConfigUpdateListener(@NonNull com.google.firebase.remoteconfig.ConfigUpdateListener); - method @NonNull public com.google.android.gms.tasks.Task ensureInitialized(); - method @NonNull public com.google.android.gms.tasks.Task fetch(); - method @NonNull public com.google.android.gms.tasks.Task fetch(long); - method @NonNull public com.google.android.gms.tasks.Task fetchAndActivate(); - method @NonNull public java.util.Map getAll(); - method public boolean getBoolean(@NonNull String); - method public double getDouble(@NonNull String); - method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo getInfo(); - method @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getInstance(); - method @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getInstance(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public java.util.Set getKeysByPrefix(@NonNull String); - method public long getLong(@NonNull String); - method @NonNull public String getString(@NonNull String); - method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigValue getValue(@NonNull String); - method @NonNull public com.google.android.gms.tasks.Task reset(); - method @NonNull public com.google.android.gms.tasks.Task setConfigSettingsAsync(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings); - method @NonNull public com.google.android.gms.tasks.Task setDefaultsAsync(@NonNull java.util.Map); - method @NonNull public com.google.android.gms.tasks.Task setDefaultsAsync(@XmlRes int); + method public com.google.android.gms.tasks.Task activate(); + method public com.google.firebase.remoteconfig.ConfigUpdateListenerRegistration addOnConfigUpdateListener(com.google.firebase.remoteconfig.ConfigUpdateListener); + method public com.google.android.gms.tasks.Task ensureInitialized(); + method public com.google.android.gms.tasks.Task fetch(); + method public com.google.android.gms.tasks.Task fetch(long); + method public com.google.android.gms.tasks.Task fetchAndActivate(); + method public java.util.Map getAll(); + method public boolean getBoolean(String); + method public double getDouble(String); + method public com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo getInfo(); + method public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getInstance(); + method public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getInstance(com.google.firebase.FirebaseApp); + method public java.util.Set getKeysByPrefix(String); + method public long getLong(String); + method public String getString(String); + method public com.google.firebase.remoteconfig.FirebaseRemoteConfigValue getValue(String); + method public com.google.android.gms.tasks.Task reset(); + method public com.google.android.gms.tasks.Task setConfigSettingsAsync(com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings); + method public com.google.android.gms.tasks.Task setCustomSignals(com.google.firebase.remoteconfig.CustomSignals); + method public com.google.android.gms.tasks.Task setDefaultsAsync(@XmlRes int); + method public com.google.android.gms.tasks.Task setDefaultsAsync(java.util.Map); field public static final boolean DEFAULT_VALUE_FOR_BOOLEAN = false; - field public static final byte[] DEFAULT_VALUE_FOR_BYTE_ARRAY; + field public static final byte[]! DEFAULT_VALUE_FOR_BYTE_ARRAY; field public static final double DEFAULT_VALUE_FOR_DOUBLE = 0.0; field public static final long DEFAULT_VALUE_FOR_LONG = 0L; // 0x0L field public static final String DEFAULT_VALUE_FOR_STRING = ""; @@ -52,18 +64,18 @@ package com.google.firebase.remoteconfig { } public class FirebaseRemoteConfigClientException extends com.google.firebase.remoteconfig.FirebaseRemoteConfigException { - ctor public FirebaseRemoteConfigClientException(@NonNull String); - ctor public FirebaseRemoteConfigClientException(@NonNull String, @Nullable Throwable); - ctor public FirebaseRemoteConfigClientException(@NonNull String, @NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); - ctor public FirebaseRemoteConfigClientException(@NonNull String, @Nullable Throwable, @NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); + ctor public FirebaseRemoteConfigClientException(String); + ctor public FirebaseRemoteConfigClientException(String, com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); + ctor public FirebaseRemoteConfigClientException(String, Throwable?); + ctor public FirebaseRemoteConfigClientException(String, Throwable?, com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); } public class FirebaseRemoteConfigException extends com.google.firebase.FirebaseException { - ctor public FirebaseRemoteConfigException(@NonNull String); - ctor public FirebaseRemoteConfigException(@NonNull String, @Nullable Throwable); - ctor public FirebaseRemoteConfigException(@NonNull String, @NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); - ctor public FirebaseRemoteConfigException(@NonNull String, @Nullable Throwable, @NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); - method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code getCode(); + ctor public FirebaseRemoteConfigException(String); + ctor public FirebaseRemoteConfigException(String, com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); + ctor public FirebaseRemoteConfigException(String, Throwable?); + ctor public FirebaseRemoteConfigException(String, Throwable?, com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); + method public com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code getCode(); } public enum FirebaseRemoteConfigException.Code { @@ -81,51 +93,52 @@ package com.google.firebase.remoteconfig { } public interface FirebaseRemoteConfigInfo { - method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings getConfigSettings(); + method public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings getConfigSettings(); method public long getFetchTimeMillis(); method public int getLastFetchStatus(); } public class FirebaseRemoteConfigServerException extends com.google.firebase.remoteconfig.FirebaseRemoteConfigException { - ctor public FirebaseRemoteConfigServerException(int, @NonNull String); - ctor public FirebaseRemoteConfigServerException(int, @NonNull String, @Nullable Throwable); - ctor public FirebaseRemoteConfigServerException(@NonNull String, @NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); - ctor public FirebaseRemoteConfigServerException(int, @NonNull String, @NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); - ctor public FirebaseRemoteConfigServerException(@NonNull String, @Nullable Throwable, @NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); - ctor public FirebaseRemoteConfigServerException(int, @NonNull String, @Nullable Throwable, @NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); + ctor public FirebaseRemoteConfigServerException(int, String); + ctor public FirebaseRemoteConfigServerException(int, String, com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); + ctor public FirebaseRemoteConfigServerException(int, String, Throwable?); + ctor public FirebaseRemoteConfigServerException(int, String, Throwable?, com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); + ctor public FirebaseRemoteConfigServerException(String, com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); + ctor public FirebaseRemoteConfigServerException(String, Throwable?, com.google.firebase.remoteconfig.FirebaseRemoteConfigException.Code); method public int getHttpStatusCode(); } public class FirebaseRemoteConfigSettings { method public long getFetchTimeoutInSeconds(); method public long getMinimumFetchIntervalInSeconds(); - method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings.Builder toBuilder(); + method public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings.Builder toBuilder(); } public static class FirebaseRemoteConfigSettings.Builder { ctor public FirebaseRemoteConfigSettings.Builder(); - method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings build(); + method public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings build(); method public long getFetchTimeoutInSeconds(); method public long getMinimumFetchIntervalInSeconds(); - method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings.Builder setFetchTimeoutInSeconds(long) throws java.lang.IllegalArgumentException; - method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings.Builder setMinimumFetchIntervalInSeconds(long); + method public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings.Builder setFetchTimeoutInSeconds(long) throws java.lang.IllegalArgumentException; + method public com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings.Builder setMinimumFetchIntervalInSeconds(long); } public interface FirebaseRemoteConfigValue { method public boolean asBoolean() throws java.lang.IllegalArgumentException; - method @NonNull public byte[] asByteArray(); + method public byte[] asByteArray(); method public double asDouble() throws java.lang.IllegalArgumentException; method public long asLong() throws java.lang.IllegalArgumentException; - method @NonNull public String asString(); + method public String asString(); method public int getSource(); } public final class RemoteConfigKt { - method @NonNull public static operator com.google.firebase.remoteconfig.FirebaseRemoteConfigValue get(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfig, @NonNull String key); - method @NonNull public static kotlinx.coroutines.flow.Flow getConfigUpdates(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfig); - method @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getRemoteConfig(@NonNull com.google.firebase.Firebase); - method @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfig remoteConfig(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings remoteConfigSettings(@NonNull kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.remoteconfig.CustomSignals customSignals(kotlin.jvm.functions.Function1 builder); + method public static operator com.google.firebase.remoteconfig.FirebaseRemoteConfigValue get(com.google.firebase.remoteconfig.FirebaseRemoteConfig, String key); + method public static kotlinx.coroutines.flow.Flow getConfigUpdates(com.google.firebase.remoteconfig.FirebaseRemoteConfig); + method public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getRemoteConfig(com.google.firebase.Firebase); + method public static com.google.firebase.remoteconfig.FirebaseRemoteConfig remoteConfig(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); + method public static com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings remoteConfigSettings(kotlin.jvm.functions.Function1 init); } } @@ -133,11 +146,11 @@ package com.google.firebase.remoteconfig { package com.google.firebase.remoteconfig.ktx { public final class RemoteConfigKt { - method @Deprecated @NonNull public static operator com.google.firebase.remoteconfig.FirebaseRemoteConfigValue get(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfig, @NonNull String key); - method @Deprecated @NonNull public static kotlinx.coroutines.flow.Flow getConfigUpdates(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfig); - method @Deprecated @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getRemoteConfig(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfig remoteConfig(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings remoteConfigSettings(@NonNull kotlin.jvm.functions.Function1 init); + method @Deprecated public static operator com.google.firebase.remoteconfig.FirebaseRemoteConfigValue get(com.google.firebase.remoteconfig.FirebaseRemoteConfig, String key); + method @Deprecated public static kotlinx.coroutines.flow.Flow getConfigUpdates(com.google.firebase.remoteconfig.FirebaseRemoteConfig); + method @Deprecated public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getRemoteConfig(com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.remoteconfig.FirebaseRemoteConfig remoteConfig(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); + method public static com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings remoteConfigSettings(kotlin.jvm.functions.Function1 init); } } diff --git a/firebase-config/gradle.properties b/firebase-config/gradle.properties index b0072000b48..26e0ad751e6 100644 --- a/firebase-config/gradle.properties +++ b/firebase-config/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # -version=22.0.2 -latestReleasedVersion=22.0.1 +version=22.1.1 +latestReleasedVersion=22.1.0 android.enableUnitTestBinaryResources=true diff --git a/firebase-config/ktx/api.txt b/firebase-config/ktx/api.txt index 071387283db..da4f6cc18fe 100644 --- a/firebase-config/ktx/api.txt +++ b/firebase-config/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.remoteconfig.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/TestConstructorUtil.kt b/firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/TestConstructorUtil.kt index e6167b5ab09..4c153bff1e3 100644 --- a/firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/TestConstructorUtil.kt +++ b/firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/TestConstructorUtil.kt @@ -23,8 +23,8 @@ import com.google.firebase.installations.FirebaseInstallationsApi import com.google.firebase.remoteconfig.internal.ConfigCacheClient import com.google.firebase.remoteconfig.internal.ConfigFetchHandler import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler import java.util.concurrent.Executor @@ -41,7 +41,7 @@ fun createRemoteConfig( defaultConfigsCache: ConfigCacheClient, fetchHandler: ConfigFetchHandler, getHandler: ConfigGetParameterHandler, - frcMetadata: ConfigMetadataClient, + frcSharedPrefs: ConfigSharedPrefsClient, realtimeHandler: ConfigRealtimeHandler, rolloutsStateSubscriptionsHandler: RolloutsStateSubscriptionsHandler ): FirebaseRemoteConfig { @@ -56,7 +56,7 @@ fun createRemoteConfig( defaultConfigsCache, fetchHandler, getHandler, - frcMetadata, + frcSharedPrefs, realtimeHandler, rolloutsStateSubscriptionsHandler ) diff --git a/firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt b/firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt index bb525cf4090..d3e7d10e725 100644 --- a/firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt +++ b/firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt @@ -32,8 +32,8 @@ import com.google.firebase.remoteconfig.createRemoteConfig import com.google.firebase.remoteconfig.internal.ConfigCacheClient import com.google.firebase.remoteconfig.internal.ConfigFetchHandler import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler import org.junit.After import org.junit.Before @@ -142,7 +142,7 @@ class ConfigTests : BaseTestCase() { defaultConfigsCache = mock(ConfigCacheClient::class.java), fetchHandler = mock(ConfigFetchHandler::class.java), getHandler = mockGetHandler, - frcMetadata = mock(ConfigMetadataClient::class.java), + frcSharedPrefs = mock(ConfigSharedPrefsClient::class.java), realtimeHandler = mock(ConfigRealtimeHandler::class.java), rolloutsStateSubscriptionsHandler = mock(RolloutsStateSubscriptionsHandler::class.java) ) diff --git a/firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java b/firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java index 19900986b6f..cc8c259836e 100644 --- a/firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java +++ b/firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java @@ -35,8 +35,8 @@ import com.google.firebase.remoteconfig.internal.ConfigContainer; import com.google.firebase.remoteconfig.internal.ConfigFetchHandler; import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient; import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient; import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler; import java.util.Date; import java.util.HashMap; @@ -60,7 +60,7 @@ public class FirebaseRemoteConfigIntegrationTest { @Mock private ConfigCacheClient mockDefaultsCache; @Mock private ConfigFetchHandler mockFetchHandler; @Mock private ConfigGetParameterHandler mockGetHandler; - @Mock private ConfigMetadataClient metadataClient; + @Mock private ConfigSharedPrefsClient sharedPrefsClient; @Mock private ConfigRealtimeHandler mockConfigRealtimeHandler; @Mock private RolloutsStateSubscriptionsHandler mockRolloutsStateSubscriptionHandler; @@ -112,7 +112,7 @@ public void setUp() { mockDefaultsCache, mockFetchHandler, mockGetHandler, - metadataClient, + sharedPrefsClient, mockConfigRealtimeHandler, mockRolloutsStateSubscriptionHandler); } diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/CustomSignals.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/CustomSignals.java new file mode 100644 index 00000000000..78d8d6cc415 --- /dev/null +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/CustomSignals.java @@ -0,0 +1,86 @@ +// Copyright 2024 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.remoteconfig; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +/** + * A container type to represent key/value pairs of heterogeneous types to be set as custom signals + * in {@link FirebaseRemoteConfig#setCustomSignals}. + */ +public class CustomSignals { + final Map customSignals; + + /** Builder for constructing {@link CustomSignals} instances. */ + public static class Builder { + private Map customSignals = new HashMap(); + + /** + * Adds a custom signal with a value that can be a string or null to the builder. + * + * @param key The key for the custom signal. + * @param value The string value associated with the key. Can be null. + * @return This Builder instance to allow chaining of method calls. + */ + @NonNull + public Builder put(@NonNull String key, @Nullable String value) { + customSignals.put(key, value); + return this; + } + + /** + * Adds a custom signal with a long value to the builder. + * + * @param key The key for the custom signal. + * @param value The long value for the custom signal. + * @return This Builder instance to allow chaining of method calls. + */ + @NonNull + public Builder put(@NonNull String key, long value) { + customSignals.put(key, Long.toString(value)); + return this; + } + + /** + * Adds a custom signal with a double value to the builder. + * + * @param key The key for the custom signal. + * @param value The double value for the custom signal. + * @return This Builder instance to allow chaining of method calls. + */ + @NonNull + public Builder put(@NonNull String key, double value) { + customSignals.put(key, Double.toString(value)); + return this; + } + + /** + * Creates a {@link CustomSignals} instance with the added custom signals. + * + * @return The constructed {@link CustomSignals} instance. + */ + @NonNull + public CustomSignals build() { + return new CustomSignals(this); + } + } + + CustomSignals(@NonNull Builder builder) { + this.customSignals = builder.customSignals; + } +} diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java index b2e7e73d954..808892e7521 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java @@ -33,8 +33,8 @@ import com.google.firebase.remoteconfig.internal.ConfigFetchHandler; import com.google.firebase.remoteconfig.internal.ConfigFetchHandler.FetchResponse; import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient; import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient; import com.google.firebase.remoteconfig.internal.DefaultsXmlParser; import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler; import java.util.ArrayList; @@ -160,7 +160,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) { private final ConfigCacheClient defaultConfigsCache; private final ConfigFetchHandler fetchHandler; private final ConfigGetParameterHandler getHandler; - private final ConfigMetadataClient frcMetadata; + private final ConfigSharedPrefsClient frcSharedPrefs; private final FirebaseInstallationsApi firebaseInstallations; private final ConfigRealtimeHandler configRealtimeHandler; private final RolloutsStateSubscriptionsHandler rolloutsStateSubscriptionsHandler; @@ -181,7 +181,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) { ConfigCacheClient defaultConfigsCache, ConfigFetchHandler fetchHandler, ConfigGetParameterHandler getHandler, - ConfigMetadataClient frcMetadata, + ConfigSharedPrefsClient frcSharedPrefs, ConfigRealtimeHandler configRealtimeHandler, RolloutsStateSubscriptionsHandler rolloutsStateSubscriptionsHandler) { this.context = context; @@ -194,7 +194,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) { this.defaultConfigsCache = defaultConfigsCache; this.fetchHandler = fetchHandler; this.getHandler = getHandler; - this.frcMetadata = frcMetadata; + this.frcSharedPrefs = frcSharedPrefs; this.configRealtimeHandler = configRealtimeHandler; this.rolloutsStateSubscriptionsHandler = rolloutsStateSubscriptionsHandler; } @@ -208,7 +208,7 @@ public Task ensureInitialized() { Task activatedConfigsTask = activatedConfigsCache.get(); Task defaultsConfigsTask = defaultConfigsCache.get(); Task fetchedConfigsTask = fetchedConfigsCache.get(); - Task metadataTask = Tasks.call(executor, this::getInfo); + Task sharedPrefsTask = Tasks.call(executor, this::getInfo); Task installationIdTask = firebaseInstallations.getId(); Task installationTokenTask = firebaseInstallations.getToken(false); @@ -216,10 +216,10 @@ public Task ensureInitialized() { activatedConfigsTask, defaultsConfigsTask, fetchedConfigsTask, - metadataTask, + sharedPrefsTask, installationIdTask, installationTokenTask) - .continueWith(executor, (unusedListOfCompletedTasks) -> metadataTask.getResult()); + .continueWith(executor, (unusedListOfCompletedTasks) -> sharedPrefsTask.getResult()); } /** @@ -475,7 +475,7 @@ public Map getAll() { */ @NonNull public FirebaseRemoteConfigInfo getInfo() { - return frcMetadata.getInfo(); + return frcSharedPrefs.getInfo(); } /** @@ -488,7 +488,7 @@ public Task setConfigSettingsAsync(@NonNull FirebaseRemoteConfigSettings s return Tasks.call( executor, () -> { - frcMetadata.setConfigSettings(settings); + frcSharedPrefs.setConfigSettings(settings); // Return value required; return null for Void. return null; @@ -548,14 +548,14 @@ public Task setDefaultsAsync(@XmlRes int resourceId) { @NonNull public Task reset() { // Use a Task to avoid throwing potential file I/O errors to the caller and because - // frcMetadata's clear call is blocking. + // frcSharedPrefs's clear call is blocking. return Tasks.call( executor, () -> { activatedConfigsCache.clear(); fetchedConfigsCache.clear(); defaultConfigsCache.clear(); - frcMetadata.clear(); + frcSharedPrefs.clear(); return null; }); } @@ -652,6 +652,30 @@ private Task setDefaultsWithStringsMapAsync(Map defaultsSt FirebaseExecutors.directExecutor(), (unusedContainer) -> Tasks.forResult(null)); } + /** + * Asynchronously changes the custom signals for this {@link FirebaseRemoteConfig} instance. + * + *

Custom signals are subject to limits on the size of key/value pairs and the total + * number of signals. Any calls that exceed these limits will be discarded. + * + * @param customSignals The custom signals to set for this instance. + *

    + *
  1. New keys will add new key-value pairs in the custom signals. + *
  2. Existing keys with new values will update the corresponding signals. + *
  3. Setting a key's value to {@code null} will remove the associated signal. + *
+ */ + // TODO(b/385028620): Add link to documentation about custom signal limits. + @NonNull + public Task setCustomSignals(@NonNull CustomSignals customSignals) { + return Tasks.call( + executor, + () -> { + frcSharedPrefs.setCustomSignals(customSignals.customSignals); + return null; + }); + } + /** * Notifies the Firebase A/B Testing SDK about activated experiments. * diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfig.kt b/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfig.kt index a944a7e8857..3a7ef220198 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfig.kt +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfig.kt @@ -48,6 +48,9 @@ fun remoteConfigSettings( return builder.build() } +fun customSignals(builder: CustomSignals.Builder.() -> Unit) = + CustomSignals.Builder().apply(builder).build() + /** * Starts listening for config updates from the Remote Config backend and emits [ConfigUpdate]s via * a [Flow]. See [FirebaseRemoteConfig.addOnConfigUpdateListener] for more information. diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigComponent.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigComponent.java index 0835488f934..73fcec6e6c0 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigComponent.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigComponent.java @@ -36,8 +36,8 @@ import com.google.firebase.remoteconfig.internal.ConfigFetchHandler; import com.google.firebase.remoteconfig.internal.ConfigFetchHttpClient; import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient; import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient; import com.google.firebase.remoteconfig.internal.ConfigStorageClient; import com.google.firebase.remoteconfig.internal.Personalization; import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateFactory; @@ -166,7 +166,7 @@ public synchronized FirebaseRemoteConfig get(String namespace) { ConfigCacheClient fetchedCacheClient = getCacheClient(namespace, FETCH_FILE_NAME); ConfigCacheClient activatedCacheClient = getCacheClient(namespace, ACTIVATE_FILE_NAME); ConfigCacheClient defaultsCacheClient = getCacheClient(namespace, DEFAULTS_FILE_NAME); - ConfigMetadataClient metadataClient = getMetadataClient(context, appId, namespace); + ConfigSharedPrefsClient sharedPrefsClient = getSharedPrefsClient(context, appId, namespace); ConfigGetParameterHandler getHandler = getGetHandler(activatedCacheClient, defaultsCacheClient); Personalization personalization = @@ -187,9 +187,9 @@ public synchronized FirebaseRemoteConfig get(String namespace) { fetchedCacheClient, activatedCacheClient, defaultsCacheClient, - getFetchHandler(namespace, fetchedCacheClient, metadataClient), + getFetchHandler(namespace, fetchedCacheClient, sharedPrefsClient), getHandler, - metadataClient, + sharedPrefsClient, rolloutsStateSubscriptionsHandler); } @@ -206,7 +206,7 @@ synchronized FirebaseRemoteConfig get( ConfigCacheClient defaultsClient, ConfigFetchHandler fetchHandler, ConfigGetParameterHandler getHandler, - ConfigMetadataClient metadataClient, + ConfigSharedPrefsClient sharedPrefsClient, RolloutsStateSubscriptionsHandler rolloutsStateSubscriptionsHandler) { if (!frcNamespaceInstances.containsKey(namespace)) { FirebaseRemoteConfig in = @@ -221,7 +221,7 @@ synchronized FirebaseRemoteConfig get( defaultsClient, fetchHandler, getHandler, - metadataClient, + sharedPrefsClient, getRealtime( firebaseApp, firebaseInstallations, @@ -229,7 +229,7 @@ synchronized FirebaseRemoteConfig get( activatedClient, context, namespace, - metadataClient), + sharedPrefsClient), rolloutsStateSubscriptionsHandler); in.startLoadingConfigsFromDisk(); frcNamespaceInstances.put(namespace, in); @@ -254,20 +254,22 @@ private ConfigCacheClient getCacheClient(String namespace, String configStoreTyp @VisibleForTesting ConfigFetchHttpClient getFrcBackendApiClient( - String apiKey, String namespace, ConfigMetadataClient metadataClient) { + String apiKey, String namespace, ConfigSharedPrefsClient sharedPrefsClient) { String appId = firebaseApp.getOptions().getApplicationId(); return new ConfigFetchHttpClient( context, appId, apiKey, namespace, - /* connectTimeoutInSeconds= */ metadataClient.getFetchTimeoutInSeconds(), - /* readTimeoutInSeconds= */ metadataClient.getFetchTimeoutInSeconds()); + /* connectTimeoutInSeconds= */ sharedPrefsClient.getFetchTimeoutInSeconds(), + /* readTimeoutInSeconds= */ sharedPrefsClient.getFetchTimeoutInSeconds()); } @VisibleForTesting synchronized ConfigFetchHandler getFetchHandler( - String namespace, ConfigCacheClient fetchedCacheClient, ConfigMetadataClient metadataClient) { + String namespace, + ConfigCacheClient fetchedCacheClient, + ConfigSharedPrefsClient sharedPrefsClient) { return new ConfigFetchHandler( firebaseInstallations, isPrimaryApp(firebaseApp) ? analyticsConnector : () -> null, @@ -275,8 +277,8 @@ synchronized ConfigFetchHandler getFetchHandler( DEFAULT_CLOCK, DEFAULT_RANDOM, fetchedCacheClient, - getFrcBackendApiClient(firebaseApp.getOptions().getApiKey(), namespace, metadataClient), - metadataClient, + getFrcBackendApiClient(firebaseApp.getOptions().getApiKey(), namespace, sharedPrefsClient), + sharedPrefsClient, this.customHeaders); } @@ -287,7 +289,7 @@ synchronized ConfigRealtimeHandler getRealtime( ConfigCacheClient activatedCacheClient, Context context, String namespace, - ConfigMetadataClient metadataClient) { + ConfigSharedPrefsClient sharedPrefsClient) { return new ConfigRealtimeHandler( firebaseApp, firebaseInstallations, @@ -295,7 +297,7 @@ synchronized ConfigRealtimeHandler getRealtime( activatedCacheClient, context, namespace, - metadataClient, + sharedPrefsClient, executor); } @@ -305,13 +307,14 @@ private ConfigGetParameterHandler getGetHandler( } @VisibleForTesting - static ConfigMetadataClient getMetadataClient(Context context, String appId, String namespace) { + static ConfigSharedPrefsClient getSharedPrefsClient( + Context context, String appId, String namespace) { String fileName = String.format( "%s_%s_%s_%s", FIREBASE_REMOTE_CONFIG_FILE_NAME_PREFIX, appId, namespace, PREFERENCES_FILE_NAME); SharedPreferences preferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE); - return new ConfigMetadataClient(preferences); + return new ConfigSharedPrefsClient(preferences); } @Nullable diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigConstants.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigConstants.java index 9c1a352dd0a..410306afd9c 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigConstants.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigConstants.java @@ -51,7 +51,8 @@ public final class RemoteConfigConstants { RequestFieldKey.PACKAGE_NAME, RequestFieldKey.SDK_VERSION, RequestFieldKey.ANALYTICS_USER_PROPERTIES, - RequestFieldKey.FIRST_OPEN_TIME + RequestFieldKey.FIRST_OPEN_TIME, + RequestFieldKey.CUSTOM_SIGNALS }) @Retention(RetentionPolicy.SOURCE) public @interface RequestFieldKey { @@ -68,6 +69,7 @@ public final class RemoteConfigConstants { String SDK_VERSION = "sdkVersion"; String ANALYTICS_USER_PROPERTIES = "analyticsUserProperties"; String FIRST_OPEN_TIME = "firstOpenTime"; + String CUSTOM_SIGNALS = "customSignals"; } /** Keys of fields in the Fetch response body from the Firebase Remote Config server. */ diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandler.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandler.java index 7c98589b153..e130d13df49 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandler.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandler.java @@ -14,7 +14,7 @@ package com.google.firebase.remoteconfig.internal; -import static com.google.firebase.remoteconfig.internal.ConfigMetadataClient.LAST_FETCH_TIME_NO_FETCH_YET; +import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.LAST_FETCH_TIME_NO_FETCH_YET; import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_GATEWAY_TIMEOUT; @@ -43,7 +43,7 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException; import com.google.firebase.remoteconfig.FirebaseRemoteConfigServerException; import com.google.firebase.remoteconfig.internal.ConfigFetchHandler.FetchResponse.Status; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient.BackoffMetadata; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.BackoffMetadata; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.HttpURLConnection; @@ -96,7 +96,7 @@ public class ConfigFetchHandler { private final Random randomGenerator; private final ConfigCacheClient fetchedConfigsCache; private final ConfigFetchHttpClient frcBackendApiClient; - private final ConfigMetadataClient frcMetadata; + private final ConfigSharedPrefsClient frcSharedPrefs; private final Map customHttpHeaders; @@ -109,7 +109,7 @@ public ConfigFetchHandler( Random randomGenerator, ConfigCacheClient fetchedConfigsCache, ConfigFetchHttpClient frcBackendApiClient, - ConfigMetadataClient frcMetadata, + ConfigSharedPrefsClient frcSharedPrefs, Map customHttpHeaders) { this.firebaseInstallations = firebaseInstallations; this.analyticsConnector = analyticsConnector; @@ -118,16 +118,16 @@ public ConfigFetchHandler( this.randomGenerator = randomGenerator; this.fetchedConfigsCache = fetchedConfigsCache; this.frcBackendApiClient = frcBackendApiClient; - this.frcMetadata = frcMetadata; + this.frcSharedPrefs = frcSharedPrefs; this.customHttpHeaders = customHttpHeaders; } /** * Calls {@link #fetch(long)} with the {@link - * ConfigMetadataClient#getMinimumFetchIntervalInSeconds()}. + * ConfigSharedPrefsClient#getMinimumFetchIntervalInSeconds()}. */ public Task fetch() { - return fetch(frcMetadata.getMinimumFetchIntervalInSeconds()); + return fetch(frcSharedPrefs.getMinimumFetchIntervalInSeconds()); } /** @@ -228,7 +228,7 @@ public Task fetchNowWithTypeAndAttemptNumber( * currently throttled. * *

If a fetch request is made to the backend, updates the last fetch status, last successful - * fetch time and {@link BackoffMetadata} in {@link ConfigMetadataClient}. + * fetch time and {@link BackoffMetadata} in {@link ConfigSharedPrefsClient}. */ private Task fetchIfCacheExpiredAndNotThrottled( Task cachedFetchConfigsTask, @@ -295,7 +295,7 @@ && areCachedFetchConfigsValid(minimumFetchIntervalInSeconds, currentTime)) { * on. */ private boolean areCachedFetchConfigsValid(long cacheExpirationInSeconds, Date newFetchTime) { - Date lastSuccessfulFetchTime = frcMetadata.getLastSuccessfulFetchTime(); + Date lastSuccessfulFetchTime = frcSharedPrefs.getLastSuccessfulFetchTime(); // RC always fetches if the client has not previously had a successful fetch. if (lastSuccessfulFetchTime.equals(LAST_FETCH_TIME_NO_FETCH_YET)) { @@ -315,7 +315,7 @@ private boolean areCachedFetchConfigsValid(long cacheExpirationInSeconds, Date n */ @Nullable private Date getBackoffEndTimeInMillis(Date currentTime) { - Date backoffEndTime = frcMetadata.getBackoffMetadata().getBackoffEndTime(); + Date backoffEndTime = frcSharedPrefs.getBackoffMetadata().getBackoffEndTime(); if (currentTime.before(backoffEndTime)) { return backoffEndTime; } @@ -381,21 +381,23 @@ private FetchResponse fetchFromBackend( installationId, installationToken, getUserProperties(), - frcMetadata.getLastFetchETag(), + frcSharedPrefs.getLastFetchETag(), customFetchHeaders, getFirstOpenTime(), - currentTime); + currentTime, + frcSharedPrefs.getCustomSignals()); if (response.getFetchedConfigs() != null) { // Set template version in metadata to be saved on disk. - frcMetadata.setLastTemplateVersion(response.getFetchedConfigs().getTemplateVersionNumber()); + frcSharedPrefs.setLastTemplateVersion( + response.getFetchedConfigs().getTemplateVersionNumber()); } if (response.getLastFetchETag() != null) { - frcMetadata.setLastFetchETag(response.getLastFetchETag()); + frcSharedPrefs.setLastFetchETag(response.getLastFetchETag()); } // If the execute method did not throw exceptions, then the server sent a successful response // and the client can stop backing off. - frcMetadata.resetBackoff(); + frcSharedPrefs.resetBackoff(); return response; } catch (FirebaseRemoteConfigServerException serverHttpError) { @@ -473,7 +475,7 @@ private BackoffMetadata updateAndReturnBackoffMetadata(int statusCode, Date curr if (isThrottleableServerError(statusCode)) { updateBackoffMetadataWithLastFailedFetchTime(currentTime); } - return frcMetadata.getBackoffMetadata(); + return frcSharedPrefs.getBackoffMetadata(); } /** @@ -497,14 +499,14 @@ private boolean isThrottleableServerError(int httpStatusCode) { * disk-backed metadata. */ private void updateBackoffMetadataWithLastFailedFetchTime(Date lastFailedFetchTime) { - int numFailedFetches = frcMetadata.getBackoffMetadata().getNumFailedFetches(); + int numFailedFetches = frcSharedPrefs.getBackoffMetadata().getNumFailedFetches(); numFailedFetches++; long backoffDurationInMillis = getRandomizedBackoffDurationInMillis(numFailedFetches); Date backoffEndTime = new Date(lastFailedFetchTime.getTime() + backoffDurationInMillis); - frcMetadata.setBackoffMetadata(numFailedFetches, backoffEndTime); + frcSharedPrefs.setBackoffMetadata(numFailedFetches, backoffEndTime); } /** @@ -551,7 +553,7 @@ private boolean shouldThrottle(BackoffMetadata backoffMetadata, int httpStatusCo private void updateLastFetchStatusAndTime( Task completedFetchTask, Date fetchTime) { if (completedFetchTask.isSuccessful()) { - frcMetadata.updateLastFetchAsSuccessfulAt(fetchTime); + frcSharedPrefs.updateLastFetchAsSuccessfulAt(fetchTime); return; } @@ -562,9 +564,9 @@ private void updateLastFetchStatusAndTime( } if (fetchException instanceof FirebaseRemoteConfigFetchThrottledException) { - frcMetadata.updateLastFetchAsThrottled(); + frcSharedPrefs.updateLastFetchAsThrottled(); } else { - frcMetadata.updateLastFetchAsFailed(); + frcSharedPrefs.updateLastFetchAsFailed(); } } @@ -602,7 +604,7 @@ private Long getFirstOpenTime() { } public long getTemplateVersionNumber() { - return frcMetadata.getLastTemplateVersion(); + return frcSharedPrefs.getLastTemplateVersion(); } /** Used to verify that the fetch handler is getting Analytics as expected. */ diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClient.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClient.java index b1867347580..ab068ffc674 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClient.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClient.java @@ -21,6 +21,7 @@ import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.APP_ID; import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.APP_VERSION; import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.COUNTRY_CODE; +import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.CUSTOM_SIGNALS; import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.FIRST_OPEN_TIME; import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.INSTANCE_ID; import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.INSTANCE_ID_TOKEN; @@ -183,7 +184,8 @@ FetchResponse fetch( String lastFetchETag, Map customHeaders, Long firstOpenTime, - Date currentTime) + Date currentTime, + Map customSignalsMap) throws FirebaseRemoteConfigException { setUpUrlConnection(urlConnection, lastFetchETag, installationAuthToken, customHeaders); @@ -192,7 +194,11 @@ FetchResponse fetch( try { byte[] requestBody = createFetchRequestBody( - installationId, installationAuthToken, analyticsUserProperties, firstOpenTime) + installationId, + installationAuthToken, + analyticsUserProperties, + firstOpenTime, + customSignalsMap) .toString() .getBytes("utf-8"); setFetchRequestBody(urlConnection, requestBody); @@ -303,7 +309,8 @@ private JSONObject createFetchRequestBody( String installationId, String installationAuthToken, Map analyticsUserProperties, - Long firstOpenTime) + Long firstOpenTime, + Map customSignalsMap) throws FirebaseRemoteConfigClientException { Map requestBodyMap = new HashMap<>(); @@ -347,6 +354,13 @@ private JSONObject createFetchRequestBody( requestBodyMap.put(ANALYTICS_USER_PROPERTIES, new JSONObject(analyticsUserProperties)); + if (!customSignalsMap.isEmpty()) { + requestBodyMap.put(CUSTOM_SIGNALS, new JSONObject(customSignalsMap)); + + // Log the keys of the custom signals sent during fetch. + Log.d(TAG, "Keys of custom signals during fetch: " + customSignalsMap.keySet()); + } + if (firstOpenTime != null) { requestBodyMap.put(FIRST_OPEN_TIME, convertToISOString(firstOpenTime)); } diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHandler.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHandler.java index 53f89a71e8c..5ed1135dfc7 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHandler.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHandler.java @@ -39,7 +39,7 @@ public class ConfigRealtimeHandler { private final ConfigCacheClient activatedCacheClient; private final Context context; private final String namespace; - private final ConfigMetadataClient metadataClient; + private final ConfigSharedPrefsClient sharedPrefsClient; private final ScheduledExecutorService scheduledExecutorService; public ConfigRealtimeHandler( @@ -49,7 +49,7 @@ public ConfigRealtimeHandler( ConfigCacheClient activatedCacheClient, Context context, String namespace, - ConfigMetadataClient metadataClient, + ConfigSharedPrefsClient sharedPrefsClient, ScheduledExecutorService scheduledExecutorService) { this.listeners = new LinkedHashSet<>(); @@ -62,7 +62,7 @@ public ConfigRealtimeHandler( context, namespace, listeners, - metadataClient, + sharedPrefsClient, scheduledExecutorService); this.firebaseApp = firebaseApp; @@ -71,7 +71,7 @@ public ConfigRealtimeHandler( this.activatedCacheClient = activatedCacheClient; this.context = context; this.namespace = namespace; - this.metadataClient = metadataClient; + this.sharedPrefsClient = sharedPrefsClient; this.scheduledExecutorService = scheduledExecutorService; } diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHttpClient.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHttpClient.java index ef0ef2defc5..2c1c44480e2 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHttpClient.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHttpClient.java @@ -110,7 +110,7 @@ public class ConfigRealtimeHttpClient { private final String namespace; private final Random random; private final Clock clock; - private final ConfigMetadataClient metadataClient; + private final ConfigSharedPrefsClient sharedPrefsClient; public ConfigRealtimeHttpClient( FirebaseApp firebaseApp, @@ -120,7 +120,7 @@ public ConfigRealtimeHttpClient( Context context, String namespace, Set listeners, - ConfigMetadataClient metadataClient, + ConfigSharedPrefsClient sharedPrefsClient, ScheduledExecutorService scheduledExecutorService) { this.listeners = listeners; @@ -132,7 +132,7 @@ public ConfigRealtimeHttpClient( // Retrieve number of remaining retries from last session. The minimum retry count being one. httpRetriesRemaining = Math.max( - ORIGINAL_RETRIES - metadataClient.getRealtimeBackoffMetadata().getNumFailedStreams(), + ORIGINAL_RETRIES - sharedPrefsClient.getRealtimeBackoffMetadata().getNumFailedStreams(), 1); clock = DefaultClock.getInstance(); @@ -142,7 +142,7 @@ public ConfigRealtimeHttpClient( this.activatedCache = activatedCache; this.context = context; this.namespace = namespace; - this.metadataClient = metadataClient; + this.sharedPrefsClient = sharedPrefsClient; this.isRealtimeDisabled = false; this.isInBackground = false; } @@ -230,13 +230,13 @@ private synchronized void propagateErrors(FirebaseRemoteConfigException exceptio // Used for Tests only. @SuppressLint("VisibleForTests") public int getNumberOfFailedStreams() { - return metadataClient.getRealtimeBackoffMetadata().getNumFailedStreams(); + return sharedPrefsClient.getRealtimeBackoffMetadata().getNumFailedStreams(); } // Used for Tests only. @SuppressLint("VisibleForTests") public Date getBackoffEndTime() { - return metadataClient.getRealtimeBackoffMetadata().getBackoffEndTime(); + return sharedPrefsClient.getRealtimeBackoffMetadata().getBackoffEndTime(); } // TODO(issues/265): Make this an atomic operation within the Metadata class to avoid possible @@ -248,7 +248,7 @@ public Date getBackoffEndTime() { */ private void updateBackoffMetadataWithLastFailedStreamConnectionTime( Date lastFailedRealtimeStreamTime) { - int numFailedStreams = metadataClient.getRealtimeBackoffMetadata().getNumFailedStreams(); + int numFailedStreams = sharedPrefsClient.getRealtimeBackoffMetadata().getNumFailedStreams(); numFailedStreams++; @@ -256,7 +256,7 @@ private void updateBackoffMetadataWithLastFailedStreamConnectionTime( Date backoffEndTime = new Date(lastFailedRealtimeStreamTime.getTime() + backoffDurationInMillis); - metadataClient.setRealtimeBackoffMetadata(numFailedStreams, backoffEndTime); + sharedPrefsClient.setRealtimeBackoffMetadata(numFailedStreams, backoffEndTime); } /** @@ -362,7 +362,7 @@ public synchronized void retryHttpConnectionWhenBackoffEnds() { long retrySeconds = Math.max( 0, - metadataClient.getRealtimeBackoffMetadata().getBackoffEndTime().getTime() + sharedPrefsClient.getRealtimeBackoffMetadata().getBackoffEndTime().getTime() - currentTime.getTime()); makeRealtimeHttpConnection(retrySeconds); } @@ -473,8 +473,8 @@ public void beginRealtimeHttpStream() { return; } - ConfigMetadataClient.RealtimeBackoffMetadata backoffMetadata = - metadataClient.getRealtimeBackoffMetadata(); + ConfigSharedPrefsClient.RealtimeBackoffMetadata backoffMetadata = + sharedPrefsClient.getRealtimeBackoffMetadata(); Date currentTime = new Date(clock.currentTimeMillis()); if (currentTime.before(backoffMetadata.getBackoffEndTime())) { retryHttpConnectionWhenBackoffEnds(); @@ -506,7 +506,7 @@ public void beginRealtimeHttpStream() { if (responseCode == HttpURLConnection.HTTP_OK) { // Reset the retries remaining if we opened the connection without an exception. resetRetryCount(); - metadataClient.resetRealtimeBackoff(); + sharedPrefsClient.resetRealtimeBackoff(); // Start listening for realtime notifications. ConfigAutoFetch configAutoFetch = startAutoFetch(httpURLConnection); diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigMetadataClient.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClient.java similarity index 65% rename from firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigMetadataClient.java rename to firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClient.java index dddcb24bdb8..7ce24bc44f6 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigMetadataClient.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClient.java @@ -18,11 +18,14 @@ import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET; import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS; import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_THROTTLED; +import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.TAG; import static com.google.firebase.remoteconfig.RemoteConfigComponent.CONNECTION_TIMEOUT_IN_SECONDS; +import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.CUSTOM_SIGNALS; import static com.google.firebase.remoteconfig.internal.ConfigFetchHandler.DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.content.SharedPreferences; +import android.util.Log; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -31,14 +34,20 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; import java.lang.annotation.Retention; import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import org.json.JSONException; +import org.json.JSONObject; /** - * Client for handling Firebase Remote Config (FRC) metadata that is saved to disk and persisted - * across App life cycles. + * Client for handling Firebase Remote Config (FRC) metadata and custom signals that are saved to + * disk and persisted across App life cycles. * * @author Miraziz Yusupov */ -public class ConfigMetadataClient { +public class ConfigSharedPrefsClient { @Retention(SOURCE) @IntDef({ LAST_FETCH_STATUS_SUCCESS, @@ -75,65 +84,75 @@ public class ConfigMetadataClient { private static final String REALTIME_BACKOFF_END_TIME_IN_MILLIS_KEY = "realtime_backoff_end_time_in_millis"; - private final SharedPreferences frcMetadata; + /** Constants for custom signal limits.*/ + private static final int CUSTOM_SIGNALS_MAX_KEY_LENGTH = 250; + + private static final int CUSTOM_SIGNALS_MAX_STRING_VALUE_LENGTH = 500; + + private static final int CUSTOM_SIGNALS_MAX_COUNT = 100; + + private final SharedPreferences frcSharedPrefs; private final Object frcInfoLock; private final Object backoffMetadataLock; private final Object realtimeBackoffMetadataLock; + private final Object customSignalsLock; - public ConfigMetadataClient(SharedPreferences frcMetadata) { - this.frcMetadata = frcMetadata; + public ConfigSharedPrefsClient(SharedPreferences frcSharedPrefs) { + this.frcSharedPrefs = frcSharedPrefs; this.frcInfoLock = new Object(); this.backoffMetadataLock = new Object(); this.realtimeBackoffMetadataLock = new Object(); + this.customSignalsLock = new Object(); } public long getFetchTimeoutInSeconds() { - return frcMetadata.getLong(FETCH_TIMEOUT_IN_SECONDS_KEY, CONNECTION_TIMEOUT_IN_SECONDS); + return frcSharedPrefs.getLong(FETCH_TIMEOUT_IN_SECONDS_KEY, CONNECTION_TIMEOUT_IN_SECONDS); } public long getMinimumFetchIntervalInSeconds() { - return frcMetadata.getLong( + return frcSharedPrefs.getLong( MINIMUM_FETCH_INTERVAL_IN_SECONDS_KEY, DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS); } @LastFetchStatus int getLastFetchStatus() { - return frcMetadata.getInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_NO_FETCH_YET); + return frcSharedPrefs.getInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_NO_FETCH_YET); } Date getLastSuccessfulFetchTime() { return new Date( - frcMetadata.getLong( + frcSharedPrefs.getLong( LAST_SUCCESSFUL_FETCH_TIME_IN_MILLIS_KEY, LAST_FETCH_TIME_IN_MILLIS_NO_FETCH_YET)); } @Nullable String getLastFetchETag() { - return frcMetadata.getString(LAST_FETCH_ETAG_KEY, null); + return frcSharedPrefs.getString(LAST_FETCH_ETAG_KEY, null); } long getLastTemplateVersion() { - return frcMetadata.getLong(LAST_TEMPLATE_VERSION, 0); + return frcSharedPrefs.getLong(LAST_TEMPLATE_VERSION, 0); } public FirebaseRemoteConfigInfo getInfo() { // A lock is used here to prevent the setters in this class from changing the state of - // frcMetadata during a getInfo call. + // frcSharedPrefs during a getInfo call. synchronized (frcInfoLock) { long lastSuccessfulFetchTimeInMillis = - frcMetadata.getLong( + frcSharedPrefs.getLong( LAST_SUCCESSFUL_FETCH_TIME_IN_MILLIS_KEY, LAST_FETCH_TIME_IN_MILLIS_NO_FETCH_YET); @LastFetchStatus int lastFetchStatus = - frcMetadata.getInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_NO_FETCH_YET); + frcSharedPrefs.getInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_NO_FETCH_YET); FirebaseRemoteConfigSettings settings = new FirebaseRemoteConfigSettings.Builder() .setFetchTimeoutInSeconds( - frcMetadata.getLong(FETCH_TIMEOUT_IN_SECONDS_KEY, CONNECTION_TIMEOUT_IN_SECONDS)) + frcSharedPrefs.getLong( + FETCH_TIMEOUT_IN_SECONDS_KEY, CONNECTION_TIMEOUT_IN_SECONDS)) .setMinimumFetchIntervalInSeconds( - frcMetadata.getLong( + frcSharedPrefs.getLong( MINIMUM_FETCH_INTERVAL_IN_SECONDS_KEY, DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS)) .build(); @@ -147,14 +166,14 @@ public FirebaseRemoteConfigInfo getInfo() { } /** - * Clears all metadata values from memory and disk. + * Clears all metadata and custom signals values from memory and disk. * *

The method is blocking and returns only when the values in disk are also cleared. */ @WorkerThread public void clear() { synchronized (frcInfoLock) { - frcMetadata.edit().clear().commit(); + frcSharedPrefs.edit().clear().commit(); } } @@ -167,7 +186,7 @@ public void clear() { @WorkerThread public void setConfigSettings(FirebaseRemoteConfigSettings settings) { synchronized (frcInfoLock) { - frcMetadata + frcSharedPrefs .edit() .putLong(FETCH_TIMEOUT_IN_SECONDS_KEY, settings.getFetchTimeoutInSeconds()) .putLong( @@ -184,7 +203,7 @@ public void setConfigSettings(FirebaseRemoteConfigSettings settings) { */ public void setConfigSettingsWithoutWaitingOnDiskWrite(FirebaseRemoteConfigSettings settings) { synchronized (frcInfoLock) { - frcMetadata + frcSharedPrefs .edit() .putLong(FETCH_TIMEOUT_IN_SECONDS_KEY, settings.getFetchTimeoutInSeconds()) .putLong( @@ -195,7 +214,7 @@ public void setConfigSettingsWithoutWaitingOnDiskWrite(FirebaseRemoteConfigSetti void updateLastFetchAsSuccessfulAt(Date fetchTime) { synchronized (frcInfoLock) { - frcMetadata + frcSharedPrefs .edit() .putInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_SUCCESS) .putLong(LAST_SUCCESSFUL_FETCH_TIME_IN_MILLIS_KEY, fetchTime.getTime()) @@ -205,25 +224,25 @@ void updateLastFetchAsSuccessfulAt(Date fetchTime) { void updateLastFetchAsFailed() { synchronized (frcInfoLock) { - frcMetadata.edit().putInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_FAILURE).apply(); + frcSharedPrefs.edit().putInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_FAILURE).apply(); } } void updateLastFetchAsThrottled() { synchronized (frcInfoLock) { - frcMetadata.edit().putInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_THROTTLED).apply(); + frcSharedPrefs.edit().putInt(LAST_FETCH_STATUS_KEY, LAST_FETCH_STATUS_THROTTLED).apply(); } } void setLastFetchETag(String eTag) { synchronized (frcInfoLock) { - frcMetadata.edit().putString(LAST_FETCH_ETAG_KEY, eTag).apply(); + frcSharedPrefs.edit().putString(LAST_FETCH_ETAG_KEY, eTag).apply(); } } void setLastTemplateVersion(long templateVersion) { synchronized (frcInfoLock) { - frcMetadata.edit().putLong(LAST_TEMPLATE_VERSION, templateVersion).apply(); + frcSharedPrefs.edit().putLong(LAST_TEMPLATE_VERSION, templateVersion).apply(); } } @@ -234,14 +253,15 @@ void setLastTemplateVersion(long templateVersion) { BackoffMetadata getBackoffMetadata() { synchronized (backoffMetadataLock) { return new BackoffMetadata( - frcMetadata.getInt(NUM_FAILED_FETCHES_KEY, NO_FAILED_FETCHES), - new Date(frcMetadata.getLong(BACKOFF_END_TIME_IN_MILLIS_KEY, NO_BACKOFF_TIME_IN_MILLIS))); + frcSharedPrefs.getInt(NUM_FAILED_FETCHES_KEY, NO_FAILED_FETCHES), + new Date( + frcSharedPrefs.getLong(BACKOFF_END_TIME_IN_MILLIS_KEY, NO_BACKOFF_TIME_IN_MILLIS))); } } void setBackoffMetadata(int numFailedFetches, Date backoffEndTime) { synchronized (backoffMetadataLock) { - frcMetadata + frcSharedPrefs .edit() .putInt(NUM_FAILED_FETCHES_KEY, numFailedFetches) .putLong(BACKOFF_END_TIME_IN_MILLIS_KEY, backoffEndTime.getTime()) @@ -249,6 +269,77 @@ void setBackoffMetadata(int numFailedFetches, Date backoffEndTime) { } } + public void setCustomSignals(Map newCustomSignals) { + synchronized (customSignalsLock) { + // Retrieve existing custom signals + Map existingCustomSignals = getCustomSignals(); + // Tracks whether the custom signals have been modified. + boolean modified = false; + + for (Map.Entry entry : newCustomSignals.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + // Validate key and value length + if (key.length() > CUSTOM_SIGNALS_MAX_KEY_LENGTH + || (value != null && value.length() > CUSTOM_SIGNALS_MAX_STRING_VALUE_LENGTH)) { + Log.w( + TAG, + String.format( + "Invalid custom signal: Custom signal keys must be %d characters or less, and values must be %d characters or less.", + CUSTOM_SIGNALS_MAX_KEY_LENGTH, CUSTOM_SIGNALS_MAX_STRING_VALUE_LENGTH)); + return; + } + + // Merge new signals with existing ones, overwriting existing keys. + // Also, remove entries where the new value is null. + if (value != null) { + modified |= !Objects.equals(existingCustomSignals.put(key, value), value); + } else { + modified |= existingCustomSignals.remove(key) != null; + } + } + + // Check if the map has actually changed and the size limit + if (!modified) { + return; + } + if (existingCustomSignals.size() > CUSTOM_SIGNALS_MAX_COUNT) { + Log.w( + TAG, + String.format( + "Invalid custom signal: Too many custom signals provided. The maximum allowed is %d.", + CUSTOM_SIGNALS_MAX_COUNT)); + return; + } + + frcSharedPrefs + .edit() + .putString(CUSTOM_SIGNALS, new JSONObject(existingCustomSignals).toString()) + .commit(); + + // Log the keys of the updated custom signals. + Log.d(TAG, "Keys of updated custom signals: " + getCustomSignals().keySet()); + } + } + + public Map getCustomSignals() { + String jsonString = frcSharedPrefs.getString(CUSTOM_SIGNALS, "{}"); + try { + JSONObject existingCustomSignalsJson = new JSONObject(jsonString); + Map custom_signals = new HashMap<>(); + Iterator keys = existingCustomSignalsJson.keys(); + while (keys.hasNext()) { + String key = keys.next(); + String value = existingCustomSignalsJson.optString(key); + custom_signals.put(key, value); + } + return custom_signals; + } catch (JSONException e) { + return new HashMap<>(); + } + } + void resetBackoff() { setBackoffMetadata(NO_FAILED_FETCHES, NO_BACKOFF_TIME); } @@ -286,16 +377,16 @@ Date getBackoffEndTime() { public RealtimeBackoffMetadata getRealtimeBackoffMetadata() { synchronized (realtimeBackoffMetadataLock) { return new RealtimeBackoffMetadata( - frcMetadata.getInt(NUM_FAILED_REALTIME_STREAMS_KEY, NO_FAILED_REALTIME_STREAMS), + frcSharedPrefs.getInt(NUM_FAILED_REALTIME_STREAMS_KEY, NO_FAILED_REALTIME_STREAMS), new Date( - frcMetadata.getLong( + frcSharedPrefs.getLong( REALTIME_BACKOFF_END_TIME_IN_MILLIS_KEY, NO_BACKOFF_TIME_IN_MILLIS))); } } void setRealtimeBackoffMetadata(int numFailedStreams, Date backoffEndTime) { synchronized (realtimeBackoffMetadataLock) { - frcMetadata + frcSharedPrefs .edit() .putInt(NUM_FAILED_REALTIME_STREAMS_KEY, numFailedStreams) .putLong(REALTIME_BACKOFF_END_TIME_IN_MILLIS_KEY, backoffEndTime.getTime()) diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/FirebaseRemoteConfigInfoImpl.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/FirebaseRemoteConfigInfoImpl.java index 15f0bb00028..4a16f7bf4e9 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/FirebaseRemoteConfigInfoImpl.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/FirebaseRemoteConfigInfoImpl.java @@ -16,7 +16,7 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo; import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient.LastFetchStatus; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.LastFetchStatus; /** * Impl class for FirebaseRemoteConfigInfo. diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/CustomSignalsTest.java b/firebase-config/src/test/java/com/google/firebase/remoteconfig/CustomSignalsTest.java new file mode 100644 index 00000000000..cf97e17226e --- /dev/null +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/CustomSignalsTest.java @@ -0,0 +1,86 @@ +// Copyright 2024 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.remoteconfig; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; + +/** Unit tests for the {@link CustomSignals}.*/ +public class CustomSignalsTest { + @Test + public void testCustomSignals_builderPutString() { + CustomSignals customSignals = + new CustomSignals.Builder().put("key1", "value1").put("key2", "value2").build(); + Map expectedSignals = ImmutableMap.of("key1", "value1", "key2", "value2"); + assertThat(customSignals.customSignals).isEqualTo(expectedSignals); + } + + @Test + public void testCustomSignals_builderPutLong() { + CustomSignals customSignals = + new CustomSignals.Builder().put("key1", 123L).put("key2", 456L).build(); + Map expectedSignals = ImmutableMap.of("key1", "123", "key2", "456"); + assertThat(customSignals.customSignals).isEqualTo(expectedSignals); + } + + @Test + public void testCustomSignals_builderPutDouble() { + CustomSignals customSignals = + new CustomSignals.Builder().put("key1", 12.34).put("key2", 56.78).build(); + Map expectedSignals = ImmutableMap.of("key1", "12.34", "key2", "56.78"); + assertThat(customSignals.customSignals).isEqualTo(expectedSignals); + } + + @Test + public void testCustomSignals_builderPutNullValue() { + CustomSignals customSignals = new CustomSignals.Builder().put("key1", null).build(); + Map expectedSignals = new HashMap<>(); + expectedSignals.put("key1", null); + assertThat(customSignals.customSignals).isEqualTo(expectedSignals); + } + + @Test + public void testCustomSignals_builderPutDuplicateKeys() { + CustomSignals customSignals = + new CustomSignals.Builder() + .put("key1", "value1") + .put("key1", "value2") + .put("key1", "value3") + .build(); + Map expectedSignals = ImmutableMap.of("key1", "value3"); + assertThat(customSignals.customSignals).isEqualTo(expectedSignals); + } + + @Test + public void testCustomSignals_builderPutMixedTypes() { + CustomSignals customSignals = + new CustomSignals.Builder() + .put("key1", "value1") + .put("key2", 123L) + .put("key3", 45.67) + .put("key4", null) + .build(); + Map expectedSignals = new HashMap<>(); + expectedSignals.put("key1", "value1"); + expectedSignals.put("key2", "123"); + expectedSignals.put("key3", "45.67"); + expectedSignals.put("key4", null); + assertThat(customSignals.customSignals).isEqualTo(expectedSignals); + } +} diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java b/firebase-config/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java index 81f292a4c2b..fffc439dc2b 100644 --- a/firebase-config/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java @@ -72,9 +72,9 @@ import com.google.firebase.remoteconfig.internal.ConfigFetchHandler; import com.google.firebase.remoteconfig.internal.ConfigFetchHandler.FetchResponse; import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient; import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler; import com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient; import com.google.firebase.remoteconfig.internal.FakeHttpURLConnection; import com.google.firebase.remoteconfig.internal.Personalization; import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler; @@ -155,7 +155,7 @@ public final class FirebaseRemoteConfigTest { @Mock private ConfigCacheClient mockDefaultsCache; @Mock private ConfigFetchHandler mockFetchHandler; @Mock private ConfigGetParameterHandler mockGetHandler; - @Mock private ConfigMetadataClient metadataClient; + @Mock private ConfigSharedPrefsClient sharedPrefsClient; @Mock private ConfigRealtimeHandler mockConfigRealtimeHandler; @Mock private ConfigAutoFetch mockConfigAutoFetch; @@ -192,7 +192,7 @@ public final class FirebaseRemoteConfigTest { private ConfigContainer realtimeFetchedContainer; private ConfigAutoFetch configAutoFetch; private ConfigRealtimeHttpClient configRealtimeHttpClient; - private ConfigMetadataClient realtimeMetadataClient; + private ConfigSharedPrefsClient realtimeSharedPrefsClient; private FetchResponse firstFetchedContainerResponse; @@ -240,7 +240,7 @@ public void setUp() throws Exception { mockDefaultsCache, mockFetchHandler, mockGetHandler, - metadataClient, + sharedPrefsClient, mockConfigRealtimeHandler, mockRolloutsStateSubscriptionsHandler); @@ -259,7 +259,7 @@ public void setUp() throws Exception { mockFireperfDefaultsCache, mockFireperfFetchHandler, mockFireperfGetHandler, - RemoteConfigComponent.getMetadataClient(context, APP_ID, FIREPERF_NAMESPACE), + RemoteConfigComponent.getSharedPrefsClient(context, APP_ID, FIREPERF_NAMESPACE), mockRolloutsStateSubscriptionsHandler); personalizationFrc = @@ -276,7 +276,8 @@ public void setUp() throws Exception { mockDefaultsCache, mockFetchHandler, parameterHandler, - RemoteConfigComponent.getMetadataClient(context, APP_ID, PERSONALIZATION_NAMESPACE), + RemoteConfigComponent.getSharedPrefsClient( + context, APP_ID, PERSONALIZATION_NAMESPACE), mockRolloutsStateSubscriptionsHandler); firstFetchedContainer = @@ -349,8 +350,9 @@ public void onError(@NonNull FirebaseRemoteConfigException error) { listeners, mockRetryListener, scheduledExecutorService); - realtimeMetadataClient = - new ConfigMetadataClient(context.getSharedPreferences("test_file", Context.MODE_PRIVATE)); + realtimeSharedPrefsClient = + new ConfigSharedPrefsClient( + context.getSharedPreferences("test_file", Context.MODE_PRIVATE)); configRealtimeHttpClient = new ConfigRealtimeHttpClient( firebaseApp, @@ -360,7 +362,7 @@ public void onError(@NonNull FirebaseRemoteConfigException error) { context, "firebase", listeners, - realtimeMetadataClient, + realtimeSharedPrefsClient, scheduledExecutorService); } @@ -1024,7 +1026,7 @@ public void getLong_fireperfNamespace_keyExists_returnsRemoteValue() { @Test public void getInfo_returnsInfo() { - when(metadataClient.getInfo()).thenReturn(mockFrcInfo); + when(sharedPrefsClient.getInfo()).thenReturn(mockFrcInfo); long fetchTimeInMillis = 100L; int lastFetchStatus = LAST_FETCH_STATUS_THROTTLED; @@ -1071,11 +1073,11 @@ public void clear_hasSettings_clearsEverything() { verify(mockActivatedCache).clear(); verify(mockFetchedCache).clear(); verify(mockDefaultsCache).clear(); - verify(metadataClient).clear(); + verify(sharedPrefsClient).clear(); } @Test - public void setConfigSettingsAsync_updatesMetadata() { + public void setConfigSettingsAsync_updatesSharedPrefs() { long fetchTimeout = 13L; long minimumFetchInterval = 666L; FirebaseRemoteConfigSettings frcSettings = @@ -1087,7 +1089,7 @@ public void setConfigSettingsAsync_updatesMetadata() { Task setterTask = frc.setConfigSettingsAsync(frcSettings); assertThat(setterTask.isSuccessful()).isTrue(); - verify(metadataClient).setConfigSettings(frcSettings); + verify(sharedPrefsClient).setConfigSettings(frcSettings); } @Test @@ -1658,6 +1660,22 @@ public void realtimeRequest_setRequestParams_succeedsWithCorrectParams() throws assertThat(fakeConnection.getRequestMethod()).isEqualTo("POST"); } + @Test + public void setCustomSignals_succeeds_and_calls_sharedPrefsClient() { + CustomSignals customSignals = + new CustomSignals.Builder() + .put("key1", "value1") + .put("key2", 123L) + .put("key3", 12.34) + .put("key4", null) + .build(); + + Task setterTask = frc.setCustomSignals(customSignals); + + assertThat(setterTask.isSuccessful()).isTrue(); + verify(sharedPrefsClient).setCustomSignals(customSignals.customSignals); + } + private static void loadCacheWithConfig( ConfigCacheClient cacheClient, ConfigContainer container) { when(cacheClient.getBlocking()).thenReturn(container); diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigComponentTest.java b/firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigComponentTest.java index c6869267a4d..e0ee9220855 100644 --- a/firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigComponentTest.java +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigComponentTest.java @@ -39,7 +39,7 @@ import com.google.firebase.remoteconfig.internal.ConfigFetchHandler; import com.google.firebase.remoteconfig.internal.ConfigFetchHttpClient; import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient; import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler; import com.google.firebase.remoteconfig.interop.rollouts.RolloutsStateSubscriber; import java.util.Date; @@ -73,7 +73,7 @@ public class RemoteConfigComponentTest { @Mock private ConfigCacheClient mockDefaultsCache; @Mock private ConfigFetchHandler mockFetchHandler; @Mock private ConfigGetParameterHandler mockGetParameterHandler; - @Mock private ConfigMetadataClient mockMetadataClient; + @Mock private ConfigSharedPrefsClient mockSharedPrefsClient; @Mock private RolloutsStateSubscriptionsHandler mockRolloutsStateSubscriptionsHandler; @Mock private RolloutsStateSubscriber mockRolloutsStateSubscriber; @@ -82,7 +82,7 @@ public class RemoteConfigComponentTest { private ExecutorService directExecutor; private ScheduledExecutorService scheduledExecutorService; private FirebaseApp defaultApp; - private ConfigMetadataClient metadataClient; + private ConfigSharedPrefsClient sharedPrefsClient; @Before public void setUp() { @@ -94,7 +94,8 @@ public void setUp() { defaultApp = initializeFirebaseApp(context); - metadataClient = RemoteConfigComponent.getMetadataClient(context, APP_ID, "personalization"); + sharedPrefsClient = + RemoteConfigComponent.getSharedPrefsClient(context, APP_ID, "personalization"); when(mockFirebaseApp.getOptions()) .thenReturn(new FirebaseOptions.Builder().setApplicationId(APP_ID).build()); @@ -106,7 +107,7 @@ public void setUp() { public void frc2p_doesNotCallAbt() throws Exception { FirebaseRemoteConfig fireperfFrc = - getFrcInstanceFromComponentWithMetadataClient( + getFrcInstanceFromComponentWithSharedPrefsClient( getNewFrcComponent(), /* namespace= */ "fireperf"); loadConfigsWithExperimentsForActivate(); @@ -123,7 +124,7 @@ public void frcNonMainFirebaseApp_doesNotCallAbt() throws Exception { when(mockFirebaseApp.getName()).thenReturn("secondary"); FirebaseRemoteConfig frc = - getFrcInstanceFromComponentWithMetadataClient( + getFrcInstanceFromComponentWithSharedPrefsClient( getNewFrcComponentWithoutLoadingDefault(), DEFAULT_NAMESPACE); loadConfigsWithExperimentsForActivate(); @@ -139,7 +140,7 @@ public void getFetchHandler_nonMainFirebaseApp_doesNotUseAnalytics() { ConfigFetchHandler fetchHandler = getNewFrcComponent() - .getFetchHandler(DEFAULT_NAMESPACE, mockFetchedCache, mockMetadataClient); + .getFetchHandler(DEFAULT_NAMESPACE, mockFetchedCache, mockSharedPrefsClient); assertThat(fetchHandler.getAnalyticsConnector().get()).isNull(); } @@ -149,10 +150,12 @@ public void getFetchHandler_nonMainFirebaseApp_doesNotUseAnalytics() { getFrcBackendApiClient_fetchTimeoutIsNotSet_buildsConfigFetchHttpClientWithDefaultConnectionTimeout() { RemoteConfigComponent frcComponent = defaultApp.get(RemoteConfigComponent.class); - when(mockMetadataClient.getFetchTimeoutInSeconds()).thenReturn(CONNECTION_TIMEOUT_IN_SECONDS); + when(mockSharedPrefsClient.getFetchTimeoutInSeconds()) + .thenReturn(CONNECTION_TIMEOUT_IN_SECONDS); ConfigFetchHttpClient frcBackendClient = - frcComponent.getFrcBackendApiClient(DUMMY_API_KEY, DEFAULT_NAMESPACE, mockMetadataClient); + frcComponent.getFrcBackendApiClient( + DUMMY_API_KEY, DEFAULT_NAMESPACE, mockSharedPrefsClient); int actualConnectTimeout = getConnectTimeoutInSeconds(frcBackendClient); int actualReadTimeout = getReadTimeoutInSeconds(frcBackendClient); @@ -167,11 +170,12 @@ public void getFetchHandler_nonMainFirebaseApp_doesNotUseAnalytics() { RemoteConfigComponent frcComponent = defaultApp.get(RemoteConfigComponent.class); long customConnectionTimeoutInSeconds = 2 * CONNECTION_TIMEOUT_IN_SECONDS; - when(mockMetadataClient.getFetchTimeoutInSeconds()) + when(mockSharedPrefsClient.getFetchTimeoutInSeconds()) .thenReturn(customConnectionTimeoutInSeconds); ConfigFetchHttpClient frcBackendClient = - frcComponent.getFrcBackendApiClient(DUMMY_API_KEY, DEFAULT_NAMESPACE, mockMetadataClient); + frcComponent.getFrcBackendApiClient( + DUMMY_API_KEY, DEFAULT_NAMESPACE, mockSharedPrefsClient); int actualConnectTimeout = getConnectTimeoutInSeconds(frcBackendClient); int actualReadTimeout = getReadTimeoutInSeconds(frcBackendClient); @@ -181,9 +185,9 @@ public void getFetchHandler_nonMainFirebaseApp_doesNotUseAnalytics() { @Test public void registerRolloutsStateSubscriber_firebaseNamespace_callsSubscriptionHandler() { - // Mock metadata client response since Realtime handler can't be mocked here. - when(mockMetadataClient.getRealtimeBackoffMetadata()) - .thenReturn(new ConfigMetadataClient.RealtimeBackoffMetadata(0, new Date())); + // Mock shared preference client response since Realtime handler can't be mocked here. + when(mockSharedPrefsClient.getRealtimeBackoffMetadata()) + .thenReturn(new ConfigSharedPrefsClient.RealtimeBackoffMetadata(0, new Date())); RemoteConfigComponent frcComponent = getNewFrcComponentWithoutLoadingDefault(); FirebaseRemoteConfig instance = getFrcInstanceFromComponent(frcComponent, DEFAULT_NAMESPACE); @@ -216,7 +220,7 @@ private RemoteConfigComponent getNewFrcComponentWithoutLoadingDefault() { /* loadGetDefault= */ false); } - private FirebaseRemoteConfig getFrcInstanceFromComponentWithMetadataClient( + private FirebaseRemoteConfig getFrcInstanceFromComponentWithSharedPrefsClient( RemoteConfigComponent frcComponent, String namespace) { return frcComponent.get( mockFirebaseApp, @@ -229,7 +233,7 @@ private FirebaseRemoteConfig getFrcInstanceFromComponentWithMetadataClient( mockDefaultsCache, mockFetchHandler, mockGetParameterHandler, - metadataClient, + sharedPrefsClient, mockRolloutsStateSubscriptionsHandler); } @@ -246,7 +250,7 @@ private FirebaseRemoteConfig getFrcInstanceFromComponent( mockDefaultsCache, mockFetchHandler, mockGetParameterHandler, - mockMetadataClient, + mockSharedPrefsClient, mockRolloutsStateSubscriptionsHandler); } diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigTests.kt b/firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigTests.kt index 7624ee3827d..81cb8115661 100644 --- a/firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigTests.kt +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigTests.kt @@ -29,8 +29,8 @@ import com.google.firebase.platforminfo.UserAgentPublisher import com.google.firebase.remoteconfig.internal.ConfigCacheClient import com.google.firebase.remoteconfig.internal.ConfigFetchHandler import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler import org.junit.After import org.junit.Before @@ -139,7 +139,7 @@ class ConfigTests : BaseTestCase() { defaultConfigsCache = mock(ConfigCacheClient::class.java), fetchHandler = mock(ConfigFetchHandler::class.java), getHandler = mockGetHandler, - frcMetadata = mock(ConfigMetadataClient::class.java), + frcSharedPrefs = mock(ConfigSharedPrefsClient::class.java), realtimeHandler = mock(ConfigRealtimeHandler::class.java), rolloutsStateSubscriptionsHandler = mock(RolloutsStateSubscriptionsHandler::class.java) ) @@ -147,6 +147,19 @@ class ConfigTests : BaseTestCase() { `when`(mockGetHandler.getValue("KEY")).thenReturn(StringRemoteConfigValue("non default value")) assertThat(remoteConfig["KEY"].asString()).isEqualTo("non default value") } + + @Test + fun `Custom Signals builder support multiple types`() { + val customSignals = customSignals { + put("key1", "value1") + put("key2", 123L) + put("key3", 45.67) + put("key4", null) + } + val expectedSignals = + mapOf("key1" to "value1", "key2" to "123", "key3" to "45.67", "key4" to null) + assertThat(customSignals.customSignals).isEqualTo(expectedSignals) + } } @RunWith(RobolectricTestRunner::class) diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/TestConstructorUtil.kt b/firebase-config/src/test/java/com/google/firebase/remoteconfig/TestConstructorUtil.kt index e6167b5ab09..4c153bff1e3 100644 --- a/firebase-config/src/test/java/com/google/firebase/remoteconfig/TestConstructorUtil.kt +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/TestConstructorUtil.kt @@ -23,8 +23,8 @@ import com.google.firebase.installations.FirebaseInstallationsApi import com.google.firebase.remoteconfig.internal.ConfigCacheClient import com.google.firebase.remoteconfig.internal.ConfigFetchHandler import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler import java.util.concurrent.Executor @@ -41,7 +41,7 @@ fun createRemoteConfig( defaultConfigsCache: ConfigCacheClient, fetchHandler: ConfigFetchHandler, getHandler: ConfigGetParameterHandler, - frcMetadata: ConfigMetadataClient, + frcSharedPrefs: ConfigSharedPrefsClient, realtimeHandler: ConfigRealtimeHandler, rolloutsStateSubscriptionsHandler: RolloutsStateSubscriptionsHandler ): FirebaseRemoteConfig { @@ -56,7 +56,7 @@ fun createRemoteConfig( defaultConfigsCache, fetchHandler, getHandler, - frcMetadata, + frcSharedPrefs, realtimeHandler, rolloutsStateSubscriptionsHandler ) diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandlerTest.java b/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandlerTest.java index ed2781710fd..740a00191ec 100644 --- a/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandlerTest.java +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandlerTest.java @@ -29,9 +29,9 @@ import static com.google.firebase.remoteconfig.internal.ConfigFetchHandler.DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS; import static com.google.firebase.remoteconfig.internal.ConfigFetchHandler.FIRST_OPEN_TIME_KEY; import static com.google.firebase.remoteconfig.internal.ConfigFetchHandler.HTTP_TOO_MANY_REQUESTS; -import static com.google.firebase.remoteconfig.internal.ConfigMetadataClient.LAST_FETCH_TIME_NO_FETCH_YET; -import static com.google.firebase.remoteconfig.internal.ConfigMetadataClient.NO_BACKOFF_TIME; -import static com.google.firebase.remoteconfig.internal.ConfigMetadataClient.NO_FAILED_FETCHES; +import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.LAST_FETCH_TIME_NO_FETCH_YET; +import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.NO_BACKOFF_TIME; +import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.NO_FAILED_FETCHES; import static com.google.firebase.remoteconfig.testutil.Assert.assertThrows; import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; @@ -70,7 +70,7 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigServerException; import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; import com.google.firebase.remoteconfig.internal.ConfigFetchHandler.FetchResponse; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient.BackoffMetadata; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.BackoffMetadata; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; @@ -127,7 +127,7 @@ public class ConfigFetchHandlerTest { private Context context; @Mock private FirebaseInstallationsApi mockFirebaseInstallations; - private ConfigMetadataClient metadataClient; + private ConfigSharedPrefsClient sharedPrefsClient; private ConfigFetchHandler fetchHandler; @@ -142,8 +142,9 @@ public void setUp() throws Exception { directExecutor = MoreExecutors.directExecutor(); context = ApplicationProvider.getApplicationContext(); mockClock = new MockClock(0L); - metadataClient = - new ConfigMetadataClient(context.getSharedPreferences("test_file", Context.MODE_PRIVATE)); + sharedPrefsClient = + new ConfigSharedPrefsClient( + context.getSharedPreferences("test_file", Context.MODE_PRIVATE)); loadBackendApiClient(); loadInstallationIdAndAuthToken(); @@ -201,7 +202,8 @@ public void fetch_firstFetch_includesInstallationAuthToken() throws Exception { /* lastFetchETag= */ any(), /* customHeaders= */ any(), /* firstOpenTime= */ any(), - /* currentTime= */ any()); + /* currentTime= */ any(), + /* customSignals= */ any()); } @Test @@ -400,7 +402,8 @@ public void fetch_gettingFetchCacheFails_doesNotThrowException() throws Exceptio @Test public void fetch_fetchBackendCallFails_taskThrowsException() throws Exception { - when(mockBackendFetchApiClient.fetch(any(), any(), any(), any(), any(), any(), any(), any())) + when(mockBackendFetchApiClient.fetch( + any(), any(), any(), any(), any(), any(), any(), any(), any())) .thenThrow( new FirebaseRemoteConfigClientException("Fetch failed due to an unexpected error.")); @@ -549,7 +552,7 @@ public void fetch_getsMultipleFailedResponsesFromServer_resetsBackoffAfterSucces assertWithMessage("Fetch() failed!").that(fetchTask.isSuccessful()).isTrue(); - BackoffMetadata backoffMetadata = metadataClient.getBackoffMetadata(); + BackoffMetadata backoffMetadata = sharedPrefsClient.getBackoffMetadata(); assertThat(backoffMetadata.getNumFailedFetches()).isEqualTo(NO_FAILED_FETCHES); assertThat(backoffMetadata.getBackoffEndTime()).isEqualTo(NO_BACKOFF_TIME); } @@ -706,8 +709,9 @@ public void fetch_firstAndOnlyFetchFails_metadataFailStatusAndNoFetchYetTime() t fetchHandler.fetch(); - assertThat(metadataClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_FAILURE); - assertThat(metadataClient.getLastSuccessfulFetchTime()).isEqualTo(LAST_FETCH_TIME_NO_FETCH_YET); + assertThat(sharedPrefsClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_FAILURE); + assertThat(sharedPrefsClient.getLastSuccessfulFetchTime()) + .isEqualTo(LAST_FETCH_TIME_NO_FETCH_YET); } @Test @@ -716,8 +720,8 @@ public void fetch_fetchSucceeds_metadataSuccessStatusAndFetchTimeUpdated() throw fetchHandler.fetch(); - assertThat(metadataClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_SUCCESS); - assertThat(metadataClient.getLastSuccessfulFetchTime()) + assertThat(sharedPrefsClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_SUCCESS); + assertThat(sharedPrefsClient.getLastSuccessfulFetchTime()) .isEqualTo(firstFetchedContainer.getFetchTime()); } @@ -731,8 +735,8 @@ public void fetch_firstFetchSucceedsSecondFetchFails_failStatusAndFirstFetchTime fetchHandler.fetch(/* minimumFetchIntervalInSeconds= */ 0); - assertThat(metadataClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_FAILURE); - assertThat(metadataClient.getLastSuccessfulFetchTime()) + assertThat(sharedPrefsClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_FAILURE); + assertThat(sharedPrefsClient.getLastSuccessfulFetchTime()) .isEqualTo(firstFetchedContainer.getFetchTime()); } @@ -745,8 +749,8 @@ public void getInfo_twoFetchesSucceed_successStatusAndSecondFetchTime() throws E fetchHandler.fetch(/* minimumFetchIntervalInSeconds= */ 0); - assertThat(metadataClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_SUCCESS); - assertThat(metadataClient.getLastSuccessfulFetchTime()) + assertThat(sharedPrefsClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_SUCCESS); + assertThat(sharedPrefsClient.getLastSuccessfulFetchTime()) .isEqualTo(secondFetchedContainer.getFetchTime()); } @@ -759,11 +763,35 @@ public void getInfo_hitsThrottleLimit_throttledStatus() throws Exception { fetchHandler.fetch(/* minimumFetchIntervalInSeconds= */ 0); - assertThat(metadataClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_THROTTLED); - assertThat(metadataClient.getLastSuccessfulFetchTime()) + assertThat(sharedPrefsClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_THROTTLED); + assertThat(sharedPrefsClient.getLastSuccessfulFetchTime()) .isEqualTo(firstFetchedContainer.getFetchTime()); } + @Test + public void fetch_usesLatestCustomSignals() throws Exception { + Map customSignals = + ImmutableMap.of( + "subscription", "premium", + "age", "20"); + sharedPrefsClient.setCustomSignals(customSignals); + fetchCallToHttpClientUpdatesClockAndReturnsConfig(firstFetchedContainer); + fetchHandler.fetch(); + + verify(mockBackendFetchApiClient) + .fetch( + any(HttpURLConnection.class), + /* instanceId= */ any(), + /* instanceIdToken= */ any(), + /* analyticsUserProperties= */ any(), + /* lastFetchETag= */ any(), + /* customHeaders= */ any(), + /* firstOpenTime= */ any(), + /* currentTime= */ any(), + /* customSignals= */ eq(sharedPrefsClient.getCustomSignals())); + assertThat(sharedPrefsClient.getCustomSignals()).isEqualTo(customSignals); + } + private ConfigFetchHandler getNewFetchHandler(AnalyticsConnector analyticsConnector) { ConfigFetchHandler fetchHandler = spy( @@ -775,7 +803,7 @@ private ConfigFetchHandler getNewFetchHandler(AnalyticsConnector analyticsConnec mockRandom, mockFetchedCache, mockBackendFetchApiClient, - metadataClient, + sharedPrefsClient, /* customHttpHeaders= */ ImmutableMap.of())); return fetchHandler; } @@ -809,7 +837,8 @@ private void setBackendResponseConfigsTo(ConfigContainer container) throws Excep /* lastFetchETag= */ any(), /* customHeaders= */ any(), /* firstOpenTime= */ any(), - /* currentTime= */ any()); + /* currentTime= */ any(), + /* customSignals= */ any()); } private void setBackendResponseToNoChange(Date date) throws Exception { @@ -821,7 +850,8 @@ private void setBackendResponseToNoChange(Date date) throws Exception { /* lastFetchETag= */ any(), /* customHeaders= */ any(), /* firstOpenTime= */ any(), - /* currentTime= */ any())) + /* currentTime= */ any(), + /* customSignals= */ any())) .thenReturn(FetchResponse.forBackendHasNoUpdates(date, firstFetchedContainer)); } @@ -836,7 +866,8 @@ private void fetchCallToBackendThrowsException(int httpErrorCode) throws Excepti /* lastFetchETag= */ any(), /* customHeaders= */ any(), /* firstOpenTime= */ any(), - /* currentTime= */ any()); + /* currentTime= */ any(), + /* customSignals= */ any()); } /** @@ -875,7 +906,8 @@ private void callFetchAssertThrottledAndAdvanceClock(int httpCode) throws Except long backoffDurationInMillis = loadAndGetNextBackoffDuration( - /* numFailedFetches= */ metadataClient.getBackoffMetadata().getNumFailedFetches() + 1); + /* numFailedFetches= */ sharedPrefsClient.getBackoffMetadata().getNumFailedFetches() + + 1); assertThrowsThrottledException(fetchHandler.fetch(/* minimumFetchIntervalInSeconds= */ 0L)); @@ -900,7 +932,7 @@ private long loadAndGetNextBackoffDuration(int numFailedFetches) { } private void setMinimumFetchIntervalInMetadata(long minimumFetchIntervalInSeconds) { - metadataClient.setConfigSettings( + sharedPrefsClient.setConfigSettings( new FirebaseRemoteConfigSettings.Builder() .setMinimumFetchIntervalInSeconds(minimumFetchIntervalInSeconds) .build()); @@ -916,7 +948,8 @@ private void verifyBackendIsCalled() throws Exception { /* lastFetchETag= */ any(), /* customHeaders= */ any(), /* firstOpenTime= */ any(), - /* currentTime= */ any()); + /* currentTime= */ any(), + /* customSignals= */ any()); } private void verifyBackendIsCalled(Map userProperties, Long firstOpenTime) @@ -930,7 +963,8 @@ private void verifyBackendIsCalled(Map userProperties, Long firs /* lastFetchETag= */ any(), /* customHeaders= */ any(), /* firstOpenTime= */ eq(firstOpenTime), - /* currentTime= */ any()); + /* currentTime= */ any(), + /* customSignals= */ any()); } private void verifyBackendIsNeverCalled() throws Exception { @@ -943,7 +977,8 @@ private void verifyBackendIsNeverCalled() throws Exception { /* lastFetchETag= */ any(), /* customHeaders= */ any(), /* firstOpenTime= */ any(), - /* currentTime= */ any()); + /* currentTime= */ any(), + /* customSignals= */ any()); } private void verifyETags(@Nullable String requestETag, String responseETag) throws Exception { @@ -956,8 +991,9 @@ private void verifyETags(@Nullable String requestETag, String responseETag) thro /* lastFetchETag= */ eq(requestETag), /* customHeaders= */ any(), /* firstOpenTime= */ any(), - /* currentTime= */ any()); - assertThat(metadataClient.getLastFetchETag()).isEqualTo(responseETag); + /* currentTime= */ any(), + /* customSignals= */ any()); + assertThat(sharedPrefsClient.getLastFetchETag()).isEqualTo(responseETag); } private void loadBackendApiClient() throws Exception { @@ -966,7 +1002,7 @@ private void loadBackendApiClient() throws Exception { } private void loadETags(String requestETag, String responseETag) { - metadataClient.setLastFetchETag(requestETag); + sharedPrefsClient.setLastFetchETag(requestETag); this.responseETag = responseETag; } @@ -981,7 +1017,7 @@ private void loadCacheAndClockWithConfig( when(cacheClient.getBlocking()).thenReturn(container); when(cacheClient.get()).thenReturn(Tasks.forResult(container)); mockClock.setCurrentTime(container.getFetchTime().getTime()); - metadataClient.updateLastFetchAsSuccessfulAt(container.getFetchTime()); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(container.getFetchTime()); } private static void cachePutReturnsConfig( diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClientTest.java b/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClientTest.java index edbded6f79b..ae068b4f8d2 100644 --- a/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClientTest.java +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClientTest.java @@ -344,7 +344,8 @@ private FetchResponse fetch(String eTag) throws Exception { eTag, /* customHeaders= */ ImmutableMap.of(), /* firstOpenTime= */ null, - /* currentTime= */ new Date(mockClock.currentTimeMillis())); + /* currentTime= */ new Date(mockClock.currentTimeMillis()), + /* customSignals= */ ImmutableMap.of()); } private FetchResponse fetch(String eTag, Map userProperties, Long firstOpenTime) @@ -357,7 +358,8 @@ private FetchResponse fetch(String eTag, Map userProperties, Lon eTag, /* customHeaders= */ ImmutableMap.of(), firstOpenTime, - new Date(mockClock.currentTimeMillis())); + new Date(mockClock.currentTimeMillis()), + /* customSignals= */ ImmutableMap.of()); } private FetchResponse fetch(String eTag, Map customHeaders) throws Exception { @@ -369,7 +371,8 @@ private FetchResponse fetch(String eTag, Map customHeaders) thro eTag, customHeaders, /* firstOpenTime= */ null, - new Date(mockClock.currentTimeMillis())); + new Date(mockClock.currentTimeMillis()), + /* customSignals= */ ImmutableMap.of()); } private FetchResponse fetchWithoutInstallationId() throws Exception { @@ -381,7 +384,8 @@ private FetchResponse fetchWithoutInstallationId() throws Exception { /* lastFetchETag= */ "bogus-etag", /* customHeaders= */ ImmutableMap.of(), /* firstOpenTime= */ null, - new Date(mockClock.currentTimeMillis())); + new Date(mockClock.currentTimeMillis()), + /* customSignals= */ ImmutableMap.of()); } private FetchResponse fetchWithoutInstallationAuthToken() throws Exception { @@ -393,7 +397,8 @@ private FetchResponse fetchWithoutInstallationAuthToken() throws Exception { /* lastFetchETag= */ "bogus-etag", /* customHeaders= */ ImmutableMap.of(), /* firstOpenTime= */ null, - new Date(mockClock.currentTimeMillis())); + new Date(mockClock.currentTimeMillis()), + /* customSignals= */ ImmutableMap.of()); } private void setServerResponseTo(JSONObject requestBody, String eTag) { diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigMetadataClientTest.java b/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClientTest.java similarity index 60% rename from firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigMetadataClientTest.java rename to firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClientTest.java index e2c38df4a30..2edfb171ddc 100644 --- a/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigMetadataClientTest.java +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClientTest.java @@ -21,20 +21,24 @@ import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_THROTTLED; import static com.google.firebase.remoteconfig.RemoteConfigComponent.CONNECTION_TIMEOUT_IN_SECONDS; import static com.google.firebase.remoteconfig.internal.ConfigFetchHandler.DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS; -import static com.google.firebase.remoteconfig.internal.ConfigMetadataClient.LAST_FETCH_TIME_IN_MILLIS_NO_FETCH_YET; -import static com.google.firebase.remoteconfig.internal.ConfigMetadataClient.LAST_FETCH_TIME_NO_FETCH_YET; -import static com.google.firebase.remoteconfig.internal.ConfigMetadataClient.NO_BACKOFF_TIME; -import static com.google.firebase.remoteconfig.internal.ConfigMetadataClient.NO_FAILED_FETCHES; +import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.LAST_FETCH_TIME_IN_MILLIS_NO_FETCH_YET; +import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.LAST_FETCH_TIME_NO_FETCH_YET; +import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.NO_BACKOFF_TIME; +import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.NO_FAILED_FETCHES; import android.content.Context; import android.content.SharedPreferences; import androidx.test.core.app.ApplicationProvider; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo; import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient.BackoffMetadata; -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient.LastFetchStatus; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.BackoffMetadata; +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.LastFetchStatus; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,121 +46,123 @@ import org.robolectric.annotation.Config; /** - * Unit tests for the {@link ConfigMetadataClient}. + * Unit tests for the {@link ConfigSharedPrefsClient}. * * @author Miraziz Yusupov */ @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) -public class ConfigMetadataClientTest { - private ConfigMetadataClient metadataClient; +public class ConfigSharedPrefsClientTest { + private ConfigSharedPrefsClient sharedPrefsClient; private FirebaseRemoteConfigSettings.Builder settingsBuilder; @Before public void setUp() { - SharedPreferences metadata = + SharedPreferences sharedPrefs = ApplicationProvider.getApplicationContext() .getSharedPreferences("TEST_FILE_NAME", Context.MODE_PRIVATE); - metadata.edit().clear().commit(); + sharedPrefs.edit().clear().commit(); - metadataClient = new ConfigMetadataClient(metadata); + sharedPrefsClient = new ConfigSharedPrefsClient(sharedPrefs); settingsBuilder = new FirebaseRemoteConfigSettings.Builder(); } @Test public void getFetchTimeoutInSeconds_isNotSet_returnsDefault() { - assertThat(metadataClient.getFetchTimeoutInSeconds()).isEqualTo(CONNECTION_TIMEOUT_IN_SECONDS); + assertThat(sharedPrefsClient.getFetchTimeoutInSeconds()) + .isEqualTo(CONNECTION_TIMEOUT_IN_SECONDS); } @Test public void getFetchTimeoutInSeconds_isSetTo10Seconds_returns10Seconds() { long expectedFetchTimeout = 10L; - metadataClient.setConfigSettings( + sharedPrefsClient.setConfigSettings( settingsBuilder.setFetchTimeoutInSeconds(expectedFetchTimeout).build()); - long fetchTimeout = metadataClient.getFetchTimeoutInSeconds(); + long fetchTimeout = sharedPrefsClient.getFetchTimeoutInSeconds(); assertThat(fetchTimeout).isEqualTo(expectedFetchTimeout); } @Test public void getMinimumFetchIntervalInSeconds_isNotSet_returnsDefault() { - assertThat(metadataClient.getMinimumFetchIntervalInSeconds()) + assertThat(sharedPrefsClient.getMinimumFetchIntervalInSeconds()) .isEqualTo(DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS); } @Test public void getMinimumFetchIntervalInSeconds_isSetTo10Seconds_returns10Seconds() { long expectedMinimumFetchInterval = 10L; - metadataClient.setConfigSettings( + sharedPrefsClient.setConfigSettings( settingsBuilder.setMinimumFetchIntervalInSeconds(expectedMinimumFetchInterval).build()); - long minimumFetchInterval = metadataClient.getMinimumFetchIntervalInSeconds(); + long minimumFetchInterval = sharedPrefsClient.getMinimumFetchIntervalInSeconds(); assertThat(minimumFetchInterval).isEqualTo(expectedMinimumFetchInterval); } @Test public void getLastFetchStatus_isNotSet_returnsZero() { - assertThat(metadataClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_NO_FETCH_YET); + assertThat(sharedPrefsClient.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_NO_FETCH_YET); } @Test public void getLastFetchStatus_isSetToSuccess_returnsSuccess() { - metadataClient.updateLastFetchAsSuccessfulAt(new Date(100L)); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(new Date(100L)); - @LastFetchStatus int lastFetchStatus = metadataClient.getLastFetchStatus(); + @LastFetchStatus int lastFetchStatus = sharedPrefsClient.getLastFetchStatus(); assertThat(lastFetchStatus).isEqualTo(LAST_FETCH_STATUS_SUCCESS); } @Test public void getLastSuccessfulFetchTime_isNotSet_returnsZero() { - assertThat(metadataClient.getLastSuccessfulFetchTime()).isEqualTo(LAST_FETCH_TIME_NO_FETCH_YET); + assertThat(sharedPrefsClient.getLastSuccessfulFetchTime()) + .isEqualTo(LAST_FETCH_TIME_NO_FETCH_YET); } @Test public void getLastSuccessfulFetchTime_isSet_returnsTime() { Date fetchTime = new Date(1000L); - metadataClient.updateLastFetchAsSuccessfulAt(fetchTime); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(fetchTime); - Date lastSuccessfulFetchTime = metadataClient.getLastSuccessfulFetchTime(); + Date lastSuccessfulFetchTime = sharedPrefsClient.getLastSuccessfulFetchTime(); assertThat(lastSuccessfulFetchTime).isEqualTo(fetchTime); } @Test public void getLastFetchETag_isNotSet_returnsEmptyString() { - assertThat(metadataClient.getLastFetchETag()).isNull(); + assertThat(sharedPrefsClient.getLastFetchETag()).isNull(); } @Test public void getLastFetchETag_isSet_returnsETag() { String expectedETag = "an etag"; - metadataClient.setLastFetchETag(expectedETag); + sharedPrefsClient.setLastFetchETag(expectedETag); - String eTag = metadataClient.getLastFetchETag(); + String eTag = sharedPrefsClient.getLastFetchETag(); assertThat(eTag).isEqualTo(expectedETag); } @Test public void getLastTemplateVersion_isNotSet_returnsDefault() { - assertThat(metadataClient.getLastTemplateVersion()).isEqualTo(0); + assertThat(sharedPrefsClient.getLastTemplateVersion()).isEqualTo(0); } @Test public void getLastTemplateVersion_isSet_returnsTemplateVersion() { - metadataClient.setLastTemplateVersion(1); - assertThat(metadataClient.getLastTemplateVersion()).isEqualTo(1); + sharedPrefsClient.setLastTemplateVersion(1); + assertThat(sharedPrefsClient.getLastTemplateVersion()).isEqualTo(1); } @Test public void getRealtimeBackoffMetadata_isNotSet_returnsNoFailedStreamsAndNotThrottled() { - ConfigMetadataClient.RealtimeBackoffMetadata defaultRealtimeBackoffMetadata = - metadataClient.getRealtimeBackoffMetadata(); + ConfigSharedPrefsClient.RealtimeBackoffMetadata defaultRealtimeBackoffMetadata = + sharedPrefsClient.getRealtimeBackoffMetadata(); assertThat(defaultRealtimeBackoffMetadata.getNumFailedStreams()).isEqualTo(NO_FAILED_FETCHES); assertThat(defaultRealtimeBackoffMetadata.getBackoffEndTime()).isEqualTo(NO_BACKOFF_TIME); @@ -166,10 +172,10 @@ public void getRealtimeBackoffMetadata_isNotSet_returnsNoFailedStreamsAndNotThro public void getRealtimeBackoffMetadata_hasValues_returnsValues() { int numFailedStreams = 5; Date backoffEndTime = new Date(1000L); - metadataClient.setRealtimeBackoffMetadata(numFailedStreams, backoffEndTime); + sharedPrefsClient.setRealtimeBackoffMetadata(numFailedStreams, backoffEndTime); - ConfigMetadataClient.RealtimeBackoffMetadata backoffMetadata = - metadataClient.getRealtimeBackoffMetadata(); + ConfigSharedPrefsClient.RealtimeBackoffMetadata backoffMetadata = + sharedPrefsClient.getRealtimeBackoffMetadata(); assertThat(backoffMetadata.getNumFailedStreams()).isEqualTo(numFailedStreams); assertThat(backoffMetadata.getBackoffEndTime()).isEqualTo(backoffEndTime); @@ -177,26 +183,26 @@ public void getRealtimeBackoffMetadata_hasValues_returnsValues() { @Test public void resetRealtimeBackoff_hasValues_clearsAllValues() { - metadataClient.setRealtimeBackoffMetadata( + sharedPrefsClient.setRealtimeBackoffMetadata( /* numFailedStreams= */ 5, /* backoffEndTime= */ new Date(1000L)); - ConfigMetadataClient.RealtimeBackoffMetadata realtimeBackoffMetadata = - metadataClient.getRealtimeBackoffMetadata(); + ConfigSharedPrefsClient.RealtimeBackoffMetadata realtimeBackoffMetadata = + sharedPrefsClient.getRealtimeBackoffMetadata(); Preconditions.checkArgument(realtimeBackoffMetadata.getNumFailedStreams() != NO_FAILED_FETCHES); Preconditions.checkArgument( !realtimeBackoffMetadata.getBackoffEndTime().equals(NO_BACKOFF_TIME)); - metadataClient.resetRealtimeBackoff(); + sharedPrefsClient.resetRealtimeBackoff(); - ConfigMetadataClient.RealtimeBackoffMetadata resetMetadata = - metadataClient.getRealtimeBackoffMetadata(); + ConfigSharedPrefsClient.RealtimeBackoffMetadata resetMetadata = + sharedPrefsClient.getRealtimeBackoffMetadata(); assertThat(resetMetadata.getNumFailedStreams()).isEqualTo(NO_FAILED_FETCHES); assertThat(resetMetadata.getBackoffEndTime()).isEqualTo(NO_BACKOFF_TIME); } @Test public void getBackoffMetadata_isNotSet_returnsNoFailedFetchesAndNotThrottled() { - BackoffMetadata defaultBackoffMetadata = metadataClient.getBackoffMetadata(); + BackoffMetadata defaultBackoffMetadata = sharedPrefsClient.getBackoffMetadata(); assertThat(defaultBackoffMetadata.getNumFailedFetches()).isEqualTo(NO_FAILED_FETCHES); assertThat(defaultBackoffMetadata.getBackoffEndTime()).isEqualTo(NO_BACKOFF_TIME); @@ -206,9 +212,9 @@ public void getBackoffMetadata_isNotSet_returnsNoFailedFetchesAndNotThrottled() public void getBackoffMetadata_hasValues_returnsValues() { int numFailedFetches = 5; Date backoffEndTime = new Date(1000L); - metadataClient.setBackoffMetadata(numFailedFetches, backoffEndTime); + sharedPrefsClient.setBackoffMetadata(numFailedFetches, backoffEndTime); - BackoffMetadata backoffMetadata = metadataClient.getBackoffMetadata(); + BackoffMetadata backoffMetadata = sharedPrefsClient.getBackoffMetadata(); assertThat(backoffMetadata.getNumFailedFetches()).isEqualTo(numFailedFetches); assertThat(backoffMetadata.getBackoffEndTime()).isEqualTo(backoffEndTime); @@ -216,23 +222,23 @@ public void getBackoffMetadata_hasValues_returnsValues() { @Test public void resetBackoff_hasValues_clearsAllValues() { - metadataClient.setBackoffMetadata( + sharedPrefsClient.setBackoffMetadata( /* numFailedFetches= */ 5, /* backoffEndTime= */ new Date(1000L)); - BackoffMetadata backoffMetadata = metadataClient.getBackoffMetadata(); + BackoffMetadata backoffMetadata = sharedPrefsClient.getBackoffMetadata(); Preconditions.checkArgument(backoffMetadata.getNumFailedFetches() != NO_FAILED_FETCHES); Preconditions.checkArgument(!backoffMetadata.getBackoffEndTime().equals(NO_BACKOFF_TIME)); - metadataClient.resetBackoff(); + sharedPrefsClient.resetBackoff(); - BackoffMetadata resetMetadata = metadataClient.getBackoffMetadata(); + BackoffMetadata resetMetadata = sharedPrefsClient.getBackoffMetadata(); assertThat(resetMetadata.getNumFailedFetches()).isEqualTo(NO_FAILED_FETCHES); assertThat(resetMetadata.getBackoffEndTime()).isEqualTo(NO_BACKOFF_TIME); } @Test public void getInfo_hasNoSetValues_returnsDefaults() { - FirebaseRemoteConfigInfo info = metadataClient.getInfo(); + FirebaseRemoteConfigInfo info = sharedPrefsClient.getInfo(); assertThat(info.getFetchTimeMillis()).isEqualTo(LAST_FETCH_TIME_IN_MILLIS_NO_FETCH_YET); assertThat(info.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_NO_FETCH_YET); @@ -245,18 +251,18 @@ public void getInfo_hasNoSetValues_returnsDefaults() { @Test public void getInfo_hasSetValues_returnsValues() { Date lastSuccessfulFetchTime = new Date(1000L); - metadataClient.updateLastFetchAsSuccessfulAt(lastSuccessfulFetchTime); - metadataClient.updateLastFetchAsFailed(); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(lastSuccessfulFetchTime); + sharedPrefsClient.updateLastFetchAsFailed(); long fetchTimeout = 666L; long minimumFetchInterval = 666L; - metadataClient.setConfigSettings( + sharedPrefsClient.setConfigSettings( new FirebaseRemoteConfigSettings.Builder() .setFetchTimeoutInSeconds(fetchTimeout) .setMinimumFetchIntervalInSeconds(minimumFetchInterval) .build()); - FirebaseRemoteConfigInfo info = metadataClient.getInfo(); + FirebaseRemoteConfigInfo info = sharedPrefsClient.getInfo(); assertThat(info.getFetchTimeMillis()).isEqualTo(lastSuccessfulFetchTime.getTime()); assertThat(info.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_FAILURE); @@ -267,9 +273,9 @@ public void getInfo_hasSetValues_returnsValues() { @Test public void getInfo_firstAndOnlyFetchFails_failStatusAndNoFetchYetTime() { - metadataClient.updateLastFetchAsFailed(); + sharedPrefsClient.updateLastFetchAsFailed(); - FirebaseRemoteConfigInfo info = metadataClient.getInfo(); + FirebaseRemoteConfigInfo info = sharedPrefsClient.getInfo(); assertThat(info.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_FAILURE); assertThat(info.getFetchTimeMillis()).isEqualTo(LAST_FETCH_TIME_IN_MILLIS_NO_FETCH_YET); @@ -278,9 +284,9 @@ public void getInfo_firstAndOnlyFetchFails_failStatusAndNoFetchYetTime() { @Test public void getInfo_fetchSucceeds_successStatusAndFetchTimeUpdated() { Date fetchTime = new Date(100L); - metadataClient.updateLastFetchAsSuccessfulAt(fetchTime); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(fetchTime); - FirebaseRemoteConfigInfo info = metadataClient.getInfo(); + FirebaseRemoteConfigInfo info = sharedPrefsClient.getInfo(); assertThat(info.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_SUCCESS); assertThat(info.getFetchTimeMillis()).isEqualTo(fetchTime.getTime()); @@ -289,11 +295,11 @@ public void getInfo_fetchSucceeds_successStatusAndFetchTimeUpdated() { @Test public void getInfo_firstFetchSucceedsSecondFetchFails_failStatusAndFirstFetchTime() { Date fetchTime = new Date(100L); - metadataClient.updateLastFetchAsSuccessfulAt(fetchTime); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(fetchTime); - metadataClient.updateLastFetchAsFailed(); + sharedPrefsClient.updateLastFetchAsFailed(); - FirebaseRemoteConfigInfo info = metadataClient.getInfo(); + FirebaseRemoteConfigInfo info = sharedPrefsClient.getInfo(); assertThat(info.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_FAILURE); assertThat(info.getFetchTimeMillis()).isEqualTo(fetchTime.getTime()); @@ -302,12 +308,12 @@ public void getInfo_firstFetchSucceedsSecondFetchFails_failStatusAndFirstFetchTi @Test public void getInfo_twoFetchesSucceed_successStatusAndSecondFetchTime() { Date fetchTime = new Date(100L); - metadataClient.updateLastFetchAsSuccessfulAt(fetchTime); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(fetchTime); Date secondFetchTime = new Date(200L); - metadataClient.updateLastFetchAsSuccessfulAt(secondFetchTime); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(secondFetchTime); - FirebaseRemoteConfigInfo info = metadataClient.getInfo(); + FirebaseRemoteConfigInfo info = sharedPrefsClient.getInfo(); assertThat(info.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_SUCCESS); assertThat(info.getFetchTimeMillis()).isEqualTo(secondFetchTime.getTime()); @@ -316,11 +322,11 @@ public void getInfo_twoFetchesSucceed_successStatusAndSecondFetchTime() { @Test public void getInfo_hitsThrottleLimit_throttledStatus() { Date fetchTime = new Date(100L); - metadataClient.updateLastFetchAsSuccessfulAt(fetchTime); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(fetchTime); - metadataClient.updateLastFetchAsThrottled(); + sharedPrefsClient.updateLastFetchAsThrottled(); - FirebaseRemoteConfigInfo info = metadataClient.getInfo(); + FirebaseRemoteConfigInfo info = sharedPrefsClient.getInfo(); assertThat(info.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_THROTTLED); assertThat(info.getFetchTimeMillis()).isEqualTo(fetchTime.getTime()); @@ -329,19 +335,19 @@ public void getInfo_hitsThrottleLimit_throttledStatus() { @Test public void clear_hasSetValues_clearsAll() { Date lastSuccessfulFetchTime = new Date(1000L); - metadataClient.updateLastFetchAsSuccessfulAt(lastSuccessfulFetchTime); + sharedPrefsClient.updateLastFetchAsSuccessfulAt(lastSuccessfulFetchTime); long fetchTimeout = 666L; long minimumFetchInterval = 666L; - metadataClient.setConfigSettings( + sharedPrefsClient.setConfigSettings( new FirebaseRemoteConfigSettings.Builder() .setFetchTimeoutInSeconds(fetchTimeout) .setMinimumFetchIntervalInSeconds(minimumFetchInterval) .build()); - metadataClient.clear(); + sharedPrefsClient.clear(); - FirebaseRemoteConfigInfo info = metadataClient.getInfo(); + FirebaseRemoteConfigInfo info = sharedPrefsClient.getInfo(); assertThat(info.getFetchTimeMillis()).isEqualTo(LAST_FETCH_TIME_IN_MILLIS_NO_FETCH_YET); assertThat(info.getLastFetchStatus()).isEqualTo(LAST_FETCH_STATUS_NO_FETCH_YET); assertThat(info.getConfigSettings().getFetchTimeoutInSeconds()) @@ -349,4 +355,40 @@ public void clear_hasSetValues_clearsAll() { assertThat(info.getConfigSettings().getMinimumFetchIntervalInSeconds()) .isEqualTo(DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS); } + + @Test + public void getCustomSignals_isNotSet_returnsEmptyMap() { + assertThat(sharedPrefsClient.getCustomSignals()).isEqualTo(Collections.emptyMap()); + } + + @Test + public void getCustomSignals_isSet_returnsCustomSignals() { + Map SAMPLE_CUSTOM_SIGNALS = + ImmutableMap.of( + "subscription", "premium", + "age", "20"); + sharedPrefsClient.setCustomSignals(SAMPLE_CUSTOM_SIGNALS); + assertThat(sharedPrefsClient.getCustomSignals()).isEqualTo(SAMPLE_CUSTOM_SIGNALS); + } + + @Test + public void setCustomSignals_multipleTimes_addsNewSignals() { + Map signals1 = ImmutableMap.of("subscription", "premium"); + Map signals2 = ImmutableMap.of("age", "20", "subscription", "basic"); + sharedPrefsClient.setCustomSignals(signals1); + sharedPrefsClient.setCustomSignals(signals2); + Map expectedSignals = ImmutableMap.of("subscription", "basic", "age", "20"); + assertThat(sharedPrefsClient.getCustomSignals()).isEqualTo(expectedSignals); + } + + @Test + public void setCustomSignals_nullValue_removesSignal() { + Map signals1 = ImmutableMap.of("subscription", "premium", "age", "20"); + sharedPrefsClient.setCustomSignals(signals1); + Map signals2 = new HashMap<>(); + signals2.put("age", null); + sharedPrefsClient.setCustomSignals(signals2); + Map expectedSignals = ImmutableMap.of("subscription", "premium"); + assertThat(sharedPrefsClient.getCustomSignals()).isEqualTo(expectedSignals); + } } diff --git a/firebase-config/src/test/java/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt b/firebase-config/src/test/java/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt index c11ea869fbf..2a423843a7c 100644 --- a/firebase-config/src/test/java/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt +++ b/firebase-config/src/test/java/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt @@ -32,8 +32,8 @@ import com.google.firebase.remoteconfig.createRemoteConfig import com.google.firebase.remoteconfig.internal.ConfigCacheClient import com.google.firebase.remoteconfig.internal.ConfigFetchHandler import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler -import com.google.firebase.remoteconfig.internal.ConfigMetadataClient import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler +import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler import org.junit.After import org.junit.Before @@ -142,7 +142,7 @@ class ConfigTests : BaseTestCase() { defaultConfigsCache = mock(ConfigCacheClient::class.java), fetchHandler = mock(ConfigFetchHandler::class.java), getHandler = mockGetHandler, - frcMetadata = mock(ConfigMetadataClient::class.java), + frcSharedPrefs = mock(ConfigSharedPrefsClient::class.java), realtimeHandler = mock(ConfigRealtimeHandler::class.java), rolloutsStateSubscriptionsHandler = mock(RolloutsStateSubscriptionsHandler::class.java) ) diff --git a/firebase-crashlytics-ndk/api.txt b/firebase-crashlytics-ndk/api.txt index d802177e249..da4f6cc18fe 100644 --- a/firebase-crashlytics-ndk/api.txt +++ b/firebase-crashlytics-ndk/api.txt @@ -1 +1 @@ -// Signature format: 2.0 +// Signature format: 3.0 diff --git a/firebase-crashlytics-ndk/gradle.properties b/firebase-crashlytics-ndk/gradle.properties index 345224dc4c7..5ab96e1d760 100644 --- a/firebase-crashlytics-ndk/gradle.properties +++ b/firebase-crashlytics-ndk/gradle.properties @@ -1,2 +1,2 @@ -version=19.3.1 -latestReleasedVersion=19.3.0 +version=19.4.1 +latestReleasedVersion=19.4.0 diff --git a/firebase-crashlytics/CHANGELOG.md b/firebase-crashlytics/CHANGELOG.md index 4d526585b44..7086b0b0c9d 100644 --- a/firebase-crashlytics/CHANGELOG.md +++ b/firebase-crashlytics/CHANGELOG.md @@ -1,7 +1,16 @@ # Unreleased + + +# 19.4.0 * [feature] Added an overload for `recordException` that allows logging additional custom keys to the non fatal event [#3551] + +## Kotlin +The Kotlin extensions library transitively includes the updated +`firebase-crashlytics` library. The Kotlin extensions library has no additional +updates. + # 19.3.0 * [fixed] Fixed inefficiency in the Kotlin `FirebaseCrashlytics.setCustomKeys` extension. * [fixed] Execute failure listener outside the main thread [#6535] diff --git a/firebase-crashlytics/api.txt b/firebase-crashlytics/api.txt index 28576427fab..8cde5315a80 100644 --- a/firebase-crashlytics/api.txt +++ b/firebase-crashlytics/api.txt @@ -1,4 +1,4 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.crashlytics { public class CustomKeysAndValues { @@ -6,51 +6,51 @@ package com.google.firebase.crashlytics { public static class CustomKeysAndValues.Builder { ctor public CustomKeysAndValues.Builder(); - method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues build(); - method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putBoolean(@NonNull String, boolean); - method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putDouble(@NonNull String, double); - method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putFloat(@NonNull String, float); - method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putInt(@NonNull String, int); - method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putLong(@NonNull String, long); - method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putString(@NonNull String, @NonNull String); + method public com.google.firebase.crashlytics.CustomKeysAndValues build(); + method public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putBoolean(String, boolean); + method public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putDouble(String, double); + method public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putFloat(String, float); + method public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putInt(String, int); + method public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putLong(String, long); + method public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putString(String, String); } public class FirebaseCrashlytics { - method @NonNull public com.google.android.gms.tasks.Task checkForUnsentReports(); + method public com.google.android.gms.tasks.Task checkForUnsentReports(); method public void deleteUnsentReports(); method public boolean didCrashOnPreviousExecution(); - method @NonNull public static com.google.firebase.crashlytics.FirebaseCrashlytics getInstance(); + method public static com.google.firebase.crashlytics.FirebaseCrashlytics getInstance(); method public boolean isCrashlyticsCollectionEnabled(); - method public void log(@NonNull String); - method public void recordException(@NonNull Throwable); - method public void recordException(@NonNull Throwable, @NonNull com.google.firebase.crashlytics.CustomKeysAndValues); + method public void log(String); + method public void recordException(Throwable); + method public void recordException(Throwable, com.google.firebase.crashlytics.CustomKeysAndValues); method public void sendUnsentReports(); method public void setCrashlyticsCollectionEnabled(boolean); - method public void setCrashlyticsCollectionEnabled(@Nullable Boolean); - method public void setCustomKey(@NonNull String, boolean); - method public void setCustomKey(@NonNull String, double); - method public void setCustomKey(@NonNull String, float); - method public void setCustomKey(@NonNull String, int); - method public void setCustomKey(@NonNull String, long); - method public void setCustomKey(@NonNull String, @NonNull String); - method public void setCustomKeys(@NonNull com.google.firebase.crashlytics.CustomKeysAndValues); - method public void setUserId(@NonNull String); + method public void setCrashlyticsCollectionEnabled(Boolean?); + method public void setCustomKey(String, boolean); + method public void setCustomKey(String, double); + method public void setCustomKey(String, float); + method public void setCustomKey(String, int); + method public void setCustomKey(String, String); + method public void setCustomKey(String, long); + method public void setCustomKeys(com.google.firebase.crashlytics.CustomKeysAndValues); + method public void setUserId(String); } public final class FirebaseCrashlyticsKt { - method @NonNull public static com.google.firebase.crashlytics.FirebaseCrashlytics getCrashlytics(@NonNull com.google.firebase.Firebase); - method public static void recordException(@NonNull com.google.firebase.crashlytics.FirebaseCrashlytics, @NonNull Throwable throwable, @NonNull kotlin.jvm.functions.Function1 init); - method public static void setCustomKeys(@NonNull com.google.firebase.crashlytics.FirebaseCrashlytics, @NonNull kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.crashlytics.FirebaseCrashlytics getCrashlytics(com.google.firebase.Firebase); + method public static void recordException(com.google.firebase.crashlytics.FirebaseCrashlytics, Throwable throwable, kotlin.jvm.functions.Function1 init); + method public static void setCustomKeys(com.google.firebase.crashlytics.FirebaseCrashlytics, kotlin.jvm.functions.Function1 init); } public final class KeyValueBuilder { - ctor @Deprecated public KeyValueBuilder(@NonNull com.google.firebase.crashlytics.FirebaseCrashlytics crashlytics); - method public void key(@NonNull String key, boolean value); - method public void key(@NonNull String key, double value); - method public void key(@NonNull String key, float value); - method public void key(@NonNull String key, int value); - method public void key(@NonNull String key, long value); - method public void key(@NonNull String key, @NonNull String value); + ctor @Deprecated public KeyValueBuilder(com.google.firebase.crashlytics.FirebaseCrashlytics crashlytics); + method public void key(String key, boolean value); + method public void key(String key, double value); + method public void key(String key, float value); + method public void key(String key, int value); + method public void key(String key, String value); + method public void key(String key, long value); } } @@ -58,18 +58,18 @@ package com.google.firebase.crashlytics { package com.google.firebase.crashlytics.ktx { public final class FirebaseCrashlyticsKt { - method @Deprecated @NonNull public static com.google.firebase.crashlytics.FirebaseCrashlytics getCrashlytics(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated public static void setCustomKeys(@NonNull com.google.firebase.crashlytics.FirebaseCrashlytics, @NonNull kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.firebase.crashlytics.FirebaseCrashlytics getCrashlytics(com.google.firebase.ktx.Firebase); + method @Deprecated public static void setCustomKeys(com.google.firebase.crashlytics.FirebaseCrashlytics, kotlin.jvm.functions.Function1 init); } @Deprecated public final class KeyValueBuilder { - ctor @Deprecated public KeyValueBuilder(@NonNull com.google.firebase.crashlytics.FirebaseCrashlytics crashlytics); - method @Deprecated public void key(@NonNull String key, boolean value); - method @Deprecated public void key(@NonNull String key, double value); - method @Deprecated public void key(@NonNull String key, float value); - method @Deprecated public void key(@NonNull String key, int value); - method @Deprecated public void key(@NonNull String key, long value); - method @Deprecated public void key(@NonNull String key, @NonNull String value); + ctor @Deprecated public KeyValueBuilder(com.google.firebase.crashlytics.FirebaseCrashlytics crashlytics); + method @Deprecated public void key(String key, boolean value); + method @Deprecated public void key(String key, double value); + method @Deprecated public void key(String key, float value); + method @Deprecated public void key(String key, int value); + method @Deprecated public void key(String key, String value); + method @Deprecated public void key(String key, long value); } } diff --git a/firebase-crashlytics/gradle.properties b/firebase-crashlytics/gradle.properties index 345224dc4c7..5ab96e1d760 100644 --- a/firebase-crashlytics/gradle.properties +++ b/firebase-crashlytics/gradle.properties @@ -1,2 +1,2 @@ -version=19.3.1 -latestReleasedVersion=19.3.0 +version=19.4.1 +latestReleasedVersion=19.4.0 diff --git a/firebase-crashlytics/ktx/api.txt b/firebase-crashlytics/ktx/api.txt index fdf9446609e..da4f6cc18fe 100644 --- a/firebase-crashlytics/ktx/api.txt +++ b/firebase-crashlytics/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.crashlytics.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/KeyValueBuilder.kt b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/KeyValueBuilder.kt index 74d3793e215..636b975ab1d 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/KeyValueBuilder.kt +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/KeyValueBuilder.kt @@ -23,7 +23,7 @@ private constructor( private val builder: CustomKeysAndValues.Builder, ) { @Deprecated( - "Do not construct this directly. Use `setCustomKeys` instead. To be removed in the next major release." + "Do not construct this directly. Use [setCustomKeys] instead. To be removed in the next major release." ) constructor(crashlytics: FirebaseCrashlytics) : this(crashlytics, CustomKeysAndValues.Builder()) diff --git a/firebase-database-collection/api.txt b/firebase-database-collection/api.txt index d802177e249..da4f6cc18fe 100644 --- a/firebase-database-collection/api.txt +++ b/firebase-database-collection/api.txt @@ -1 +1 @@ -// Signature format: 2.0 +// Signature format: 3.0 diff --git a/firebase-database/api.txt b/firebase-database/api.txt index 402c0ff3f02..9c1929bdba5 100644 --- a/firebase-database/api.txt +++ b/firebase-database/api.txt @@ -1,80 +1,80 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.database { public abstract sealed class ChildEvent { } public static final class ChildEvent.Added extends com.google.firebase.database.ChildEvent { - ctor public ChildEvent.Added(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @NonNull public com.google.firebase.database.DataSnapshot component1(); - method @Nullable public String component2(); - method @NonNull public com.google.firebase.database.ChildEvent.Added copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Nullable public String getPreviousChildName(); - method @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); - property @Nullable public final String previousChildName; - property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + ctor public ChildEvent.Added(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method public com.google.firebase.database.DataSnapshot component1(); + method public String? component2(); + method public com.google.firebase.database.ChildEvent.Added copy(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method public String? getPreviousChildName(); + method public com.google.firebase.database.DataSnapshot getSnapshot(); + property public final String? previousChildName; + property public final com.google.firebase.database.DataSnapshot snapshot; } public static final class ChildEvent.Changed extends com.google.firebase.database.ChildEvent { - ctor public ChildEvent.Changed(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @NonNull public com.google.firebase.database.DataSnapshot component1(); - method @Nullable public String component2(); - method @NonNull public com.google.firebase.database.ChildEvent.Changed copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Nullable public String getPreviousChildName(); - method @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); - property @Nullable public final String previousChildName; - property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + ctor public ChildEvent.Changed(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method public com.google.firebase.database.DataSnapshot component1(); + method public String? component2(); + method public com.google.firebase.database.ChildEvent.Changed copy(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method public String? getPreviousChildName(); + method public com.google.firebase.database.DataSnapshot getSnapshot(); + property public final String? previousChildName; + property public final com.google.firebase.database.DataSnapshot snapshot; } public static final class ChildEvent.Moved extends com.google.firebase.database.ChildEvent { - ctor public ChildEvent.Moved(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @NonNull public com.google.firebase.database.DataSnapshot component1(); - method @Nullable public String component2(); - method @NonNull public com.google.firebase.database.ChildEvent.Moved copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Nullable public String getPreviousChildName(); - method @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); - property @Nullable public final String previousChildName; - property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + ctor public ChildEvent.Moved(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method public com.google.firebase.database.DataSnapshot component1(); + method public String? component2(); + method public com.google.firebase.database.ChildEvent.Moved copy(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method public String? getPreviousChildName(); + method public com.google.firebase.database.DataSnapshot getSnapshot(); + property public final String? previousChildName; + property public final com.google.firebase.database.DataSnapshot snapshot; } public static final class ChildEvent.Removed extends com.google.firebase.database.ChildEvent { - ctor public ChildEvent.Removed(@NonNull com.google.firebase.database.DataSnapshot snapshot); - method @NonNull public com.google.firebase.database.DataSnapshot component1(); - method @NonNull public com.google.firebase.database.ChildEvent.Removed copy(@NonNull com.google.firebase.database.DataSnapshot snapshot); - method @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); - property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + ctor public ChildEvent.Removed(com.google.firebase.database.DataSnapshot snapshot); + method public com.google.firebase.database.DataSnapshot component1(); + method public com.google.firebase.database.ChildEvent.Removed copy(com.google.firebase.database.DataSnapshot snapshot); + method public com.google.firebase.database.DataSnapshot getSnapshot(); + property public final com.google.firebase.database.DataSnapshot snapshot; } public interface ChildEventListener { - method public void onCancelled(@NonNull com.google.firebase.database.DatabaseError); - method public void onChildAdded(@NonNull com.google.firebase.database.DataSnapshot, @Nullable String); - method public void onChildChanged(@NonNull com.google.firebase.database.DataSnapshot, @Nullable String); - method public void onChildMoved(@NonNull com.google.firebase.database.DataSnapshot, @Nullable String); - method public void onChildRemoved(@NonNull com.google.firebase.database.DataSnapshot); + method public void onCancelled(com.google.firebase.database.DatabaseError); + method public void onChildAdded(com.google.firebase.database.DataSnapshot, String?); + method public void onChildChanged(com.google.firebase.database.DataSnapshot, String?); + method public void onChildMoved(com.google.firebase.database.DataSnapshot, String?); + method public void onChildRemoved(com.google.firebase.database.DataSnapshot); } public class DataSnapshot { - method @NonNull public com.google.firebase.database.DataSnapshot child(@NonNull String); + method public com.google.firebase.database.DataSnapshot child(String); method public boolean exists(); - method @NonNull public Iterable getChildren(); + method public Iterable getChildren(); method public long getChildrenCount(); - method @Nullable public String getKey(); - method @Nullable public Object getPriority(); - method @NonNull public com.google.firebase.database.DatabaseReference getRef(); - method @Nullable public Object getValue(); - method @Nullable public Object getValue(boolean); - method @Nullable public T getValue(@NonNull Class); - method @Nullable public T getValue(@NonNull com.google.firebase.database.GenericTypeIndicator); - method public boolean hasChild(@NonNull String); + method public String? getKey(); + method public Object? getPriority(); + method public com.google.firebase.database.DatabaseReference getRef(); + method public Object? getValue(); + method public Object? getValue(boolean); + method public T? getValue(com.google.firebase.database.GenericTypeIndicator); + method public T? getValue(Class); + method public boolean hasChild(String); method public boolean hasChildren(); } public class DatabaseError { - method @NonNull public static com.google.firebase.database.DatabaseError fromException(@NonNull Throwable); + method public static com.google.firebase.database.DatabaseError fromException(Throwable); method public int getCode(); - method @NonNull public String getDetails(); - method @NonNull public String getMessage(); - method @NonNull public com.google.firebase.database.DatabaseException toException(); + method public String getDetails(); + method public String getMessage(); + method public com.google.firebase.database.DatabaseException toException(); field public static final int DATA_STALE = -1; // 0xffffffff field public static final int DISCONNECTED = -4; // 0xfffffffc field public static final int EXPIRED_TOKEN = -6; // 0xfffffffa @@ -94,65 +94,65 @@ package com.google.firebase.database { } public final class DatabaseKt { - method @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.Firebase, @NonNull String url); - method @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String url); - method @NonNull public static kotlinx.coroutines.flow.Flow getChildEvents(@NonNull com.google.firebase.database.Query); - method @NonNull public static com.google.firebase.database.FirebaseDatabase getDatabase(@NonNull com.google.firebase.Firebase); - method @NonNull public static kotlinx.coroutines.flow.Flow getSnapshots(@NonNull com.google.firebase.database.Query); - method public static inline T getValue(@NonNull com.google.firebase.database.DataSnapshot); - method public static inline T getValue(@NonNull com.google.firebase.database.MutableData); - method public static inline kotlinx.coroutines.flow.Flow values(@NonNull com.google.firebase.database.Query); + method public static com.google.firebase.database.FirebaseDatabase database(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); + method public static com.google.firebase.database.FirebaseDatabase database(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app, String url); + method public static com.google.firebase.database.FirebaseDatabase database(com.google.firebase.Firebase, String url); + method public static kotlinx.coroutines.flow.Flow getChildEvents(com.google.firebase.database.Query); + method public static com.google.firebase.database.FirebaseDatabase getDatabase(com.google.firebase.Firebase); + method public static kotlinx.coroutines.flow.Flow getSnapshots(com.google.firebase.database.Query); + method public static inline T? getValue(com.google.firebase.database.DataSnapshot); + method public static inline T? getValue(com.google.firebase.database.MutableData); + method public static inline kotlinx.coroutines.flow.Flow values(com.google.firebase.database.Query); } public class DatabaseReference extends com.google.firebase.database.Query { - method @NonNull public com.google.firebase.database.DatabaseReference child(@NonNull String); - method @NonNull public com.google.firebase.database.FirebaseDatabase getDatabase(); - method @Nullable public String getKey(); - method @Nullable public com.google.firebase.database.DatabaseReference getParent(); - method @NonNull public com.google.firebase.database.DatabaseReference getRoot(); + method public com.google.firebase.database.DatabaseReference child(String); + method public com.google.firebase.database.FirebaseDatabase getDatabase(); + method public String? getKey(); + method public com.google.firebase.database.DatabaseReference? getParent(); + method public com.google.firebase.database.DatabaseReference getRoot(); method public static void goOffline(); method public static void goOnline(); - method @NonNull public com.google.firebase.database.OnDisconnect onDisconnect(); - method @NonNull public com.google.firebase.database.DatabaseReference push(); - method @NonNull public com.google.android.gms.tasks.Task removeValue(); - method public void removeValue(@Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method public void runTransaction(@NonNull com.google.firebase.database.Transaction.Handler); - method public void runTransaction(@NonNull com.google.firebase.database.Transaction.Handler, boolean); - method @NonNull public com.google.android.gms.tasks.Task setPriority(@Nullable Object); - method public void setPriority(@Nullable Object, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method @NonNull public com.google.android.gms.tasks.Task setValue(@Nullable Object); - method @NonNull public com.google.android.gms.tasks.Task setValue(@Nullable Object, @Nullable Object); - method public void setValue(@Nullable Object, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method public void setValue(@Nullable Object, @Nullable Object, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method @NonNull public com.google.android.gms.tasks.Task updateChildren(@NonNull java.util.Map); - method public void updateChildren(@NonNull java.util.Map, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); + method public com.google.firebase.database.OnDisconnect onDisconnect(); + method public com.google.firebase.database.DatabaseReference push(); + method public com.google.android.gms.tasks.Task removeValue(); + method public void removeValue(com.google.firebase.database.DatabaseReference.CompletionListener?); + method public void runTransaction(com.google.firebase.database.Transaction.Handler); + method public void runTransaction(com.google.firebase.database.Transaction.Handler, boolean); + method public com.google.android.gms.tasks.Task setPriority(Object?); + method public void setPriority(Object?, com.google.firebase.database.DatabaseReference.CompletionListener?); + method public com.google.android.gms.tasks.Task setValue(Object?); + method public void setValue(Object?, com.google.firebase.database.DatabaseReference.CompletionListener?); + method public com.google.android.gms.tasks.Task setValue(Object?, Object?); + method public void setValue(Object?, Object?, com.google.firebase.database.DatabaseReference.CompletionListener?); + method public com.google.android.gms.tasks.Task updateChildren(java.util.Map); + method public void updateChildren(java.util.Map, com.google.firebase.database.DatabaseReference.CompletionListener?); } public static interface DatabaseReference.CompletionListener { - method public void onComplete(@Nullable com.google.firebase.database.DatabaseError, @NonNull com.google.firebase.database.DatabaseReference); + method public void onComplete(com.google.firebase.database.DatabaseError?, com.google.firebase.database.DatabaseReference); } @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) public @interface Exclude { } public class FirebaseDatabase { - method @NonNull public com.google.firebase.FirebaseApp getApp(); - method @NonNull public static com.google.firebase.database.FirebaseDatabase getInstance(); - method @NonNull public static com.google.firebase.database.FirebaseDatabase getInstance(@NonNull String); - method @NonNull public static com.google.firebase.database.FirebaseDatabase getInstance(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public static com.google.firebase.database.FirebaseDatabase getInstance(@NonNull com.google.firebase.FirebaseApp, @NonNull String); - method @NonNull public com.google.firebase.database.DatabaseReference getReference(); - method @NonNull public com.google.firebase.database.DatabaseReference getReference(@NonNull String); - method @NonNull public com.google.firebase.database.DatabaseReference getReferenceFromUrl(@NonNull String); - method @NonNull public static String getSdkVersion(); + method public com.google.firebase.FirebaseApp getApp(); + method public static com.google.firebase.database.FirebaseDatabase getInstance(); + method public static com.google.firebase.database.FirebaseDatabase getInstance(com.google.firebase.FirebaseApp); + method public static com.google.firebase.database.FirebaseDatabase getInstance(com.google.firebase.FirebaseApp, String); + method public static com.google.firebase.database.FirebaseDatabase getInstance(String); + method public com.google.firebase.database.DatabaseReference getReference(); + method public com.google.firebase.database.DatabaseReference getReference(String); + method public com.google.firebase.database.DatabaseReference getReferenceFromUrl(String); + method public static String getSdkVersion(); method public void goOffline(); method public void goOnline(); method public void purgeOutstandingWrites(); - method public void setLogLevel(@NonNull com.google.firebase.database.Logger.Level); + method public void setLogLevel(com.google.firebase.database.Logger.Level); method public void setPersistenceCacheSizeBytes(long); method public void setPersistenceEnabled(boolean); - method public void useEmulator(@NonNull String, int); + method public void useEmulator(String, int); } public abstract class GenericTypeIndicator { @@ -174,34 +174,34 @@ package com.google.firebase.database { } public class MutableData { - method @NonNull public com.google.firebase.database.MutableData child(@NonNull String); - method @NonNull public Iterable getChildren(); + method public com.google.firebase.database.MutableData child(String); + method public Iterable getChildren(); method public long getChildrenCount(); - method @Nullable public String getKey(); - method @Nullable public Object getPriority(); - method @Nullable public Object getValue(); - method @Nullable public T getValue(@NonNull Class); - method @Nullable public T getValue(@NonNull com.google.firebase.database.GenericTypeIndicator); - method public boolean hasChild(@NonNull String); + method public String? getKey(); + method public Object? getPriority(); + method public Object? getValue(); + method public T? getValue(com.google.firebase.database.GenericTypeIndicator); + method public T? getValue(Class); + method public boolean hasChild(String); method public boolean hasChildren(); - method public void setPriority(@Nullable Object); - method public void setValue(@Nullable Object) throws com.google.firebase.database.DatabaseException; + method public void setPriority(Object?); + method public void setValue(Object?) throws com.google.firebase.database.DatabaseException; } public class OnDisconnect { - method @NonNull public com.google.android.gms.tasks.Task cancel(); - method public void cancel(@NonNull com.google.firebase.database.DatabaseReference.CompletionListener); - method @NonNull public com.google.android.gms.tasks.Task removeValue(); - method public void removeValue(@Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method @NonNull public com.google.android.gms.tasks.Task setValue(@Nullable Object); - method @NonNull public com.google.android.gms.tasks.Task setValue(@Nullable Object, @Nullable String); - method @NonNull public com.google.android.gms.tasks.Task setValue(@Nullable Object, double); - method public void setValue(@Nullable Object, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method public void setValue(@Nullable Object, @Nullable String, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method public void setValue(@Nullable Object, double, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method public void setValue(@Nullable Object, @Nullable java.util.Map, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); - method @NonNull public com.google.android.gms.tasks.Task updateChildren(@NonNull java.util.Map); - method public void updateChildren(@NonNull java.util.Map, @Nullable com.google.firebase.database.DatabaseReference.CompletionListener); + method public com.google.android.gms.tasks.Task cancel(); + method public void cancel(com.google.firebase.database.DatabaseReference.CompletionListener); + method public com.google.android.gms.tasks.Task removeValue(); + method public void removeValue(com.google.firebase.database.DatabaseReference.CompletionListener?); + method public com.google.android.gms.tasks.Task setValue(Object?); + method public void setValue(Object?, com.google.firebase.database.DatabaseReference.CompletionListener?); + method public com.google.android.gms.tasks.Task setValue(Object?, double); + method public void setValue(Object?, double, com.google.firebase.database.DatabaseReference.CompletionListener?); + method public com.google.android.gms.tasks.Task setValue(Object?, String?); + method public void setValue(Object?, String?, com.google.firebase.database.DatabaseReference.CompletionListener?); + method public void setValue(Object?, java.util.Map?, com.google.firebase.database.DatabaseReference.CompletionListener?); + method public com.google.android.gms.tasks.Task updateChildren(java.util.Map); + method public void updateChildren(java.util.Map, com.google.firebase.database.DatabaseReference.CompletionListener?); } @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) public @interface PropertyName { @@ -209,57 +209,57 @@ package com.google.firebase.database { } public class Query { - method @NonNull public com.google.firebase.database.ChildEventListener addChildEventListener(@NonNull com.google.firebase.database.ChildEventListener); - method public void addListenerForSingleValueEvent(@NonNull com.google.firebase.database.ValueEventListener); - method @NonNull public com.google.firebase.database.ValueEventListener addValueEventListener(@NonNull com.google.firebase.database.ValueEventListener); - method @NonNull public com.google.firebase.database.Query endAt(@Nullable String); - method @NonNull public com.google.firebase.database.Query endAt(double); - method @NonNull public com.google.firebase.database.Query endAt(boolean); - method @NonNull public com.google.firebase.database.Query endAt(@Nullable String, @Nullable String); - method @NonNull public com.google.firebase.database.Query endAt(double, @Nullable String); - method @NonNull public com.google.firebase.database.Query endAt(boolean, @Nullable String); - method @NonNull public com.google.firebase.database.Query endBefore(@Nullable String); - method @NonNull public com.google.firebase.database.Query endBefore(double); - method @NonNull public com.google.firebase.database.Query endBefore(boolean); - method @NonNull public com.google.firebase.database.Query endBefore(@Nullable String, @Nullable String); - method @NonNull public com.google.firebase.database.Query endBefore(double, @Nullable String); - method @NonNull public com.google.firebase.database.Query endBefore(boolean, @Nullable String); - method @NonNull public com.google.firebase.database.Query equalTo(@Nullable String); - method @NonNull public com.google.firebase.database.Query equalTo(double); - method @NonNull public com.google.firebase.database.Query equalTo(boolean); - method @NonNull public com.google.firebase.database.Query equalTo(@Nullable String, @Nullable String); - method @NonNull public com.google.firebase.database.Query equalTo(double, @Nullable String); - method @NonNull public com.google.firebase.database.Query equalTo(boolean, @Nullable String); - method @NonNull public com.google.android.gms.tasks.Task get(); - method @NonNull public com.google.firebase.database.DatabaseReference getRef(); + method public com.google.firebase.database.ChildEventListener addChildEventListener(com.google.firebase.database.ChildEventListener); + method public void addListenerForSingleValueEvent(com.google.firebase.database.ValueEventListener); + method public com.google.firebase.database.ValueEventListener addValueEventListener(com.google.firebase.database.ValueEventListener); + method public com.google.firebase.database.Query endAt(boolean); + method public com.google.firebase.database.Query endAt(boolean, String?); + method public com.google.firebase.database.Query endAt(double); + method public com.google.firebase.database.Query endAt(double, String?); + method public com.google.firebase.database.Query endAt(String?); + method public com.google.firebase.database.Query endAt(String?, String?); + method public com.google.firebase.database.Query endBefore(boolean); + method public com.google.firebase.database.Query endBefore(boolean, String?); + method public com.google.firebase.database.Query endBefore(double); + method public com.google.firebase.database.Query endBefore(double, String?); + method public com.google.firebase.database.Query endBefore(String?); + method public com.google.firebase.database.Query endBefore(String?, String?); + method public com.google.firebase.database.Query equalTo(boolean); + method public com.google.firebase.database.Query equalTo(boolean, String?); + method public com.google.firebase.database.Query equalTo(double); + method public com.google.firebase.database.Query equalTo(double, String?); + method public com.google.firebase.database.Query equalTo(String?); + method public com.google.firebase.database.Query equalTo(String?, String?); + method public com.google.android.gms.tasks.Task get(); + method public com.google.firebase.database.DatabaseReference getRef(); method public void keepSynced(boolean); - method @NonNull public com.google.firebase.database.Query limitToFirst(int); - method @NonNull public com.google.firebase.database.Query limitToLast(int); - method @NonNull public com.google.firebase.database.Query orderByChild(@NonNull String); - method @NonNull public com.google.firebase.database.Query orderByKey(); - method @NonNull public com.google.firebase.database.Query orderByPriority(); - method @NonNull public com.google.firebase.database.Query orderByValue(); - method public void removeEventListener(@NonNull com.google.firebase.database.ValueEventListener); - method public void removeEventListener(@NonNull com.google.firebase.database.ChildEventListener); - method @NonNull public com.google.firebase.database.Query startAfter(@Nullable String); - method @NonNull public com.google.firebase.database.Query startAfter(double); - method @NonNull public com.google.firebase.database.Query startAfter(boolean); - method @NonNull public com.google.firebase.database.Query startAfter(@Nullable String, @Nullable String); - method @NonNull public com.google.firebase.database.Query startAfter(double, @Nullable String); - method @NonNull public com.google.firebase.database.Query startAfter(boolean, @Nullable String); - method @NonNull public com.google.firebase.database.Query startAt(@Nullable String); - method @NonNull public com.google.firebase.database.Query startAt(double); - method @NonNull public com.google.firebase.database.Query startAt(boolean); - method @NonNull public com.google.firebase.database.Query startAt(@Nullable String, @Nullable String); - method @NonNull public com.google.firebase.database.Query startAt(double, @Nullable String); - method @NonNull public com.google.firebase.database.Query startAt(boolean, @Nullable String); + method public com.google.firebase.database.Query limitToFirst(int); + method public com.google.firebase.database.Query limitToLast(int); + method public com.google.firebase.database.Query orderByChild(String); + method public com.google.firebase.database.Query orderByKey(); + method public com.google.firebase.database.Query orderByPriority(); + method public com.google.firebase.database.Query orderByValue(); + method public void removeEventListener(com.google.firebase.database.ChildEventListener); + method public void removeEventListener(com.google.firebase.database.ValueEventListener); + method public com.google.firebase.database.Query startAfter(boolean); + method public com.google.firebase.database.Query startAfter(boolean, String?); + method public com.google.firebase.database.Query startAfter(double); + method public com.google.firebase.database.Query startAfter(double, String?); + method public com.google.firebase.database.Query startAfter(String?); + method public com.google.firebase.database.Query startAfter(String?, String?); + method public com.google.firebase.database.Query startAt(boolean); + method public com.google.firebase.database.Query startAt(boolean, String?); + method public com.google.firebase.database.Query startAt(double); + method public com.google.firebase.database.Query startAt(double, String?); + method public com.google.firebase.database.Query startAt(String?); + method public com.google.firebase.database.Query startAt(String?, String?); } public class ServerValue { ctor public ServerValue(); - method @NonNull public static final Object increment(long); - method @NonNull public static final Object increment(double); - field @NonNull public static final java.util.Map TIMESTAMP; + method public static final Object increment(double); + method public static final Object increment(long); + field public static final java.util.Map TIMESTAMP; } @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public @interface ThrowOnExtraProperties { @@ -267,13 +267,13 @@ package com.google.firebase.database { public class Transaction { ctor public Transaction(); - method @NonNull public static com.google.firebase.database.Transaction.Result abort(); - method @NonNull public static com.google.firebase.database.Transaction.Result success(@NonNull com.google.firebase.database.MutableData); + method public static com.google.firebase.database.Transaction.Result abort(); + method public static com.google.firebase.database.Transaction.Result success(com.google.firebase.database.MutableData); } public static interface Transaction.Handler { - method @NonNull public com.google.firebase.database.Transaction.Result doTransaction(@NonNull com.google.firebase.database.MutableData); - method public void onComplete(@Nullable com.google.firebase.database.DatabaseError, boolean, @Nullable com.google.firebase.database.DataSnapshot); + method public com.google.firebase.database.Transaction.Result doTransaction(com.google.firebase.database.MutableData); + method public void onComplete(com.google.firebase.database.DatabaseError?, boolean, com.google.firebase.database.DataSnapshot?); } public static class Transaction.Result { @@ -281,8 +281,8 @@ package com.google.firebase.database { } public interface ValueEventListener { - method public void onCancelled(@NonNull com.google.firebase.database.DatabaseError); - method public void onDataChange(@NonNull com.google.firebase.database.DataSnapshot); + method public void onCancelled(com.google.firebase.database.DatabaseError); + method public void onDataChange(com.google.firebase.database.DataSnapshot); } } @@ -293,56 +293,56 @@ package com.google.firebase.database.ktx { } @Deprecated public static final class ChildEvent.Added extends com.google.firebase.database.ktx.ChildEvent { - ctor @Deprecated public ChildEvent.Added(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Deprecated @NonNull public com.google.firebase.database.DataSnapshot component1(); - method @Deprecated @Nullable public String component2(); - method @Deprecated @NonNull public com.google.firebase.database.ktx.ChildEvent.Added copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Deprecated @Nullable public String getPreviousChildName(); - method @Deprecated @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); - property @Nullable public final String previousChildName; - property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + ctor @Deprecated public ChildEvent.Added(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method @Deprecated public com.google.firebase.database.DataSnapshot component1(); + method @Deprecated public String? component2(); + method @Deprecated public com.google.firebase.database.ktx.ChildEvent.Added copy(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method @Deprecated public String? getPreviousChildName(); + method @Deprecated public com.google.firebase.database.DataSnapshot getSnapshot(); + property @Deprecated public final String? previousChildName; + property @Deprecated public final com.google.firebase.database.DataSnapshot snapshot; } @Deprecated public static final class ChildEvent.Changed extends com.google.firebase.database.ktx.ChildEvent { - ctor @Deprecated public ChildEvent.Changed(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Deprecated @NonNull public com.google.firebase.database.DataSnapshot component1(); - method @Deprecated @Nullable public String component2(); - method @Deprecated @NonNull public com.google.firebase.database.ktx.ChildEvent.Changed copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Deprecated @Nullable public String getPreviousChildName(); - method @Deprecated @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); - property @Nullable public final String previousChildName; - property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + ctor @Deprecated public ChildEvent.Changed(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method @Deprecated public com.google.firebase.database.DataSnapshot component1(); + method @Deprecated public String? component2(); + method @Deprecated public com.google.firebase.database.ktx.ChildEvent.Changed copy(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method @Deprecated public String? getPreviousChildName(); + method @Deprecated public com.google.firebase.database.DataSnapshot getSnapshot(); + property @Deprecated public final String? previousChildName; + property @Deprecated public final com.google.firebase.database.DataSnapshot snapshot; } @Deprecated public static final class ChildEvent.Moved extends com.google.firebase.database.ktx.ChildEvent { - ctor @Deprecated public ChildEvent.Moved(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Deprecated @NonNull public com.google.firebase.database.DataSnapshot component1(); - method @Deprecated @Nullable public String component2(); - method @Deprecated @NonNull public com.google.firebase.database.ktx.ChildEvent.Moved copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); - method @Deprecated @Nullable public String getPreviousChildName(); - method @Deprecated @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); - property @Nullable public final String previousChildName; - property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + ctor @Deprecated public ChildEvent.Moved(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method @Deprecated public com.google.firebase.database.DataSnapshot component1(); + method @Deprecated public String? component2(); + method @Deprecated public com.google.firebase.database.ktx.ChildEvent.Moved copy(com.google.firebase.database.DataSnapshot snapshot, String? previousChildName); + method @Deprecated public String? getPreviousChildName(); + method @Deprecated public com.google.firebase.database.DataSnapshot getSnapshot(); + property @Deprecated public final String? previousChildName; + property @Deprecated public final com.google.firebase.database.DataSnapshot snapshot; } @Deprecated public static final class ChildEvent.Removed extends com.google.firebase.database.ktx.ChildEvent { - ctor @Deprecated public ChildEvent.Removed(@NonNull com.google.firebase.database.DataSnapshot snapshot); - method @Deprecated @NonNull public com.google.firebase.database.DataSnapshot component1(); - method @Deprecated @NonNull public com.google.firebase.database.ktx.ChildEvent.Removed copy(@NonNull com.google.firebase.database.DataSnapshot snapshot); - method @Deprecated @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); - property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + ctor @Deprecated public ChildEvent.Removed(com.google.firebase.database.DataSnapshot snapshot); + method @Deprecated public com.google.firebase.database.DataSnapshot component1(); + method @Deprecated public com.google.firebase.database.ktx.ChildEvent.Removed copy(com.google.firebase.database.DataSnapshot snapshot); + method @Deprecated public com.google.firebase.database.DataSnapshot getSnapshot(); + property @Deprecated public final com.google.firebase.database.DataSnapshot snapshot; } public final class DatabaseKt { - method @Deprecated @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.ktx.Firebase, @NonNull String url); - method @Deprecated @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @Deprecated @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String url); - method @Deprecated @NonNull public static kotlinx.coroutines.flow.Flow getChildEvents(@NonNull com.google.firebase.database.Query); - method @Deprecated @NonNull public static com.google.firebase.database.FirebaseDatabase getDatabase(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @NonNull public static kotlinx.coroutines.flow.Flow getSnapshots(@NonNull com.google.firebase.database.Query); - method @Deprecated public static inline T getValue(@NonNull com.google.firebase.database.DataSnapshot); - method @Deprecated public static inline T getValue(@NonNull com.google.firebase.database.MutableData); - method @Deprecated public static inline kotlinx.coroutines.flow.Flow values(@NonNull com.google.firebase.database.Query); + method @Deprecated public static com.google.firebase.database.FirebaseDatabase database(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); + method @Deprecated public static com.google.firebase.database.FirebaseDatabase database(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app, String url); + method @Deprecated public static com.google.firebase.database.FirebaseDatabase database(com.google.firebase.ktx.Firebase, String url); + method @Deprecated public static kotlinx.coroutines.flow.Flow getChildEvents(com.google.firebase.database.Query); + method @Deprecated public static com.google.firebase.database.FirebaseDatabase getDatabase(com.google.firebase.ktx.Firebase); + method @Deprecated public static kotlinx.coroutines.flow.Flow getSnapshots(com.google.firebase.database.Query); + method @Deprecated public static inline T? getValue(com.google.firebase.database.DataSnapshot); + method @Deprecated public static inline T? getValue(com.google.firebase.database.MutableData); + method @Deprecated public static inline kotlinx.coroutines.flow.Flow values(com.google.firebase.database.Query); } } diff --git a/firebase-database/ktx/api.txt b/firebase-database/ktx/api.txt index 9767e86197c..da4f6cc18fe 100644 --- a/firebase-database/ktx/api.txt +++ b/firebase-database/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.database.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-dataconnect/CHANGELOG.md b/firebase-dataconnect/CHANGELOG.md index 07a93ef2117..fa16a7ed32d 100644 --- a/firebase-dataconnect/CHANGELOG.md +++ b/firebase-dataconnect/CHANGELOG.md @@ -1,4 +1,7 @@ # Unreleased + + +# 16.0.0-beta04 * [changed] `FirebaseDataConnect.logLevel` type changed from `LogLevel` to `MutableStateFlow`. This enables apps to "collect" the flow to, for example, update a UI component when the log level changes. diff --git a/firebase-dataconnect/api.txt b/firebase-dataconnect/api.txt index 96e5c1e4601..19fb52985f5 100644 --- a/firebase-dataconnect/api.txt +++ b/firebase-dataconnect/api.txt @@ -1,57 +1,57 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.dataconnect { @kotlinx.serialization.Serializable(with=AnyValueSerializer::class) public final class AnyValue { - ctor public AnyValue(@NonNull java.util.Map value); - ctor public AnyValue(@NonNull java.util.List value); - ctor public AnyValue(@NonNull String value); ctor public AnyValue(boolean value); ctor public AnyValue(double value); - method @NonNull public Object getValue(); - property @NonNull public final Object value; - field @NonNull public static final com.google.firebase.dataconnect.AnyValue.Companion Companion; + ctor public AnyValue(String value); + ctor public AnyValue(java.util.List value); + ctor public AnyValue(java.util.Map value); + method public Object getValue(); + property public final Object value; + field public static final com.google.firebase.dataconnect.AnyValue.Companion Companion; } public static final class AnyValue.Companion { } public final class AnyValueKt { - method public static T decode(@NonNull com.google.firebase.dataconnect.AnyValue, @NonNull kotlinx.serialization.DeserializationStrategy deserializer, @Nullable kotlinx.serialization.modules.SerializersModule serializersModule = null); - method public static inline T decode(@NonNull com.google.firebase.dataconnect.AnyValue); - method @NonNull public static com.google.firebase.dataconnect.AnyValue encode(@NonNull com.google.firebase.dataconnect.AnyValue.Companion, @Nullable T value, @NonNull kotlinx.serialization.SerializationStrategy serializer, @Nullable kotlinx.serialization.modules.SerializersModule serializersModule = null); - method public static inline com.google.firebase.dataconnect.AnyValue encode(@NonNull com.google.firebase.dataconnect.AnyValue.Companion, @Nullable T value); - method @NonNull public static com.google.firebase.dataconnect.AnyValue fromAny(@NonNull com.google.firebase.dataconnect.AnyValue.Companion, @NonNull Object value); - method @Nullable public static com.google.firebase.dataconnect.AnyValue fromNullableAny(@NonNull com.google.firebase.dataconnect.AnyValue.Companion, @Nullable Object value); + method public static inline T decode(com.google.firebase.dataconnect.AnyValue); + method public static T decode(com.google.firebase.dataconnect.AnyValue, kotlinx.serialization.DeserializationStrategy deserializer, kotlinx.serialization.modules.SerializersModule? serializersModule = null); + method public static inline com.google.firebase.dataconnect.AnyValue encode(com.google.firebase.dataconnect.AnyValue.Companion, T value); + method public static com.google.firebase.dataconnect.AnyValue encode(com.google.firebase.dataconnect.AnyValue.Companion, T value, kotlinx.serialization.SerializationStrategy serializer, kotlinx.serialization.modules.SerializersModule? serializersModule = null); + method public static com.google.firebase.dataconnect.AnyValue fromAny(com.google.firebase.dataconnect.AnyValue.Companion, Object value); + method public static com.google.firebase.dataconnect.AnyValue? fromNullableAny(com.google.firebase.dataconnect.AnyValue.Companion, Object? value); } public final class ConnectorConfig { - ctor public ConnectorConfig(@NonNull String connector, @NonNull String location, @NonNull String serviceId); - method @NonNull public String getConnector(); - method @NonNull public String getLocation(); - method @NonNull public String getServiceId(); - property @NonNull public final String connector; - property @NonNull public final String location; - property @NonNull public final String serviceId; + ctor public ConnectorConfig(String connector, String location, String serviceId); + method public String getConnector(); + method public String getLocation(); + method public String getServiceId(); + property public final String connector; + property public final String location; + property public final String serviceId; } public final class ConnectorConfigKt { - method @NonNull public static com.google.firebase.dataconnect.ConnectorConfig copy(@NonNull com.google.firebase.dataconnect.ConnectorConfig, @NonNull String connector = connector, @NonNull String location = location, @NonNull String serviceId = serviceId); + method public static com.google.firebase.dataconnect.ConnectorConfig copy(com.google.firebase.dataconnect.ConnectorConfig, String connector = connector, String location = location, String serviceId = serviceId); } public class DataConnectException extends java.lang.Exception { - ctor public DataConnectException(@NonNull String message, @Nullable Throwable cause = null); + ctor public DataConnectException(String message, Throwable? cause = null); } public final class DataConnectSettings { - ctor public DataConnectSettings(@NonNull String host = "firebasedataconnect.googleapis.com", boolean sslEnabled = true); - method @NonNull public String getHost(); + ctor public DataConnectSettings(String host = "firebasedataconnect.googleapis.com", boolean sslEnabled = true); + method public String getHost(); method public boolean getSslEnabled(); - property @NonNull public final String host; + property public final String host; property public final boolean sslEnabled; } public final class DataConnectSettingsKt { - method @NonNull public static com.google.firebase.dataconnect.DataConnectSettings copy(@NonNull com.google.firebase.dataconnect.DataConnectSettings, @NonNull String host = host, boolean sslEnabled = sslEnabled); + method public static com.google.firebase.dataconnect.DataConnectSettings copy(com.google.firebase.dataconnect.DataConnectSettings, String host = host, boolean sslEnabled = sslEnabled); } @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING, message="This declaration is \"experimental\": its signature and/or semantics " + "may change in backwards-incompatible ways at any time without notice, " + "up to and including complete removal. " + "If you have a use case that relies on this declaration please open a " + "\"feature request\" issue at https://github.com/firebase/firebase-android-sdk " + "requesting this declaration\'s promotion from \"experimental\" to \"fully-supported\".") @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalFirebaseDataConnect { @@ -59,25 +59,23 @@ package com.google.firebase.dataconnect { public interface FirebaseDataConnect extends java.lang.AutoCloseable { method public void close(); - method public boolean equals(@Nullable Object other); - method @NonNull public com.google.firebase.FirebaseApp getApp(); - method @NonNull public com.google.firebase.dataconnect.ConnectorConfig getConfig(); - method @NonNull public com.google.firebase.dataconnect.DataConnectSettings getSettings(); + method public boolean equals(Object? other); + method public com.google.firebase.FirebaseApp getApp(); + method public com.google.firebase.dataconnect.ConnectorConfig getConfig(); + method public com.google.firebase.dataconnect.DataConnectSettings getSettings(); method public int hashCode(); - method @NonNull public com.google.firebase.dataconnect.MutationRef mutation(@NonNull String operationName, @Nullable Variables variables, @NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer, @Nullable kotlin.jvm.functions.Function1,kotlin.Unit> optionsBuilder = null); - method @NonNull public com.google.firebase.dataconnect.QueryRef query(@NonNull String operationName, @Nullable Variables variables, @NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer, @Nullable kotlin.jvm.functions.Function1,kotlin.Unit> optionsBuilder = null); - method @Nullable public suspend Object suspendingClose(@NonNull kotlin.coroutines.Continuation); - method @NonNull public String toString(); - method public void useEmulator(@NonNull String host = "10.0.2.2", int port = 9399); - property @NonNull public abstract com.google.firebase.FirebaseApp app; - property @NonNull public abstract com.google.firebase.dataconnect.ConnectorConfig config; - property @NonNull public abstract com.google.firebase.dataconnect.DataConnectSettings settings; - field @NonNull public static final com.google.firebase.dataconnect.FirebaseDataConnect.Companion Companion; + method public com.google.firebase.dataconnect.MutationRef mutation(String operationName, Variables variables, kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.SerializationStrategy variablesSerializer, kotlin.jvm.functions.Function1,kotlin.Unit>? optionsBuilder = null); + method public com.google.firebase.dataconnect.QueryRef query(String operationName, Variables variables, kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.SerializationStrategy variablesSerializer, kotlin.jvm.functions.Function1,kotlin.Unit>? optionsBuilder = null); + method public suspend Object? suspendingClose(kotlin.coroutines.Continuation); + method public String toString(); + method public void useEmulator(String host = "10.0.2.2", int port = 9399); + property public abstract com.google.firebase.FirebaseApp app; + property public abstract com.google.firebase.dataconnect.ConnectorConfig config; + property public abstract com.google.firebase.dataconnect.DataConnectSettings settings; + field public static final com.google.firebase.dataconnect.FirebaseDataConnect.Companion Companion; } public enum FirebaseDataConnect.CallerSdkType { - method @NonNull public static com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType valueOf(@NonNull String name) throws java.lang.IllegalArgumentException; - method @NonNull public static com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType[] values(); enum_constant public static final com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType Base; enum_constant public static final com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType Generated; } @@ -86,33 +84,33 @@ package com.google.firebase.dataconnect { } public static interface FirebaseDataConnect.MutationRefOptionsBuilder { - method @Nullable public com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType getCallerSdkType(); - method @Nullable public kotlinx.serialization.modules.SerializersModule getDataSerializersModule(); - method @Nullable public kotlinx.serialization.modules.SerializersModule getVariablesSerializersModule(); - method public void setCallerSdkType(@Nullable com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType); - method public void setDataSerializersModule(@Nullable kotlinx.serialization.modules.SerializersModule); - method public void setVariablesSerializersModule(@Nullable kotlinx.serialization.modules.SerializersModule); - property @Nullable public abstract com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType; - property @Nullable public abstract kotlinx.serialization.modules.SerializersModule dataSerializersModule; - property @Nullable public abstract kotlinx.serialization.modules.SerializersModule variablesSerializersModule; + method public com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType? getCallerSdkType(); + method public kotlinx.serialization.modules.SerializersModule? getDataSerializersModule(); + method public kotlinx.serialization.modules.SerializersModule? getVariablesSerializersModule(); + method public void setCallerSdkType(com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType?); + method public void setDataSerializersModule(kotlinx.serialization.modules.SerializersModule?); + method public void setVariablesSerializersModule(kotlinx.serialization.modules.SerializersModule?); + property public abstract com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType? callerSdkType; + property public abstract kotlinx.serialization.modules.SerializersModule? dataSerializersModule; + property public abstract kotlinx.serialization.modules.SerializersModule? variablesSerializersModule; } public static interface FirebaseDataConnect.QueryRefOptionsBuilder { - method @Nullable public com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType getCallerSdkType(); - method @Nullable public kotlinx.serialization.modules.SerializersModule getDataSerializersModule(); - method @Nullable public kotlinx.serialization.modules.SerializersModule getVariablesSerializersModule(); - method public void setCallerSdkType(@Nullable com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType); - method public void setDataSerializersModule(@Nullable kotlinx.serialization.modules.SerializersModule); - method public void setVariablesSerializersModule(@Nullable kotlinx.serialization.modules.SerializersModule); - property @Nullable public abstract com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType; - property @Nullable public abstract kotlinx.serialization.modules.SerializersModule dataSerializersModule; - property @Nullable public abstract kotlinx.serialization.modules.SerializersModule variablesSerializersModule; + method public com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType? getCallerSdkType(); + method public kotlinx.serialization.modules.SerializersModule? getDataSerializersModule(); + method public kotlinx.serialization.modules.SerializersModule? getVariablesSerializersModule(); + method public void setCallerSdkType(com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType?); + method public void setDataSerializersModule(kotlinx.serialization.modules.SerializersModule?); + method public void setVariablesSerializersModule(kotlinx.serialization.modules.SerializersModule?); + property public abstract com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType? callerSdkType; + property public abstract kotlinx.serialization.modules.SerializersModule? dataSerializersModule; + property public abstract kotlinx.serialization.modules.SerializersModule? variablesSerializersModule; } public final class FirebaseDataConnectKt { - method @NonNull public static com.google.firebase.dataconnect.FirebaseDataConnect getInstance(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion, @NonNull com.google.firebase.FirebaseApp app, @NonNull com.google.firebase.dataconnect.ConnectorConfig config, @NonNull com.google.firebase.dataconnect.DataConnectSettings settings = com.google.firebase.dataconnect.DataConnectSettings()); - method @NonNull public static com.google.firebase.dataconnect.FirebaseDataConnect getInstance(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion, @NonNull com.google.firebase.dataconnect.ConnectorConfig config, @NonNull com.google.firebase.dataconnect.DataConnectSettings settings = com.google.firebase.dataconnect.DataConnectSettings()); - method @NonNull public static kotlinx.coroutines.flow.MutableStateFlow getLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion); + method public static com.google.firebase.dataconnect.FirebaseDataConnect getInstance(com.google.firebase.dataconnect.FirebaseDataConnect.Companion, com.google.firebase.dataconnect.ConnectorConfig config, com.google.firebase.dataconnect.DataConnectSettings settings = com.google.firebase.dataconnect.DataConnectSettings()); + method public static com.google.firebase.dataconnect.FirebaseDataConnect getInstance(com.google.firebase.dataconnect.FirebaseDataConnect.Companion, com.google.firebase.FirebaseApp app, com.google.firebase.dataconnect.ConnectorConfig config, com.google.firebase.dataconnect.DataConnectSettings settings = com.google.firebase.dataconnect.DataConnectSettings()); + method public static kotlinx.coroutines.flow.MutableStateFlow getLogLevel(com.google.firebase.dataconnect.FirebaseDataConnect.Companion); } @kotlinx.serialization.Serializable(with=LocalDateSerializer::class) public final class LocalDate { @@ -126,90 +124,88 @@ package com.google.firebase.dataconnect { } public final class LocalDateKt { - method @NonNull public static com.google.firebase.dataconnect.LocalDate copy(@NonNull com.google.firebase.dataconnect.LocalDate, int year = year, int month = month, int day = day); - method @NonNull public static com.google.firebase.dataconnect.LocalDate toDataConnectLocalDate(@NonNull java.time.LocalDate); - method @NonNull public static com.google.firebase.dataconnect.LocalDate toDataConnectLocalDate(@NonNull kotlinx.datetime.LocalDate); - method @NonNull public static java.time.LocalDate toJavaLocalDate(@NonNull com.google.firebase.dataconnect.LocalDate); - method @NonNull public static kotlinx.datetime.LocalDate toKotlinxLocalDate(@NonNull com.google.firebase.dataconnect.LocalDate); + method public static com.google.firebase.dataconnect.LocalDate copy(com.google.firebase.dataconnect.LocalDate, int year = year, int month = month, int day = day); + method public static com.google.firebase.dataconnect.LocalDate toDataConnectLocalDate(java.time.LocalDate); + method public static com.google.firebase.dataconnect.LocalDate toDataConnectLocalDate(kotlinx.datetime.LocalDate); + method public static java.time.LocalDate toJavaLocalDate(com.google.firebase.dataconnect.LocalDate); + method public static kotlinx.datetime.LocalDate toKotlinxLocalDate(com.google.firebase.dataconnect.LocalDate); } public enum LogLevel { - method @NonNull public static com.google.firebase.dataconnect.LogLevel valueOf(@NonNull String name) throws java.lang.IllegalArgumentException; - method @NonNull public static com.google.firebase.dataconnect.LogLevel[] values(); enum_constant public static final com.google.firebase.dataconnect.LogLevel DEBUG; enum_constant public static final com.google.firebase.dataconnect.LogLevel NONE; enum_constant public static final com.google.firebase.dataconnect.LogLevel WARN; } public interface MutationRef extends com.google.firebase.dataconnect.OperationRef { - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.MutationRef copy(@NonNull String operationName, @Nullable Variables variables, @NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer, @NonNull com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType, @Nullable kotlinx.serialization.modules.SerializersModule dataSerializersModule, @Nullable kotlinx.serialization.modules.SerializersModule variablesSerializersModule); - method @Nullable public suspend Object execute(@NonNull kotlin.coroutines.Continuation>); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.MutationRef withDataDeserializer(@NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @Nullable kotlinx.serialization.modules.SerializersModule dataSerializersModule); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.MutationRef withVariablesSerializer(@Nullable NewVariables variables, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer, @Nullable kotlinx.serialization.modules.SerializersModule variablesSerializersModule); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.MutationRef copy(String operationName, Variables variables, kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.SerializationStrategy variablesSerializer, com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType, kotlinx.serialization.modules.SerializersModule? dataSerializersModule, kotlinx.serialization.modules.SerializersModule? variablesSerializersModule); + method public suspend Object? execute(kotlin.coroutines.Continuation>); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.MutationRef withDataDeserializer(kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.modules.SerializersModule? dataSerializersModule); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.MutationRef withVariablesSerializer(NewVariables variables, kotlinx.serialization.SerializationStrategy variablesSerializer, kotlinx.serialization.modules.SerializersModule? variablesSerializersModule); } public interface MutationResult extends com.google.firebase.dataconnect.OperationResult { - method @NonNull public com.google.firebase.dataconnect.MutationRef getRef(); - property @NonNull public abstract com.google.firebase.dataconnect.MutationRef ref; + method public com.google.firebase.dataconnect.MutationRef getRef(); + property public abstract com.google.firebase.dataconnect.MutationRef ref; } public interface OperationRef { - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.OperationRef copy(@NonNull String operationName = this.operationName, @Nullable Variables variables = this.variables, @NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer = this.dataDeserializer, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer = this.variablesSerializer, @NonNull com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType = this.callerSdkType, @Nullable kotlinx.serialization.modules.SerializersModule dataSerializersModule = this.dataSerializersModule, @Nullable kotlinx.serialization.modules.SerializersModule variablesSerializersModule = this.variablesSerializersModule); - method public boolean equals(@Nullable Object other); - method @Nullable public suspend Object execute(@NonNull kotlin.coroutines.Continuation>); - method @NonNull public com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType getCallerSdkType(); - method @NonNull public com.google.firebase.dataconnect.FirebaseDataConnect getDataConnect(); - method @NonNull public kotlinx.serialization.DeserializationStrategy getDataDeserializer(); - method @Nullable public kotlinx.serialization.modules.SerializersModule getDataSerializersModule(); - method @NonNull public String getOperationName(); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.OperationRef copy(String operationName = this.operationName, Variables variables = this.variables, kotlinx.serialization.DeserializationStrategy dataDeserializer = this.dataDeserializer, kotlinx.serialization.SerializationStrategy variablesSerializer = this.variablesSerializer, com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType = this.callerSdkType, kotlinx.serialization.modules.SerializersModule? dataSerializersModule = this.dataSerializersModule, kotlinx.serialization.modules.SerializersModule? variablesSerializersModule = this.variablesSerializersModule); + method public boolean equals(Object? other); + method public suspend Object? execute(kotlin.coroutines.Continuation>); + method public com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType getCallerSdkType(); + method public com.google.firebase.dataconnect.FirebaseDataConnect getDataConnect(); + method public kotlinx.serialization.DeserializationStrategy getDataDeserializer(); + method public kotlinx.serialization.modules.SerializersModule? getDataSerializersModule(); + method public String getOperationName(); method public Variables getVariables(); - method @NonNull public kotlinx.serialization.SerializationStrategy getVariablesSerializer(); - method @Nullable public kotlinx.serialization.modules.SerializersModule getVariablesSerializersModule(); + method public kotlinx.serialization.SerializationStrategy getVariablesSerializer(); + method public kotlinx.serialization.modules.SerializersModule? getVariablesSerializersModule(); method public int hashCode(); - method @NonNull public String toString(); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.OperationRef withDataDeserializer(@NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @Nullable kotlinx.serialization.modules.SerializersModule dataSerializersModule = this.dataSerializersModule); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.OperationRef withVariablesSerializer(@Nullable NewVariables variables, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer, @Nullable kotlinx.serialization.modules.SerializersModule variablesSerializersModule = this.variablesSerializersModule); - property @NonNull public abstract com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType; - property @NonNull public abstract com.google.firebase.dataconnect.FirebaseDataConnect dataConnect; - property @NonNull public abstract kotlinx.serialization.DeserializationStrategy dataDeserializer; - property @Nullable public abstract kotlinx.serialization.modules.SerializersModule dataSerializersModule; - property @NonNull public abstract String operationName; + method public String toString(); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.OperationRef withDataDeserializer(kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.modules.SerializersModule? dataSerializersModule = this.dataSerializersModule); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.OperationRef withVariablesSerializer(NewVariables variables, kotlinx.serialization.SerializationStrategy variablesSerializer, kotlinx.serialization.modules.SerializersModule? variablesSerializersModule = this.variablesSerializersModule); + property public abstract com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType; + property public abstract com.google.firebase.dataconnect.FirebaseDataConnect dataConnect; + property public abstract kotlinx.serialization.DeserializationStrategy dataDeserializer; + property public abstract kotlinx.serialization.modules.SerializersModule? dataSerializersModule; + property public abstract String operationName; property public abstract Variables variables; - property @NonNull public abstract kotlinx.serialization.SerializationStrategy variablesSerializer; - property @Nullable public abstract kotlinx.serialization.modules.SerializersModule variablesSerializersModule; + property public abstract kotlinx.serialization.SerializationStrategy variablesSerializer; + property public abstract kotlinx.serialization.modules.SerializersModule? variablesSerializersModule; } public interface OperationResult { - method public boolean equals(@Nullable Object other); + method public boolean equals(Object? other); method public Data getData(); - method @NonNull public com.google.firebase.dataconnect.OperationRef getRef(); + method public com.google.firebase.dataconnect.OperationRef getRef(); method public int hashCode(); - method @NonNull public String toString(); + method public String toString(); property public abstract Data data; - property @NonNull public abstract com.google.firebase.dataconnect.OperationRef ref; + property public abstract com.google.firebase.dataconnect.OperationRef ref; } @kotlinx.serialization.Serializable(with=OptionalVariable.Serializer::class) public sealed interface OptionalVariable { - method @Nullable public T valueOrNull(); + method public T? valueOrNull(); method public T valueOrThrow(); } public static final class OptionalVariable.Serializer implements kotlinx.serialization.KSerializer> { - ctor public OptionalVariable.Serializer(@NonNull kotlinx.serialization.KSerializer elementSerializer); - method @NonNull public com.google.firebase.dataconnect.OptionalVariable deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder); - method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); - method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull com.google.firebase.dataconnect.OptionalVariable value); - property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor; + ctor public OptionalVariable.Serializer(kotlinx.serialization.KSerializer elementSerializer); + method public com.google.firebase.dataconnect.OptionalVariable deserialize(kotlinx.serialization.encoding.Decoder decoder); + method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); + method public void serialize(kotlinx.serialization.encoding.Encoder encoder, com.google.firebase.dataconnect.OptionalVariable value); + property public kotlinx.serialization.descriptors.SerialDescriptor descriptor; } public static final class OptionalVariable.Undefined implements com.google.firebase.dataconnect.OptionalVariable { - method @Nullable public Void valueOrNull(); - method @NonNull public Void valueOrThrow(); - field @NonNull public static final com.google.firebase.dataconnect.OptionalVariable.Undefined INSTANCE; + method public Void? valueOrNull(); + method public Void valueOrThrow(); + field public static final com.google.firebase.dataconnect.OptionalVariable.Undefined INSTANCE; } public static final class OptionalVariable.Value implements com.google.firebase.dataconnect.OptionalVariable { - ctor public OptionalVariable.Value(@Nullable T value); + ctor public OptionalVariable.Value(T value); method public T getValue(); method public T valueOrNull(); method public T valueOrThrow(); @@ -217,36 +213,36 @@ package com.google.firebase.dataconnect { } public interface QueryRef extends com.google.firebase.dataconnect.OperationRef { - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.QueryRef copy(@NonNull String operationName, @Nullable Variables variables, @NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer, @NonNull com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType, @Nullable kotlinx.serialization.modules.SerializersModule dataSerializersModule, @Nullable kotlinx.serialization.modules.SerializersModule variablesSerializersModule); - method @Nullable public suspend Object execute(@NonNull kotlin.coroutines.Continuation>); - method @NonNull public com.google.firebase.dataconnect.QuerySubscription subscribe(); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.QueryRef withDataDeserializer(@NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @Nullable kotlinx.serialization.modules.SerializersModule dataSerializersModule); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.QueryRef withVariablesSerializer(@Nullable NewVariables variables, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer, @Nullable kotlinx.serialization.modules.SerializersModule variablesSerializersModule); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.QueryRef copy(String operationName, Variables variables, kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.SerializationStrategy variablesSerializer, com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType callerSdkType, kotlinx.serialization.modules.SerializersModule? dataSerializersModule, kotlinx.serialization.modules.SerializersModule? variablesSerializersModule); + method public suspend Object? execute(kotlin.coroutines.Continuation>); + method public com.google.firebase.dataconnect.QuerySubscription subscribe(); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.QueryRef withDataDeserializer(kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.modules.SerializersModule? dataSerializersModule); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.QueryRef withVariablesSerializer(NewVariables variables, kotlinx.serialization.SerializationStrategy variablesSerializer, kotlinx.serialization.modules.SerializersModule? variablesSerializersModule); } public interface QueryResult extends com.google.firebase.dataconnect.OperationResult { - method @NonNull public com.google.firebase.dataconnect.QueryRef getRef(); - property @NonNull public abstract com.google.firebase.dataconnect.QueryRef ref; + method public com.google.firebase.dataconnect.QueryRef getRef(); + property public abstract com.google.firebase.dataconnect.QueryRef ref; } public interface QuerySubscription { - method public boolean equals(@Nullable Object other); - method @NonNull public kotlinx.coroutines.flow.Flow> getFlow(); - method @NonNull public com.google.firebase.dataconnect.QueryRef getQuery(); + method public boolean equals(Object? other); + method public kotlinx.coroutines.flow.Flow> getFlow(); + method public com.google.firebase.dataconnect.QueryRef getQuery(); method public int hashCode(); - method @NonNull public String toString(); - property @NonNull public abstract kotlinx.coroutines.flow.Flow> flow; - property @NonNull public abstract com.google.firebase.dataconnect.QueryRef query; + method public String toString(); + property public abstract kotlinx.coroutines.flow.Flow> flow; + property public abstract com.google.firebase.dataconnect.QueryRef query; } public interface QuerySubscriptionResult { - method public boolean equals(@Nullable Object other); - method @NonNull public com.google.firebase.dataconnect.QueryRef getQuery(); - method @NonNull public Object getResult(); + method public boolean equals(Object? other); + method public com.google.firebase.dataconnect.QueryRef getQuery(); + method public Object getResult(); method public int hashCode(); - method @NonNull public String toString(); - property @NonNull public abstract com.google.firebase.dataconnect.QueryRef query; - property @NonNull public abstract Object result; + method public String toString(); + property public abstract com.google.firebase.dataconnect.QueryRef query; + property public abstract Object result; } } @@ -254,47 +250,47 @@ package com.google.firebase.dataconnect { package com.google.firebase.dataconnect.generated { public interface GeneratedConnector> { - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public T copy(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect dataConnect = this.dataConnect); - method public boolean equals(@Nullable Object other); - method @NonNull public com.google.firebase.dataconnect.FirebaseDataConnect getDataConnect(); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public T copy(com.google.firebase.dataconnect.FirebaseDataConnect dataConnect = this.dataConnect); + method public boolean equals(Object? other); + method public com.google.firebase.dataconnect.FirebaseDataConnect getDataConnect(); method public int hashCode(); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public java.util.List> mutations(); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public java.util.List> operations(); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public java.util.List> queries(); - method @NonNull public String toString(); - property @NonNull public abstract com.google.firebase.dataconnect.FirebaseDataConnect dataConnect; + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public java.util.List> mutations(); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public java.util.List> operations(); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public java.util.List> queries(); + method public String toString(); + property public abstract com.google.firebase.dataconnect.FirebaseDataConnect dataConnect; } public interface GeneratedMutation, Data, Variables> extends com.google.firebase.dataconnect.generated.GeneratedOperation { - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedMutation copy(@NonNull Connector connector, @NonNull String operationName, @NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer); - method @NonNull public default com.google.firebase.dataconnect.MutationRef ref(@Nullable Variables variables); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedMutation withDataDeserializer(@NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedMutation withVariablesSerializer(@NonNull kotlinx.serialization.SerializationStrategy variablesSerializer); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedMutation copy(Connector connector, String operationName, kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.SerializationStrategy variablesSerializer); + method public default com.google.firebase.dataconnect.MutationRef ref(Variables variables); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedMutation withDataDeserializer(kotlinx.serialization.DeserializationStrategy dataDeserializer); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedMutation withVariablesSerializer(kotlinx.serialization.SerializationStrategy variablesSerializer); } public interface GeneratedOperation, Data, Variables> { - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedOperation copy(@NonNull Connector connector = this.connector, @NonNull String operationName = this.operationName, @NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer = this.dataDeserializer, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer = this.variablesSerializer); - method public boolean equals(@Nullable Object other); - method @NonNull public Connector getConnector(); - method @NonNull public kotlinx.serialization.DeserializationStrategy getDataDeserializer(); - method @NonNull public String getOperationName(); - method @NonNull public kotlinx.serialization.SerializationStrategy getVariablesSerializer(); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedOperation copy(Connector connector = this.connector, String operationName = this.operationName, kotlinx.serialization.DeserializationStrategy dataDeserializer = this.dataDeserializer, kotlinx.serialization.SerializationStrategy variablesSerializer = this.variablesSerializer); + method public boolean equals(Object? other); + method public Connector getConnector(); + method public kotlinx.serialization.DeserializationStrategy getDataDeserializer(); + method public String getOperationName(); + method public kotlinx.serialization.SerializationStrategy getVariablesSerializer(); method public int hashCode(); - method @NonNull public default com.google.firebase.dataconnect.OperationRef ref(@Nullable Variables variables); - method @NonNull public String toString(); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedOperation withDataDeserializer(@NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedOperation withVariablesSerializer(@NonNull kotlinx.serialization.SerializationStrategy variablesSerializer); - property @NonNull public abstract Connector connector; - property @NonNull public abstract kotlinx.serialization.DeserializationStrategy dataDeserializer; - property @NonNull public abstract String operationName; - property @NonNull public abstract kotlinx.serialization.SerializationStrategy variablesSerializer; + method public default com.google.firebase.dataconnect.OperationRef ref(Variables variables); + method public String toString(); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedOperation withDataDeserializer(kotlinx.serialization.DeserializationStrategy dataDeserializer); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedOperation withVariablesSerializer(kotlinx.serialization.SerializationStrategy variablesSerializer); + property public abstract Connector connector; + property public abstract kotlinx.serialization.DeserializationStrategy dataDeserializer; + property public abstract String operationName; + property public abstract kotlinx.serialization.SerializationStrategy variablesSerializer; } public interface GeneratedQuery, Data, Variables> extends com.google.firebase.dataconnect.generated.GeneratedOperation { - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedQuery copy(@NonNull Connector connector, @NonNull String operationName, @NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer, @NonNull kotlinx.serialization.SerializationStrategy variablesSerializer); - method @NonNull public default com.google.firebase.dataconnect.QueryRef ref(@Nullable Variables variables); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedQuery withDataDeserializer(@NonNull kotlinx.serialization.DeserializationStrategy dataDeserializer); - method @NonNull @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedQuery withVariablesSerializer(@NonNull kotlinx.serialization.SerializationStrategy variablesSerializer); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedQuery copy(Connector connector, String operationName, kotlinx.serialization.DeserializationStrategy dataDeserializer, kotlinx.serialization.SerializationStrategy variablesSerializer); + method public default com.google.firebase.dataconnect.QueryRef ref(Variables variables); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedQuery withDataDeserializer(kotlinx.serialization.DeserializationStrategy dataDeserializer); + method @com.google.firebase.dataconnect.ExperimentalFirebaseDataConnect public com.google.firebase.dataconnect.generated.GeneratedQuery withVariablesSerializer(kotlinx.serialization.SerializationStrategy variablesSerializer); } } @@ -302,51 +298,51 @@ package com.google.firebase.dataconnect.generated { package com.google.firebase.dataconnect.serializers { public final class AnyValueSerializer implements kotlinx.serialization.KSerializer { - method @NonNull public com.google.firebase.dataconnect.AnyValue deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder); - method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); - method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull com.google.firebase.dataconnect.AnyValue value); - property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor; - field @NonNull public static final com.google.firebase.dataconnect.serializers.AnyValueSerializer INSTANCE; + method public com.google.firebase.dataconnect.AnyValue deserialize(kotlinx.serialization.encoding.Decoder decoder); + method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); + method public void serialize(kotlinx.serialization.encoding.Encoder encoder, com.google.firebase.dataconnect.AnyValue value); + property public kotlinx.serialization.descriptors.SerialDescriptor descriptor; + field public static final com.google.firebase.dataconnect.serializers.AnyValueSerializer INSTANCE; } public final class JavaTimeLocalDateSerializer implements kotlinx.serialization.KSerializer { - method @NonNull public java.time.LocalDate deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder); - method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); - method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull java.time.LocalDate value); - property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor; - field @NonNull public static final com.google.firebase.dataconnect.serializers.JavaTimeLocalDateSerializer INSTANCE; + method public java.time.LocalDate deserialize(kotlinx.serialization.encoding.Decoder decoder); + method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); + method public void serialize(kotlinx.serialization.encoding.Encoder encoder, java.time.LocalDate value); + property public kotlinx.serialization.descriptors.SerialDescriptor descriptor; + field public static final com.google.firebase.dataconnect.serializers.JavaTimeLocalDateSerializer INSTANCE; } public final class KotlinxDatetimeLocalDateSerializer implements kotlinx.serialization.KSerializer { - method @NonNull public kotlinx.datetime.LocalDate deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder); - method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); - method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull kotlinx.datetime.LocalDate value); - property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor; - field @NonNull public static final com.google.firebase.dataconnect.serializers.KotlinxDatetimeLocalDateSerializer INSTANCE; + method public kotlinx.datetime.LocalDate deserialize(kotlinx.serialization.encoding.Decoder decoder); + method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); + method public void serialize(kotlinx.serialization.encoding.Encoder encoder, kotlinx.datetime.LocalDate value); + property public kotlinx.serialization.descriptors.SerialDescriptor descriptor; + field public static final com.google.firebase.dataconnect.serializers.KotlinxDatetimeLocalDateSerializer INSTANCE; } public final class LocalDateSerializer implements kotlinx.serialization.KSerializer { - method @NonNull public com.google.firebase.dataconnect.LocalDate deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder); - method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); - method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull com.google.firebase.dataconnect.LocalDate value); - property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor; - field @NonNull public static final com.google.firebase.dataconnect.serializers.LocalDateSerializer INSTANCE; + method public com.google.firebase.dataconnect.LocalDate deserialize(kotlinx.serialization.encoding.Decoder decoder); + method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); + method public void serialize(kotlinx.serialization.encoding.Encoder encoder, com.google.firebase.dataconnect.LocalDate value); + property public kotlinx.serialization.descriptors.SerialDescriptor descriptor; + field public static final com.google.firebase.dataconnect.serializers.LocalDateSerializer INSTANCE; } public final class TimestampSerializer implements kotlinx.serialization.KSerializer { - method @NonNull public com.google.firebase.Timestamp deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder); - method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); - method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull com.google.firebase.Timestamp value); - property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor; - field @NonNull public static final com.google.firebase.dataconnect.serializers.TimestampSerializer INSTANCE; + method public com.google.firebase.Timestamp deserialize(kotlinx.serialization.encoding.Decoder decoder); + method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); + method public void serialize(kotlinx.serialization.encoding.Encoder encoder, com.google.firebase.Timestamp value); + property public kotlinx.serialization.descriptors.SerialDescriptor descriptor; + field public static final com.google.firebase.dataconnect.serializers.TimestampSerializer INSTANCE; } public final class UUIDSerializer implements kotlinx.serialization.KSerializer { - method @NonNull public java.util.UUID deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder); - method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); - method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull java.util.UUID value); - property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor; - field @NonNull public static final com.google.firebase.dataconnect.serializers.UUIDSerializer INSTANCE; + method public java.util.UUID deserialize(kotlinx.serialization.encoding.Decoder decoder); + method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor(); + method public void serialize(kotlinx.serialization.encoding.Encoder encoder, java.util.UUID value); + property public kotlinx.serialization.descriptors.SerialDescriptor descriptor; + field public static final com.google.firebase.dataconnect.serializers.UUIDSerializer INSTANCE; } } diff --git a/firebase-dataconnect/gradle.properties b/firebase-dataconnect/gradle.properties index 61e7407c387..6cf883fd07e 100644 --- a/firebase-dataconnect/gradle.properties +++ b/firebase-dataconnect/gradle.properties @@ -1,2 +1,2 @@ -version=16.0.0-beta04 -latestReleasedVersion=16.0.0-beta03 +version=16.0.0-beta05 +latestReleasedVersion=16.0.0-beta04 diff --git a/firebase-dataconnect/gradleplugin/plugin/src/main/resources/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersions.json b/firebase-dataconnect/gradleplugin/plugin/src/main/resources/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersions.json index 58d4e6fae79..1854796df5e 100644 --- a/firebase-dataconnect/gradleplugin/plugin/src/main/resources/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersions.json +++ b/firebase-dataconnect/gradleplugin/plugin/src/main/resources/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersions.json @@ -1,5 +1,5 @@ { - "defaultVersion": "1.7.4", + "defaultVersion": "1.7.7", "versions": [ { "version": "1.3.4", @@ -360,6 +360,60 @@ "os": "linux", "size": 25190552, "sha512DigestHex": "abbfe1f4973c0bc0f6a89d618886ccbb09bd8fd34b66c142a67c9899b70b600ebbd5f96f15a65fc78096b3adbb18708786b181ba7f51bc85314b62888ba01293" + }, + { + "version": "1.7.5", + "os": "windows", + "size": 25711616, + "sha512DigestHex": "175a3ffe2ba35ffd9650efd05da4011109d1fd6edd6e933a493eb7cb0bbc219713b745cc7d246df3b0db5bf12caeb4e4e3f2f2be6d191680761d0b14b2efe3d5" + }, + { + "version": "1.7.5", + "os": "macos", + "size": 25281280, + "sha512DigestHex": "887f09b2429f516d615f70343f9b74c44af5ad58b71dff8759bddd99da58280bab3ba6c5af130eb1a696634b62df1d81b8fda7557a5dcb6cb98b5485c68e483c" + }, + { + "version": "1.7.5", + "os": "linux", + "size": 25190552, + "sha512DigestHex": "14ec28595c1ebb2a7870e09c00c6401bebbe8b3ae56cdcc63e34cb86c1b0ec7ab359a16fb7d93f19b552343b0bd004f0990b847abee0b5feb19a720d48548432" + }, + { + "version": "1.7.6", + "os": "windows", + "size": 25752064, + "sha512DigestHex": "c0541157251660c4fede1b021042f54ce15d885c4e2689316aaebf7903e6783e531b0c632f20dbe43b996e6a63dfa7d49c7b03d5c932248fcb95f3767a9ab4ac" + }, + { + "version": "1.7.6", + "os": "macos", + "size": 25322240, + "sha512DigestHex": "5e9142bbcc4475a905c4b2e999998d7c450ec0852049012d8eefca7f264a1c880e9872d6fbb59fe770dd9388268b637a2011641173028bbbd4ddad71e8028d62" + }, + { + "version": "1.7.6", + "os": "linux", + "size": 25235608, + "sha512DigestHex": "f55a259fdeaba503ff0f5202fbb2062619c14bc140c4c220d10c395bf8a587bc4352064d5f0800d52cb085d951460ebffdad26cfeb893894e655d0d74056e998" + }, + { + "version": "1.7.7", + "os": "windows", + "size": 25788416, + "sha512DigestHex": "be114a86491cf0317e25437777febce6b2057ea4c1a4b1d26939d188091c3b1da19aeed7fcaa5085c687497842ee8330e1b623686899e0c7615340faa2b6eeff" + }, + { + "version": "1.7.7", + "os": "macos", + "size": 25359104, + "sha512DigestHex": "009ade041a6c152b9657add2ad03f5e12058224679eef39cabeee0579c907a9be22e9ad212515e52491fbacd0aa477e6afd1684c0cddea037a76ba143ddc1b32" + }, + { + "version": "1.7.7", + "os": "linux", + "size": 25268376, + "sha512DigestHex": "f55feb1ce670b4728bb30be138ab427545f77f63f9e11ee458096091c075699c647d5b768c642a1ef6b3569a2db87dbbed6f2fdaf64febd1154d1a730fda4a9c" } ] } \ No newline at end of file diff --git a/firebase-dataconnect/scripts/missingversions.py b/firebase-dataconnect/scripts/missingversions.py new file mode 100755 index 00000000000..80ee9ecfe58 --- /dev/null +++ b/firebase-dataconnect/scripts/missingversions.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +# Copyright 2025 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. + +# Run this script in the root directory of this Git repository to +# determine the versions of the Data Connect Toolkit that are missing +# from the DataConnectExecutableVersions.json file. The final output +# of this script will be the gradle command to run to update the json +# file. +# +# Make sure to run "pip install packaging" before running this script. + +import json +import os +from packaging.version import Version +import re +import subprocess +import tempfile + +regex = re.compile(r".*dataconnect-emulator-linux-v(\d+\.\d+\.\d+)") +json_path = os.path.abspath("firebase-dataconnect/gradleplugin/plugin/src/main/resources/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersions.json") +min_version = Version("1.3.4") +bucket = "gs://firemat-preview-drop/emulator/" + +args = ["gsutil", "ls", "-r", bucket] +print("Getting versions by running: " + subprocess.list2cmdline(args)) +with tempfile.TemporaryFile() as f: + subprocess.check_call(args, stdout=f) + f.seek(0) + filenames = f.read().decode("utf8", errors="strict").splitlines() + +filename_matches = [regex.fullmatch(filename) for filename in filenames] +versions_set = set(match.group(1) for match in filename_matches if match is not None) +all_versions = sorted(versions_set, key=Version) +versions = [version for version in all_versions if Version(version) >= min_version] + +try: + invalid_version_index = versions.index("1.15.0") +except ValueError: + pass +else: + versions.pop(invalid_version_index) + +print(f"Found {len(versions)} versions greater than {min_version}: {versions!r}") +print() + +with open(json_path, "rb") as f: + known_versions_map = json.load(f) +known_versions_set = frozenset(version_info["version"] for version_info in known_versions_map["versions"]) +known_versions = sorted(known_versions_set, key=Version) +print(f"Found {len(known_versions)} versions in {os.path.basename(json_path)}: {known_versions!r}") +print() + +missing_versions = [version for version in versions if version not in known_versions] +print(f"Found {len(missing_versions)} missing versions in {os.path.basename(json_path)}: {missing_versions!r}") +print() + +print(f"Run this gradle command to update {json_path}:") +print(f"./gradlew :firebase-dataconnect:connectors:updateJson -Pversions={",".join(missing_versions)} -PdefaultVersion={versions[-1]}") diff --git a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/QueryRefIntegrationTest.kt b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/QueryRefIntegrationTest.kt index efcd75d2aab..5e0527a6082 100644 --- a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/QueryRefIntegrationTest.kt +++ b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/QueryRefIntegrationTest.kt @@ -31,7 +31,7 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.kotest.property.Arb import io.kotest.property.arbitrary.next -import kotlin.time.Duration.Companion.seconds +import kotlin.time.Duration.Companion.minutes import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.test.runTest @@ -388,7 +388,7 @@ class QueryRefIntegrationTest : DataConnectIntegrationTestBase() { @Test fun executeShouldSupportMassiveConcurrency() = - runTest(timeout = 60.seconds) { + runTest(timeout = 5.minutes) { val latch = SuspendingCountDownLatch(25_000) val query = personSchema.getPerson(id = "foo") diff --git a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/QuerySubscriptionIntegrationTest.kt b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/QuerySubscriptionIntegrationTest.kt index 908382ccdcc..6b0f0c4ff47 100644 --- a/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/QuerySubscriptionIntegrationTest.kt +++ b/firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/QuerySubscriptionIntegrationTest.kt @@ -303,7 +303,7 @@ class QuerySubscriptionIntegrationTest : DataConnectIntegrationTestBase() { .toList() assertSoftly { - withClue("results.size") { results.size shouldBeInRange 1..2000 } + withClue("results.size") { results.size shouldBeInRange 1..5000 } results.forEachIndexed { i, result -> withClue("results[$i]") { result.shouldHavePersonWithName("NewName") } } diff --git a/firebase-dataconnect/testutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/javatime.kt b/firebase-dataconnect/testutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/javatime.kt index 12f780d2883..a826f1d9038 100644 --- a/firebase-dataconnect/testutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/javatime.kt +++ b/firebase-dataconnect/testutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/javatime.kt @@ -24,6 +24,7 @@ import com.google.firebase.dataconnect.testutil.property.arbitrary.JavaTimeEdgeC import com.google.firebase.dataconnect.testutil.property.arbitrary.JavaTimeEdgeCases.MIN_NANO import com.google.firebase.dataconnect.testutil.property.arbitrary.JavaTimeEdgeCases.MIN_YEAR import com.google.firebase.dataconnect.testutil.toTimestamp +import io.kotest.common.mapError import io.kotest.property.Arb import io.kotest.property.arbitrary.arbitrary import io.kotest.property.arbitrary.choice @@ -31,7 +32,6 @@ import io.kotest.property.arbitrary.enum import io.kotest.property.arbitrary.int import io.kotest.property.arbitrary.of import io.kotest.property.arbitrary.orNull -import kotlin.random.nextInt import org.threeten.bp.Instant import org.threeten.bp.OffsetDateTime import org.threeten.bp.ZoneOffset @@ -153,7 +153,11 @@ private fun Instant.toFdcFieldRegex(): Regex { return Regex(pattern) } -data class Nanoseconds(val nanoseconds: Int, val string: String) +data class Nanoseconds( + val nanoseconds: Int, + val string: String, + val digitCounts: JavaTimeArbs.NanosecondComponents +) sealed interface TimeOffset { @@ -177,8 +181,12 @@ sealed interface TimeOffset { data class HhMm(val hours: Int, val minutes: Int, val sign: Sign) : TimeOffset { init { - require(hours in 0..18) { "invalid hours: $hours (must be in the closed range 0..23)" } - require(minutes in 0..59) { "invalid minutes: $minutes (must be in the closed range 0..59)" } + require(hours in validHours) { + "invalid hours: $hours (must be in the closed range $validHours)" + } + require(minutes in validMinutes) { + "invalid minutes: $minutes (must be in the closed range $validMinutes)" + } require(hours != 18 || minutes == 0) { "invalid minutes: $minutes (must be 0 when hours=18)" } } @@ -192,15 +200,44 @@ sealed interface TimeOffset { append("$minutes".padStart(2, '0')) } + fun toSeconds(): Int { + val absValue = (hours * SECONDS_PER_HOUR) + (minutes * SECONDS_PER_MINUTE) + return when (sign) { + Sign.Positive -> absValue + Sign.Negative -> -absValue + } + } + override fun toString() = "HhMm(hours=$hours, minutes=$minutes, sign=$sign, " + "zoneOffset=$zoneOffset, rfc3339String=$rfc3339String)" + operator fun compareTo(other: HhMm): Int = toSeconds() - other.toSeconds() + @Suppress("unused") enum class Sign(val char: Char, val multiplier: Int) { Positive('+', 1), Negative('-', -1), } + + companion object { + private const val SECONDS_PER_MINUTE: Int = 60 + private const val SECONDS_PER_HOUR: Int = 60 * SECONDS_PER_MINUTE + + val validHours = 0..18 + val validMinutes = 0..59 + + val maxSeconds: Int = 18 * SECONDS_PER_HOUR + + fun forSeconds(seconds: Int, sign: Sign): HhMm { + require(seconds in 0..maxSeconds) { + "invalid seconds: $seconds (must be between 0 and $maxSeconds, inclusive)" + } + val hours = seconds / SECONDS_PER_HOUR + val minutes = (seconds - (hours * SECONDS_PER_HOUR)) / SECONDS_PER_MINUTE + return HhMm(hours = hours, minutes = minutes, sign = sign) + } + } } } @@ -215,7 +252,6 @@ object JavaTimeArbs { val minuteArb = minute() val secondArb = second() val nanosecondArb = nanosecond().orNull(nullProbability = 0.15) - val timeOffsetArb = timeOffset() return arbitrary(JavaTimeInstantEdgeCases.all) { val year = yearArb.bind() @@ -226,7 +262,55 @@ object JavaTimeArbs { val minute = minuteArb.bind() val second = secondArb.bind() val nanosecond = nanosecondArb.bind() - val timeOffset = timeOffsetArb.bind() + + val instantUtc = + OffsetDateTime.of( + year, + month, + day, + hour, + minute, + second, + nanosecond?.nanoseconds ?: 0, + ZoneOffset.UTC, + ) + .toInstant() + + // The valid range below was copied from: + // com.google.firebase.Timestamp.Timestamp.validateRange() 253_402_300_800 + val validEpochSecondRange = -62_135_596_800..253_402_300_800 + + val numSecondsBelowMaxEpochSecond = validEpochSecondRange.last - instantUtc.epochSecond + require(numSecondsBelowMaxEpochSecond > 0) { + "internal error gh98nqedss: " + + "invalid numSecondsBelowMaxEpochSecond: $numSecondsBelowMaxEpochSecond" + } + val minTimeZoneOffset = + if (numSecondsBelowMaxEpochSecond >= TimeOffset.HhMm.maxSeconds) { + null + } else { + TimeOffset.HhMm.forSeconds( + numSecondsBelowMaxEpochSecond.toInt(), + TimeOffset.HhMm.Sign.Negative + ) + } + + val numSecondsAboveMinEpochSecond = instantUtc.epochSecond - validEpochSecondRange.first + require(numSecondsAboveMinEpochSecond > 0) { + "internal error mje6a4mrbm: " + + "invalid numSecondsAboveMinEpochSecond: $numSecondsAboveMinEpochSecond" + } + val maxTimeZoneOffset = + if (numSecondsAboveMinEpochSecond >= TimeOffset.HhMm.maxSeconds) { + null + } else { + TimeOffset.HhMm.forSeconds( + numSecondsAboveMinEpochSecond.toInt(), + TimeOffset.HhMm.Sign.Positive + ) + } + + val timeOffset = timeOffset(min = minTimeZoneOffset, max = maxTimeZoneOffset).bind() val instant = OffsetDateTime.of( @@ -241,6 +325,27 @@ object JavaTimeArbs { ) .toInstant() + require(instant.epochSecond >= validEpochSecondRange.first) { + "internal error weppxzqj2y: " + + "instant.epochSecond out of range by " + + "${validEpochSecondRange.first - instant.epochSecond}: ${instant.epochSecond} (" + + "validEpochSecondRange.first=${validEpochSecondRange.first}, " + + "year=$year, month=$month, day=$day, " + + "hour=$hour, minute=$minute, second=$second, " + + "nanosecond=$nanosecond timeOffset=$timeOffset, " + + "minTimeZoneOffset=$minTimeZoneOffset, maxTimeZoneOffset=$maxTimeZoneOffset)" + } + require(instant.epochSecond <= validEpochSecondRange.last) { + "internal error yxga5xy9bm: " + + "instant.epochSecond out of range by " + + "${instant.epochSecond - validEpochSecondRange.last}: ${instant.epochSecond} (" + + "validEpochSecondRange.last=${validEpochSecondRange.last}, " + + "year=$year, month=$month, day=$day, " + + "hour=$hour, minute=$minute, second=$second, " + + "nanosecond=$nanosecond timeOffset=$timeOffset, " + + "minTimeZoneOffset=$minTimeZoneOffset, maxTimeZoneOffset=$maxTimeZoneOffset)" + } + val string = buildString { append(year) append('-') @@ -268,7 +373,10 @@ object JavaTimeArbs { } } - fun timeOffset(): Arb = Arb.choice(timeOffsetUtc(), timeOffsetHhMm()) + fun timeOffset( + min: TimeOffset.HhMm?, + max: TimeOffset.HhMm?, + ): Arb = Arb.choice(timeOffsetUtc(), timeOffsetHhMm(min = min, max = max)) fun timeOffsetUtc( case: Arb = Arb.enum(), @@ -278,20 +386,45 @@ object JavaTimeArbs { sign: Arb = Arb.enum(), hour: Arb = Arb.positiveIntWithUniformNumDigitsProbability(0..18), minute: Arb = minute(), - ): Arb = - arbitrary( + min: TimeOffset.HhMm?, + max: TimeOffset.HhMm?, + ): Arb { + require(min === null || max === null || min.toSeconds() < max.toSeconds()) { + "min must be strictly less than max, but got: " + + "min=$min (${min!!.toSeconds()} seconds), " + + "max=$max (${max!!.toSeconds()} seconds), " + + "a difference of ${min.toSeconds() - max.toSeconds()} seconds" + } + + fun isBetweenMinAndMax(other: TimeOffset.HhMm): Boolean = + (min === null || other >= min) && (max === null || other <= max) + + return arbitrary( edgecases = listOf( - TimeOffset.HhMm(hours = 0, minutes = 0, sign = TimeOffset.HhMm.Sign.Positive), - TimeOffset.HhMm(hours = 0, minutes = 0, sign = TimeOffset.HhMm.Sign.Negative), - TimeOffset.HhMm(hours = 17, minutes = 59, sign = TimeOffset.HhMm.Sign.Positive), - TimeOffset.HhMm(hours = 17, minutes = 59, sign = TimeOffset.HhMm.Sign.Negative), - TimeOffset.HhMm(hours = 18, minutes = 0, sign = TimeOffset.HhMm.Sign.Positive), - TimeOffset.HhMm(hours = 18, minutes = 0, sign = TimeOffset.HhMm.Sign.Negative), - ) + TimeOffset.HhMm(hours = 0, minutes = 0, sign = TimeOffset.HhMm.Sign.Positive), + TimeOffset.HhMm(hours = 0, minutes = 0, sign = TimeOffset.HhMm.Sign.Negative), + TimeOffset.HhMm(hours = 17, minutes = 59, sign = TimeOffset.HhMm.Sign.Positive), + TimeOffset.HhMm(hours = 17, minutes = 59, sign = TimeOffset.HhMm.Sign.Negative), + TimeOffset.HhMm(hours = 18, minutes = 0, sign = TimeOffset.HhMm.Sign.Positive), + TimeOffset.HhMm(hours = 18, minutes = 0, sign = TimeOffset.HhMm.Sign.Negative), + ) + .filter(::isBetweenMinAndMax) ) { - TimeOffset.HhMm(hours = hour.bind(), minutes = minute.bind(), sign = sign.bind()) + var count = 0 + var hhmm: TimeOffset.HhMm + while (true) { + count++ + hhmm = TimeOffset.HhMm(hours = hour.bind(), minutes = minute.bind(), sign = sign.bind()) + if (isBetweenMinAndMax(hhmm)) { + break + } else if (count > 1000) { + throw Exception("internal error j878fp4gmr: exhausted attempts to generate HhMm") + } + } + hhmm } + } fun year(): Arb = Arb.int(MIN_YEAR..MAX_YEAR) @@ -316,8 +449,12 @@ object JavaTimeArbs { repeat(digitCounts.leadingZeroes) { append('0') } if (digitCounts.proper > 0) { append(nonZeroDigits.bind()) - repeat(digitCounts.proper - 2) { append(digits.bind()) } - append(nonZeroDigits.bind()) + if (digitCounts.proper > 1) { + if (digitCounts.proper > 2) { + repeat(digitCounts.proper - 2) { append(digits.bind()) } + } + append(nonZeroDigits.bind()) + } } repeat(digitCounts.trailingZeroes) { append('0') } } @@ -327,18 +464,29 @@ object JavaTimeArbs { if (nanosecondsStringTrimmed.isEmpty()) { 0 } else { - nanosecondsStringTrimmed.toInt() + val toIntResult = nanosecondsStringTrimmed.runCatching { toInt() } + toIntResult.mapError { exception -> + Exception( + "internal error qbdgapmye2: " + + "failed to parse nanosecondsStringTrimmed as an int: " + + "\"$nanosecondsStringTrimmed\" (digitCounts=$digitCounts)", + exception + ) + } + toIntResult.getOrThrow() } - Nanoseconds(nanosecondsInt, nanosecondsString) + check(nanosecondsInt in 0..999_999_999) { + "internal error c7j2myw6bd: " + + "nanosecondsStringTrimmed parsed to a value outside the valid range: " + + "$nanosecondsInt (digitCounts=$digitCounts)" + } + + Nanoseconds(nanosecondsInt, nanosecondsString, digitCounts) } } - private data class NanosecondComponents( - val leadingZeroes: Int, - val proper: Int, - val trailingZeroes: Int - ) + data class NanosecondComponents(val leadingZeroes: Int, val proper: Int, val trailingZeroes: Int) private fun nanosecondComponents(): Arb = arbitrary( diff --git a/firebase-datatransport/api.txt b/firebase-datatransport/api.txt index d802177e249..1a515c03c5b 100644 --- a/firebase-datatransport/api.txt +++ b/firebase-datatransport/api.txt @@ -1 +1,16 @@ -// Signature format: 2.0 +// Signature format: 3.0 +package com.google.firebase.datatransport { + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) @javax.inject.Qualifier public @interface LegacyTransportBackend { + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) @javax.inject.Qualifier public @interface TransportBackend { + } + + @Keep public class TransportRegistrar implements com.google.firebase.components.ComponentRegistrar { + ctor public TransportRegistrar(); + method public java.util.List!> getComponents(); + } + +} + diff --git a/firebase-dynamic-links/CHANGELOG.md b/firebase-dynamic-links/CHANGELOG.md index 5081085cd1b..594f75d18ff 100644 --- a/firebase-dynamic-links/CHANGELOG.md +++ b/firebase-dynamic-links/CHANGELOG.md @@ -2,7 +2,8 @@ # 22.1.0 -* [changed] Added deprecation annotations to the public API. See https://firebase.google.com/support/dynamic-links-faq for further context. +* [deprecated] `firebase-dynamic-links` is deprecated. For information about timelines and alternatives, + see the [irebase-dynamic-links deprecation FAQ](/support/dynamic-links-faq). ## Kotlin diff --git a/firebase-dynamic-links/api.txt b/firebase-dynamic-links/api.txt index ff50754c965..9db51ec1155 100644 --- a/firebase-dynamic-links/api.txt +++ b/firebase-dynamic-links/api.txt @@ -1,172 +1,172 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.dynamiclinks { - public final class DynamicLink { - method @NonNull public android.net.Uri getUri(); + @Deprecated public final class DynamicLink { + method @Deprecated public android.net.Uri getUri(); } - public static final class DynamicLink.AndroidParameters { + @Deprecated public static final class DynamicLink.AndroidParameters { } - public static final class DynamicLink.AndroidParameters.Builder { - ctor public DynamicLink.AndroidParameters.Builder(); - ctor public DynamicLink.AndroidParameters.Builder(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.AndroidParameters build(); - method @NonNull public android.net.Uri getFallbackUrl(); - method public int getMinimumVersion(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.AndroidParameters.Builder setFallbackUrl(@NonNull android.net.Uri); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.AndroidParameters.Builder setMinimumVersion(int); + @Deprecated public static final class DynamicLink.AndroidParameters.Builder { + ctor @Deprecated public DynamicLink.AndroidParameters.Builder(); + ctor @Deprecated public DynamicLink.AndroidParameters.Builder(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.AndroidParameters build(); + method @Deprecated public android.net.Uri getFallbackUrl(); + method @Deprecated public int getMinimumVersion(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.AndroidParameters.Builder setFallbackUrl(android.net.Uri); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.AndroidParameters.Builder setMinimumVersion(int); } - public static final class DynamicLink.Builder { - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink buildDynamicLink(); - method @NonNull public com.google.android.gms.tasks.Task buildShortDynamicLink(); - method @NonNull public com.google.android.gms.tasks.Task buildShortDynamicLink(@com.google.firebase.dynamiclinks.ShortDynamicLink.Suffix int); - method @NonNull public String getDomainUriPrefix(); - method @NonNull public android.net.Uri getLink(); - method @NonNull public android.net.Uri getLongLink(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setAndroidParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.AndroidParameters); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setDomainUriPrefix(@NonNull String); - method @Deprecated @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setDynamicLinkDomain(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setGoogleAnalyticsParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setIosParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.IosParameters); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setItunesConnectAnalyticsParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setLink(@NonNull android.net.Uri); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setLongLink(@NonNull android.net.Uri); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setNavigationInfoParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.NavigationInfoParameters); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.Builder setSocialMetaTagParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters); + @Deprecated public static final class DynamicLink.Builder { + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink buildDynamicLink(); + method @Deprecated public com.google.android.gms.tasks.Task buildShortDynamicLink(); + method @Deprecated public com.google.android.gms.tasks.Task buildShortDynamicLink(@com.google.firebase.dynamiclinks.ShortDynamicLink.Suffix int); + method @Deprecated public String getDomainUriPrefix(); + method @Deprecated public android.net.Uri getLink(); + method @Deprecated public android.net.Uri getLongLink(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setAndroidParameters(com.google.firebase.dynamiclinks.DynamicLink.AndroidParameters); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setDomainUriPrefix(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setDynamicLinkDomain(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setGoogleAnalyticsParameters(com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setIosParameters(com.google.firebase.dynamiclinks.DynamicLink.IosParameters); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setItunesConnectAnalyticsParameters(com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setLink(android.net.Uri); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setLongLink(android.net.Uri); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setNavigationInfoParameters(com.google.firebase.dynamiclinks.DynamicLink.NavigationInfoParameters); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.Builder setSocialMetaTagParameters(com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters); } - public static final class DynamicLink.GoogleAnalyticsParameters { + @Deprecated public static final class DynamicLink.GoogleAnalyticsParameters { } - public static final class DynamicLink.GoogleAnalyticsParameters.Builder { - ctor public DynamicLink.GoogleAnalyticsParameters.Builder(); - ctor public DynamicLink.GoogleAnalyticsParameters.Builder(@NonNull String, @NonNull String, @NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters build(); - method @NonNull public String getCampaign(); - method @NonNull public String getContent(); - method @NonNull public String getMedium(); - method @NonNull public String getSource(); - method @NonNull public String getTerm(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setCampaign(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setContent(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setMedium(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setSource(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setTerm(@NonNull String); + @Deprecated public static final class DynamicLink.GoogleAnalyticsParameters.Builder { + ctor @Deprecated public DynamicLink.GoogleAnalyticsParameters.Builder(); + ctor @Deprecated public DynamicLink.GoogleAnalyticsParameters.Builder(String, String, String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters build(); + method @Deprecated public String getCampaign(); + method @Deprecated public String getContent(); + method @Deprecated public String getMedium(); + method @Deprecated public String getSource(); + method @Deprecated public String getTerm(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setCampaign(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setContent(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setMedium(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setSource(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.GoogleAnalyticsParameters.Builder setTerm(String); } - public static final class DynamicLink.IosParameters { + @Deprecated public static final class DynamicLink.IosParameters { } - public static final class DynamicLink.IosParameters.Builder { - ctor public DynamicLink.IosParameters.Builder(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.IosParameters build(); - method @NonNull public String getAppStoreId(); - method @NonNull public String getCustomScheme(); - method @NonNull public String getIpadBundleId(); - method @NonNull public android.net.Uri getIpadFallbackUrl(); - method @NonNull public String getMinimumVersion(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setAppStoreId(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setCustomScheme(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setFallbackUrl(@NonNull android.net.Uri); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setIpadBundleId(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setIpadFallbackUrl(@NonNull android.net.Uri); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setMinimumVersion(@NonNull String); + @Deprecated public static final class DynamicLink.IosParameters.Builder { + ctor @Deprecated public DynamicLink.IosParameters.Builder(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.IosParameters build(); + method @Deprecated public String getAppStoreId(); + method @Deprecated public String getCustomScheme(); + method @Deprecated public String getIpadBundleId(); + method @Deprecated public android.net.Uri getIpadFallbackUrl(); + method @Deprecated public String getMinimumVersion(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setAppStoreId(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setCustomScheme(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setFallbackUrl(android.net.Uri); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setIpadBundleId(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setIpadFallbackUrl(android.net.Uri); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.IosParameters.Builder setMinimumVersion(String); } - public static final class DynamicLink.ItunesConnectAnalyticsParameters { + @Deprecated public static final class DynamicLink.ItunesConnectAnalyticsParameters { } - public static final class DynamicLink.ItunesConnectAnalyticsParameters.Builder { - ctor public DynamicLink.ItunesConnectAnalyticsParameters.Builder(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters build(); - method @NonNull public String getAffiliateToken(); - method @NonNull public String getCampaignToken(); - method @NonNull public String getProviderToken(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters.Builder setAffiliateToken(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters.Builder setCampaignToken(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters.Builder setProviderToken(@NonNull String); + @Deprecated public static final class DynamicLink.ItunesConnectAnalyticsParameters.Builder { + ctor @Deprecated public DynamicLink.ItunesConnectAnalyticsParameters.Builder(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters build(); + method @Deprecated public String getAffiliateToken(); + method @Deprecated public String getCampaignToken(); + method @Deprecated public String getProviderToken(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters.Builder setAffiliateToken(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters.Builder setCampaignToken(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.ItunesConnectAnalyticsParameters.Builder setProviderToken(String); } - public static final class DynamicLink.NavigationInfoParameters { + @Deprecated public static final class DynamicLink.NavigationInfoParameters { } - public static final class DynamicLink.NavigationInfoParameters.Builder { - ctor public DynamicLink.NavigationInfoParameters.Builder(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.NavigationInfoParameters build(); - method public boolean getForcedRedirectEnabled(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.NavigationInfoParameters.Builder setForcedRedirectEnabled(boolean); + @Deprecated public static final class DynamicLink.NavigationInfoParameters.Builder { + ctor @Deprecated public DynamicLink.NavigationInfoParameters.Builder(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.NavigationInfoParameters build(); + method @Deprecated public boolean getForcedRedirectEnabled(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.NavigationInfoParameters.Builder setForcedRedirectEnabled(boolean); } - public static final class DynamicLink.SocialMetaTagParameters { + @Deprecated public static final class DynamicLink.SocialMetaTagParameters { } - public static final class DynamicLink.SocialMetaTagParameters.Builder { - ctor public DynamicLink.SocialMetaTagParameters.Builder(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters build(); - method @NonNull public String getDescription(); - method @NonNull public android.net.Uri getImageUrl(); - method @NonNull public String getTitle(); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters.Builder setDescription(@NonNull String); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters.Builder setImageUrl(@NonNull android.net.Uri); - method @NonNull public com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters.Builder setTitle(@NonNull String); - } - - public abstract class FirebaseDynamicLinks { - ctor public FirebaseDynamicLinks(); - method @NonNull public abstract com.google.firebase.dynamiclinks.DynamicLink.Builder createDynamicLink(); - method @NonNull public abstract com.google.android.gms.tasks.Task getDynamicLink(@Nullable android.content.Intent); - method @NonNull public abstract com.google.android.gms.tasks.Task getDynamicLink(@NonNull android.net.Uri); - method @NonNull public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks getInstance(); - method @NonNull public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks getInstance(@NonNull com.google.firebase.FirebaseApp); + @Deprecated public static final class DynamicLink.SocialMetaTagParameters.Builder { + ctor @Deprecated public DynamicLink.SocialMetaTagParameters.Builder(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters build(); + method @Deprecated public String getDescription(); + method @Deprecated public android.net.Uri getImageUrl(); + method @Deprecated public String getTitle(); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters.Builder setDescription(String); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters.Builder setImageUrl(android.net.Uri); + method @Deprecated public com.google.firebase.dynamiclinks.DynamicLink.SocialMetaTagParameters.Builder setTitle(String); + } + + @Deprecated public abstract class FirebaseDynamicLinks { + ctor @Deprecated public FirebaseDynamicLinks(); + method @Deprecated public abstract com.google.firebase.dynamiclinks.DynamicLink.Builder createDynamicLink(); + method @Deprecated public abstract com.google.android.gms.tasks.Task getDynamicLink(android.content.Intent?); + method @Deprecated public abstract com.google.android.gms.tasks.Task getDynamicLink(android.net.Uri); + method @Deprecated public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks getInstance(); + method @Deprecated public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks getInstance(com.google.firebase.FirebaseApp); } public final class FirebaseDynamicLinksKt { - method public static void androidParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - method public static void androidParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull String packageName, @NonNull kotlin.jvm.functions.Function1 init); - method @Nullable public static operator android.net.Uri component1(@NonNull com.google.firebase.dynamiclinks.ShortDynamicLink); - method @Nullable public static operator android.net.Uri component1(@NonNull com.google.firebase.dynamiclinks.PendingDynamicLinkData); - method @Nullable public static operator android.net.Uri component2(@NonNull com.google.firebase.dynamiclinks.ShortDynamicLink); - method public static operator int component2(@NonNull com.google.firebase.dynamiclinks.PendingDynamicLinkData); - method @NonNull public static operator java.util.List component3(@NonNull com.google.firebase.dynamiclinks.ShortDynamicLink); - method public static operator long component3(@NonNull com.google.firebase.dynamiclinks.PendingDynamicLinkData); - method @NonNull public static com.google.firebase.dynamiclinks.DynamicLink dynamicLink(@NonNull com.google.firebase.dynamiclinks.FirebaseDynamicLinks, @NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks dynamicLinks(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks getDynamicLinks(@NonNull com.google.firebase.Firebase); - method public static void googleAnalyticsParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - method public static void googleAnalyticsParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull String source, @NonNull String medium, @NonNull String campaign, @NonNull kotlin.jvm.functions.Function1 init); - method public static void iosParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull String bundleId, @NonNull kotlin.jvm.functions.Function1 init); - method public static void itunesConnectAnalyticsParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - method public static void navigationInfoParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.android.gms.tasks.Task shortLinkAsync(@NonNull com.google.firebase.dynamiclinks.FirebaseDynamicLinks, @NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.android.gms.tasks.Task shortLinkAsync(@NonNull com.google.firebase.dynamiclinks.FirebaseDynamicLinks, int suffix, @NonNull kotlin.jvm.functions.Function1 init); - method public static void socialMetaTagParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - } - - public class PendingDynamicLinkData { - ctor protected PendingDynamicLinkData(@Nullable String, int, long, @Nullable android.net.Uri); - method public long getClickTimestamp(); - method @Nullable public android.net.Uri getLink(); - method public int getMinimumAppVersion(); - method @Nullable public android.content.Intent getUpdateAppIntent(@NonNull android.content.Context); - method @NonNull public android.os.Bundle getUtmParameters(); - } - - public interface ShortDynamicLink { - method @Nullable public android.net.Uri getPreviewLink(); - method @Nullable public android.net.Uri getShortLink(); - method @NonNull public java.util.List getWarnings(); - } - - @IntDef({com.google.firebase.dynamiclinks.ShortDynamicLink.Suffix.UNGUESSABLE, com.google.firebase.dynamiclinks.ShortDynamicLink.Suffix.SHORT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ShortDynamicLink.Suffix { - field public static final int SHORT = 2; // 0x2 - field public static final int UNGUESSABLE = 1; // 0x1 - } - - public static interface ShortDynamicLink.Warning { - method @Deprecated @Nullable public String getCode(); - method @Nullable public String getMessage(); + method @Deprecated public static void androidParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, String packageName, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void androidParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + method @Deprecated public static operator android.net.Uri? component1(com.google.firebase.dynamiclinks.PendingDynamicLinkData); + method @Deprecated public static operator android.net.Uri? component1(com.google.firebase.dynamiclinks.ShortDynamicLink); + method @Deprecated public static operator int component2(com.google.firebase.dynamiclinks.PendingDynamicLinkData); + method @Deprecated public static operator android.net.Uri? component2(com.google.firebase.dynamiclinks.ShortDynamicLink); + method @Deprecated public static operator long component3(com.google.firebase.dynamiclinks.PendingDynamicLinkData); + method @Deprecated public static operator java.util.List component3(com.google.firebase.dynamiclinks.ShortDynamicLink); + method @Deprecated public static com.google.firebase.dynamiclinks.DynamicLink dynamicLink(com.google.firebase.dynamiclinks.FirebaseDynamicLinks, kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks dynamicLinks(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); + method @Deprecated public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks getDynamicLinks(com.google.firebase.Firebase); + method @Deprecated public static void googleAnalyticsParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, String source, String medium, String campaign, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void googleAnalyticsParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void iosParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, String bundleId, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void itunesConnectAnalyticsParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void navigationInfoParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.android.gms.tasks.Task shortLinkAsync(com.google.firebase.dynamiclinks.FirebaseDynamicLinks, int suffix, kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.android.gms.tasks.Task shortLinkAsync(com.google.firebase.dynamiclinks.FirebaseDynamicLinks, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void socialMetaTagParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + } + + @Deprecated public class PendingDynamicLinkData { + ctor @Deprecated protected PendingDynamicLinkData(String?, int, long, android.net.Uri?); + method @Deprecated public long getClickTimestamp(); + method @Deprecated public android.net.Uri? getLink(); + method @Deprecated public int getMinimumAppVersion(); + method @Deprecated public android.content.Intent? getUpdateAppIntent(android.content.Context); + method @Deprecated public android.os.Bundle getUtmParameters(); + } + + @Deprecated public interface ShortDynamicLink { + method @Deprecated public android.net.Uri? getPreviewLink(); + method @Deprecated public android.net.Uri? getShortLink(); + method @Deprecated public java.util.List getWarnings(); + } + + @Deprecated @IntDef({com.google.firebase.dynamiclinks.ShortDynamicLink.Suffix.UNGUESSABLE, com.google.firebase.dynamiclinks.ShortDynamicLink.Suffix.SHORT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ShortDynamicLink.Suffix { + field @Deprecated public static final int SHORT = 2; // 0x2 + field @Deprecated public static final int UNGUESSABLE = 1; // 0x1 + } + + @Deprecated public static interface ShortDynamicLink.Warning { + method @Deprecated public String? getCode(); + method @Deprecated public String? getMessage(); } } @@ -174,25 +174,25 @@ package com.google.firebase.dynamiclinks { package com.google.firebase.dynamiclinks.ktx { public final class FirebaseDynamicLinksKt { - method @Deprecated public static void androidParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated public static void androidParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull String packageName, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated @Nullable public static operator android.net.Uri component1(@NonNull com.google.firebase.dynamiclinks.ShortDynamicLink); - method @Deprecated @Nullable public static operator android.net.Uri component1(@NonNull com.google.firebase.dynamiclinks.PendingDynamicLinkData); - method @Deprecated @Nullable public static operator android.net.Uri component2(@NonNull com.google.firebase.dynamiclinks.ShortDynamicLink); - method @Deprecated public static operator int component2(@NonNull com.google.firebase.dynamiclinks.PendingDynamicLinkData); - method @Deprecated @NonNull public static operator java.util.List component3(@NonNull com.google.firebase.dynamiclinks.ShortDynamicLink); - method @Deprecated public static operator long component3(@NonNull com.google.firebase.dynamiclinks.PendingDynamicLinkData); - method @Deprecated @NonNull public static com.google.firebase.dynamiclinks.DynamicLink dynamicLink(@NonNull com.google.firebase.dynamiclinks.FirebaseDynamicLinks, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated @NonNull public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks dynamicLinks(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @Deprecated @NonNull public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks getDynamicLinks(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated public static void googleAnalyticsParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated public static void googleAnalyticsParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull String source, @NonNull String medium, @NonNull String campaign, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated public static void iosParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull String bundleId, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated public static void itunesConnectAnalyticsParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated public static void navigationInfoParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated @NonNull public static com.google.android.gms.tasks.Task shortLinkAsync(@NonNull com.google.firebase.dynamiclinks.FirebaseDynamicLinks, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated @NonNull public static com.google.android.gms.tasks.Task shortLinkAsync(@NonNull com.google.firebase.dynamiclinks.FirebaseDynamicLinks, int suffix, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated public static void socialMetaTagParameters(@NonNull com.google.firebase.dynamiclinks.DynamicLink.Builder, @NonNull kotlin.jvm.functions.Function1 init); + method @Deprecated public static void androidParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, String packageName, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void androidParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + method @Deprecated public static operator android.net.Uri? component1(com.google.firebase.dynamiclinks.PendingDynamicLinkData); + method @Deprecated public static operator android.net.Uri? component1(com.google.firebase.dynamiclinks.ShortDynamicLink); + method @Deprecated public static operator int component2(com.google.firebase.dynamiclinks.PendingDynamicLinkData); + method @Deprecated public static operator android.net.Uri? component2(com.google.firebase.dynamiclinks.ShortDynamicLink); + method @Deprecated public static operator long component3(com.google.firebase.dynamiclinks.PendingDynamicLinkData); + method @Deprecated public static operator java.util.List component3(com.google.firebase.dynamiclinks.ShortDynamicLink); + method @Deprecated public static com.google.firebase.dynamiclinks.DynamicLink dynamicLink(com.google.firebase.dynamiclinks.FirebaseDynamicLinks, kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks dynamicLinks(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); + method @Deprecated public static com.google.firebase.dynamiclinks.FirebaseDynamicLinks getDynamicLinks(com.google.firebase.ktx.Firebase); + method @Deprecated public static void googleAnalyticsParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, String source, String medium, String campaign, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void googleAnalyticsParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void iosParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, String bundleId, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void itunesConnectAnalyticsParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void navigationInfoParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.android.gms.tasks.Task shortLinkAsync(com.google.firebase.dynamiclinks.FirebaseDynamicLinks, int suffix, kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.android.gms.tasks.Task shortLinkAsync(com.google.firebase.dynamiclinks.FirebaseDynamicLinks, kotlin.jvm.functions.Function1 init); + method @Deprecated public static void socialMetaTagParameters(com.google.firebase.dynamiclinks.DynamicLink.Builder, kotlin.jvm.functions.Function1 init); } } diff --git a/firebase-dynamic-links/ktx/api.txt b/firebase-dynamic-links/ktx/api.txt index 448c591bfb5..da4f6cc18fe 100644 --- a/firebase-dynamic-links/ktx/api.txt +++ b/firebase-dynamic-links/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.dynamiclinks.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-firestore/CHANGELOG.md b/firebase-firestore/CHANGELOG.md index 89405972e01..66fce5b35ce 100644 --- a/firebase-firestore/CHANGELOG.md +++ b/firebase-firestore/CHANGELOG.md @@ -1,6 +1,15 @@ # Unreleased +# 25.1.2 +* [fixed] Fixed a server and sdk mismatch in unicode string sorting. [#6615](//github.com/firebase/firebase-android-sdk/pull/6615) + + +## Kotlin +The Kotlin extensions library transitively includes the updated +`firebase-firestore` library. The Kotlin extensions library has no additional +updates. + # 25.1.1 * [changed] Update Firestore proto definitions. [#6369](//github.com/firebase/firebase-android-sdk/pull/6369) * [changed] Updated protobuf dependency to `3.25.5` to fix diff --git a/firebase-firestore/api.txt b/firebase-firestore/api.txt index 3c36326eec6..e3a55cf729c 100644 --- a/firebase-firestore/api.txt +++ b/firebase-firestore/api.txt @@ -1,15 +1,15 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.firestore { public abstract class AggregateField { - method @NonNull public static com.google.firebase.firestore.AggregateField.AverageAggregateField average(@NonNull String); - method @NonNull public static com.google.firebase.firestore.AggregateField.AverageAggregateField average(@NonNull com.google.firebase.firestore.FieldPath); - method @NonNull public static com.google.firebase.firestore.AggregateField.CountAggregateField count(); - method @NonNull @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public String getAlias(); - method @NonNull @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public String getFieldPath(); - method @NonNull @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public String getOperator(); - method @NonNull public static com.google.firebase.firestore.AggregateField.SumAggregateField sum(@NonNull String); - method @NonNull public static com.google.firebase.firestore.AggregateField.SumAggregateField sum(@NonNull com.google.firebase.firestore.FieldPath); + method public static com.google.firebase.firestore.AggregateField.AverageAggregateField average(com.google.firebase.firestore.FieldPath); + method public static com.google.firebase.firestore.AggregateField.AverageAggregateField average(String); + method public static com.google.firebase.firestore.AggregateField.CountAggregateField count(); + method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public String getAlias(); + method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public String getFieldPath(); + method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public String getOperator(); + method public static com.google.firebase.firestore.AggregateField.SumAggregateField sum(com.google.firebase.firestore.FieldPath); + method public static com.google.firebase.firestore.AggregateField.SumAggregateField sum(String); } public static class AggregateField.AverageAggregateField extends com.google.firebase.firestore.AggregateField { @@ -22,45 +22,45 @@ package com.google.firebase.firestore { } public class AggregateQuery { - method @NonNull public com.google.android.gms.tasks.Task get(@NonNull com.google.firebase.firestore.AggregateSource); - method @NonNull @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public java.util.List getAggregateFields(); - method @NonNull public com.google.firebase.firestore.Query getQuery(); + method public com.google.android.gms.tasks.Task get(com.google.firebase.firestore.AggregateSource); + method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public java.util.List getAggregateFields(); + method public com.google.firebase.firestore.Query getQuery(); } public class AggregateQuerySnapshot { - method @Nullable public Object get(@NonNull com.google.firebase.firestore.AggregateField); - method public long get(@NonNull com.google.firebase.firestore.AggregateField.CountAggregateField); - method @Nullable public Double get(@NonNull com.google.firebase.firestore.AggregateField.AverageAggregateField); + method public Object? get(com.google.firebase.firestore.AggregateField); + method public Double? get(com.google.firebase.firestore.AggregateField.AverageAggregateField); + method public long get(com.google.firebase.firestore.AggregateField.CountAggregateField); method public long getCount(); - method @Nullable public Double getDouble(@NonNull com.google.firebase.firestore.AggregateField); - method @Nullable public Long getLong(@NonNull com.google.firebase.firestore.AggregateField); - method @NonNull public com.google.firebase.firestore.AggregateQuery getQuery(); + method public Double? getDouble(com.google.firebase.firestore.AggregateField); + method public Long? getLong(com.google.firebase.firestore.AggregateField); + method public com.google.firebase.firestore.AggregateQuery getQuery(); } public enum AggregateSource { enum_constant public static final com.google.firebase.firestore.AggregateSource SERVER; } - public class Blob implements java.lang.Comparable { - method public int compareTo(@NonNull com.google.firebase.firestore.Blob); - method @NonNull public static com.google.firebase.firestore.Blob fromBytes(@NonNull byte[]); - method @NonNull public byte[] toBytes(); + public class Blob implements java.lang.Comparable { + method public int compareTo(com.google.firebase.firestore.Blob); + method public static com.google.firebase.firestore.Blob fromBytes(byte[]); + method public byte[] toBytes(); } public class CollectionReference extends com.google.firebase.firestore.Query { - method @NonNull public com.google.android.gms.tasks.Task add(@NonNull Object); - method @NonNull public com.google.firebase.firestore.DocumentReference document(); - method @NonNull public com.google.firebase.firestore.DocumentReference document(@NonNull String); - method @NonNull public String getId(); - method @Nullable public com.google.firebase.firestore.DocumentReference getParent(); - method @NonNull public String getPath(); + method public com.google.android.gms.tasks.Task add(Object); + method public com.google.firebase.firestore.DocumentReference document(); + method public com.google.firebase.firestore.DocumentReference document(String); + method public String getId(); + method public com.google.firebase.firestore.DocumentReference? getParent(); + method public String getPath(); } public class DocumentChange { - method @NonNull public com.google.firebase.firestore.QueryDocumentSnapshot getDocument(); + method public com.google.firebase.firestore.QueryDocumentSnapshot getDocument(); method public int getNewIndex(); method public int getOldIndex(); - method @NonNull public com.google.firebase.firestore.DocumentChange.Type getType(); + method public com.google.firebase.firestore.DocumentChange.Type getType(); } public enum DocumentChange.Type { @@ -73,59 +73,59 @@ package com.google.firebase.firestore { } public class DocumentReference { - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull android.app.Activity, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull android.app.Activity, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.SnapshotListenOptions, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.CollectionReference collection(@NonNull String); - method @NonNull public com.google.android.gms.tasks.Task delete(); - method @NonNull public com.google.android.gms.tasks.Task get(); - method @NonNull public com.google.android.gms.tasks.Task get(@NonNull com.google.firebase.firestore.Source); - method @NonNull public com.google.firebase.firestore.FirebaseFirestore getFirestore(); - method @NonNull public String getId(); - method @NonNull public com.google.firebase.firestore.CollectionReference getParent(); - method @NonNull public String getPath(); - method @NonNull public com.google.android.gms.tasks.Task set(@NonNull Object); - method @NonNull public com.google.android.gms.tasks.Task set(@NonNull Object, @NonNull com.google.firebase.firestore.SetOptions); - method @NonNull public com.google.android.gms.tasks.Task update(@NonNull java.util.Map); - method @NonNull public com.google.android.gms.tasks.Task update(@NonNull String, @Nullable Object, java.lang.Object...); - method @NonNull public com.google.android.gms.tasks.Task update(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object, java.lang.Object...); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(android.app.Activity, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(android.app.Activity, com.google.firebase.firestore.MetadataChanges, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(com.google.firebase.firestore.MetadataChanges, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(com.google.firebase.firestore.SnapshotListenOptions, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(java.util.concurrent.Executor, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(java.util.concurrent.Executor, com.google.firebase.firestore.MetadataChanges, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.CollectionReference collection(String); + method public com.google.android.gms.tasks.Task delete(); + method public com.google.android.gms.tasks.Task get(); + method public com.google.android.gms.tasks.Task get(com.google.firebase.firestore.Source); + method public com.google.firebase.firestore.FirebaseFirestore getFirestore(); + method public String getId(); + method public com.google.firebase.firestore.CollectionReference getParent(); + method public String getPath(); + method public com.google.android.gms.tasks.Task set(Object); + method public com.google.android.gms.tasks.Task set(Object, com.google.firebase.firestore.SetOptions); + method public com.google.android.gms.tasks.Task update(com.google.firebase.firestore.FieldPath, Object?, java.lang.Object!...!); + method public com.google.android.gms.tasks.Task update(String, Object?, java.lang.Object!...!); + method public com.google.android.gms.tasks.Task update(java.util.Map); } public class DocumentSnapshot { - method public boolean contains(@NonNull String); - method public boolean contains(@NonNull com.google.firebase.firestore.FieldPath); + method public boolean contains(com.google.firebase.firestore.FieldPath); + method public boolean contains(String); method public boolean exists(); - method @Nullable public Object get(@NonNull String); - method @Nullable public Object get(@NonNull String, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); - method @Nullable public Object get(@NonNull com.google.firebase.firestore.FieldPath); - method @Nullable public Object get(@NonNull com.google.firebase.firestore.FieldPath, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); - method @Nullable public T get(@NonNull String, @NonNull Class); - method @Nullable public T get(@NonNull String, @NonNull Class, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); - method @Nullable public T get(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Class); - method @Nullable public T get(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Class, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); - method @Nullable public com.google.firebase.firestore.Blob getBlob(@NonNull String); - method @Nullable public Boolean getBoolean(@NonNull String); - method @Nullable public java.util.Map getData(); - method @Nullable public java.util.Map getData(@NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); - method @Nullable public java.util.Date getDate(@NonNull String); - method @Nullable public java.util.Date getDate(@NonNull String, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); - method @Nullable public com.google.firebase.firestore.DocumentReference getDocumentReference(@NonNull String); - method @Nullable public Double getDouble(@NonNull String); - method @Nullable public com.google.firebase.firestore.GeoPoint getGeoPoint(@NonNull String); - method @NonNull public String getId(); - method @Nullable public Long getLong(@NonNull String); - method @NonNull public com.google.firebase.firestore.SnapshotMetadata getMetadata(); - method @NonNull public com.google.firebase.firestore.DocumentReference getReference(); - method @Nullable public String getString(@NonNull String); - method @Nullable public com.google.firebase.Timestamp getTimestamp(@NonNull String); - method @Nullable public com.google.firebase.Timestamp getTimestamp(@NonNull String, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); - method @Nullable public com.google.firebase.firestore.VectorValue getVectorValue(@NonNull String); - method @Nullable public T toObject(@NonNull Class); - method @Nullable public T toObject(@NonNull Class, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public Object? get(com.google.firebase.firestore.FieldPath); + method public Object? get(com.google.firebase.firestore.FieldPath, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public T? get(com.google.firebase.firestore.FieldPath, Class); + method public T? get(com.google.firebase.firestore.FieldPath, Class, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public Object? get(String); + method public Object? get(String, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public T? get(String, Class); + method public T? get(String, Class, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public com.google.firebase.firestore.Blob? getBlob(String); + method public Boolean? getBoolean(String); + method public java.util.Map? getData(); + method public java.util.Map? getData(com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public java.util.Date? getDate(String); + method public java.util.Date? getDate(String, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public com.google.firebase.firestore.DocumentReference? getDocumentReference(String); + method public Double? getDouble(String); + method public com.google.firebase.firestore.GeoPoint? getGeoPoint(String); + method public String getId(); + method public Long? getLong(String); + method public com.google.firebase.firestore.SnapshotMetadata getMetadata(); + method public com.google.firebase.firestore.DocumentReference getReference(); + method public String? getString(String); + method public com.google.firebase.Timestamp? getTimestamp(String); + method public com.google.firebase.Timestamp? getTimestamp(String, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public com.google.firebase.firestore.VectorValue? getVectorValue(String); + method public T? toObject(Class); + method public T? toObject(Class, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); } public enum DocumentSnapshot.ServerTimestampBehavior { @@ -135,94 +135,94 @@ package com.google.firebase.firestore { } public interface EventListener { - method public void onEvent(@Nullable T, @Nullable com.google.firebase.firestore.FirebaseFirestoreException); + method public void onEvent(T?, com.google.firebase.firestore.FirebaseFirestoreException?); } @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) public @interface Exclude { } public final class FieldPath { - method @NonNull public static com.google.firebase.firestore.FieldPath documentId(); - method @NonNull public static com.google.firebase.firestore.FieldPath of(java.lang.String...); + method public static com.google.firebase.firestore.FieldPath documentId(); + method public static com.google.firebase.firestore.FieldPath of(java.lang.String!...!); } public abstract class FieldValue { - method @NonNull public static com.google.firebase.firestore.FieldValue arrayRemove(java.lang.Object...); - method @NonNull public static com.google.firebase.firestore.FieldValue arrayUnion(java.lang.Object...); - method @NonNull public static com.google.firebase.firestore.FieldValue delete(); - method @NonNull public static com.google.firebase.firestore.FieldValue increment(long); - method @NonNull public static com.google.firebase.firestore.FieldValue increment(double); - method @NonNull public static com.google.firebase.firestore.FieldValue serverTimestamp(); - method @NonNull public static com.google.firebase.firestore.VectorValue vector(@NonNull double[]); + method public static com.google.firebase.firestore.FieldValue arrayRemove(java.lang.Object!...!); + method public static com.google.firebase.firestore.FieldValue arrayUnion(java.lang.Object!...!); + method public static com.google.firebase.firestore.FieldValue delete(); + method public static com.google.firebase.firestore.FieldValue increment(double); + method public static com.google.firebase.firestore.FieldValue increment(long); + method public static com.google.firebase.firestore.FieldValue serverTimestamp(); + method public static com.google.firebase.firestore.VectorValue vector(double[]); } public class Filter { ctor public Filter(); - method @NonNull public static com.google.firebase.firestore.Filter and(com.google.firebase.firestore.Filter...); - method @NonNull public static com.google.firebase.firestore.Filter arrayContains(@NonNull String, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter arrayContains(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter arrayContainsAny(@NonNull String, @NonNull java.util.List); - method @NonNull public static com.google.firebase.firestore.Filter arrayContainsAny(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List); - method @NonNull public static com.google.firebase.firestore.Filter equalTo(@NonNull String, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter equalTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter greaterThan(@NonNull String, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter greaterThan(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter greaterThanOrEqualTo(@NonNull String, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter greaterThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter inArray(@NonNull String, @NonNull java.util.List); - method @NonNull public static com.google.firebase.firestore.Filter inArray(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List); - method @NonNull public static com.google.firebase.firestore.Filter lessThan(@NonNull String, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter lessThan(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter lessThanOrEqualTo(@NonNull String, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter lessThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter notEqualTo(@NonNull String, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter notEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public static com.google.firebase.firestore.Filter notInArray(@NonNull String, @NonNull java.util.List); - method @NonNull public static com.google.firebase.firestore.Filter notInArray(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List); - method @NonNull public static com.google.firebase.firestore.Filter or(com.google.firebase.firestore.Filter...); + method public static com.google.firebase.firestore.Filter and(com.google.firebase.firestore.Filter!...!); + method public static com.google.firebase.firestore.Filter arrayContains(com.google.firebase.firestore.FieldPath, Object?); + method public static com.google.firebase.firestore.Filter arrayContains(String, Object?); + method public static com.google.firebase.firestore.Filter arrayContainsAny(com.google.firebase.firestore.FieldPath, java.util.List); + method public static com.google.firebase.firestore.Filter arrayContainsAny(String, java.util.List); + method public static com.google.firebase.firestore.Filter equalTo(com.google.firebase.firestore.FieldPath, Object?); + method public static com.google.firebase.firestore.Filter equalTo(String, Object?); + method public static com.google.firebase.firestore.Filter greaterThan(com.google.firebase.firestore.FieldPath, Object?); + method public static com.google.firebase.firestore.Filter greaterThan(String, Object?); + method public static com.google.firebase.firestore.Filter greaterThanOrEqualTo(com.google.firebase.firestore.FieldPath, Object?); + method public static com.google.firebase.firestore.Filter greaterThanOrEqualTo(String, Object?); + method public static com.google.firebase.firestore.Filter inArray(com.google.firebase.firestore.FieldPath, java.util.List); + method public static com.google.firebase.firestore.Filter inArray(String, java.util.List); + method public static com.google.firebase.firestore.Filter lessThan(com.google.firebase.firestore.FieldPath, Object?); + method public static com.google.firebase.firestore.Filter lessThan(String, Object?); + method public static com.google.firebase.firestore.Filter lessThanOrEqualTo(com.google.firebase.firestore.FieldPath, Object?); + method public static com.google.firebase.firestore.Filter lessThanOrEqualTo(String, Object?); + method public static com.google.firebase.firestore.Filter notEqualTo(com.google.firebase.firestore.FieldPath, Object?); + method public static com.google.firebase.firestore.Filter notEqualTo(String, Object?); + method public static com.google.firebase.firestore.Filter notInArray(com.google.firebase.firestore.FieldPath, java.util.List); + method public static com.google.firebase.firestore.Filter notInArray(String, java.util.List); + method public static com.google.firebase.firestore.Filter or(com.google.firebase.firestore.Filter!...!); } public class FirebaseFirestore { - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(@NonNull Runnable); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(@NonNull android.app.Activity, @NonNull Runnable); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(@NonNull java.util.concurrent.Executor, @NonNull Runnable); - method @NonNull public com.google.firebase.firestore.WriteBatch batch(); - method @NonNull public com.google.android.gms.tasks.Task clearPersistence(); - method @NonNull public com.google.firebase.firestore.CollectionReference collection(@NonNull String); - method @NonNull public com.google.firebase.firestore.Query collectionGroup(@NonNull String); - method @NonNull public com.google.android.gms.tasks.Task disableNetwork(); - method @NonNull public com.google.firebase.firestore.DocumentReference document(@NonNull String); - method @NonNull public com.google.android.gms.tasks.Task enableNetwork(); - method @NonNull public com.google.firebase.FirebaseApp getApp(); - method @NonNull public com.google.firebase.firestore.FirebaseFirestoreSettings getFirestoreSettings(); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestore getInstance(); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestore getInstance(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestore getInstance(@NonNull String); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestore getInstance(@NonNull com.google.firebase.FirebaseApp, @NonNull String); - method @NonNull public com.google.android.gms.tasks.Task getNamedQuery(@NonNull String); - method @Nullable public com.google.firebase.firestore.PersistentCacheIndexManager getPersistentCacheIndexManager(); - method @NonNull public com.google.firebase.firestore.LoadBundleTask loadBundle(@NonNull java.io.InputStream); - method @NonNull public com.google.firebase.firestore.LoadBundleTask loadBundle(@NonNull byte[]); - method @NonNull public com.google.firebase.firestore.LoadBundleTask loadBundle(@NonNull java.nio.ByteBuffer); - method @NonNull public com.google.android.gms.tasks.Task runBatch(@NonNull com.google.firebase.firestore.WriteBatch.Function); - method @NonNull public com.google.android.gms.tasks.Task runTransaction(@NonNull com.google.firebase.firestore.Transaction.Function); - method @NonNull public com.google.android.gms.tasks.Task runTransaction(@NonNull com.google.firebase.firestore.TransactionOptions, @NonNull com.google.firebase.firestore.Transaction.Function); - method public void setFirestoreSettings(@NonNull com.google.firebase.firestore.FirebaseFirestoreSettings); - method @Deprecated @NonNull @com.google.firebase.annotations.PreviewApi public com.google.android.gms.tasks.Task setIndexConfiguration(@NonNull String); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(android.app.Activity, Runnable); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(Runnable); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(java.util.concurrent.Executor, Runnable); + method public com.google.firebase.firestore.WriteBatch batch(); + method public com.google.android.gms.tasks.Task clearPersistence(); + method public com.google.firebase.firestore.CollectionReference collection(String); + method public com.google.firebase.firestore.Query collectionGroup(String); + method public com.google.android.gms.tasks.Task disableNetwork(); + method public com.google.firebase.firestore.DocumentReference document(String); + method public com.google.android.gms.tasks.Task enableNetwork(); + method public com.google.firebase.FirebaseApp getApp(); + method public com.google.firebase.firestore.FirebaseFirestoreSettings getFirestoreSettings(); + method public static com.google.firebase.firestore.FirebaseFirestore getInstance(); + method public static com.google.firebase.firestore.FirebaseFirestore getInstance(com.google.firebase.FirebaseApp); + method public static com.google.firebase.firestore.FirebaseFirestore getInstance(com.google.firebase.FirebaseApp, String); + method public static com.google.firebase.firestore.FirebaseFirestore getInstance(String); + method public com.google.android.gms.tasks.Task getNamedQuery(String); + method public com.google.firebase.firestore.PersistentCacheIndexManager? getPersistentCacheIndexManager(); + method public com.google.firebase.firestore.LoadBundleTask loadBundle(byte[]); + method public com.google.firebase.firestore.LoadBundleTask loadBundle(java.io.InputStream); + method public com.google.firebase.firestore.LoadBundleTask loadBundle(java.nio.ByteBuffer); + method public com.google.android.gms.tasks.Task runBatch(com.google.firebase.firestore.WriteBatch.Function); + method public com.google.android.gms.tasks.Task runTransaction(com.google.firebase.firestore.Transaction.Function); + method public com.google.android.gms.tasks.Task runTransaction(com.google.firebase.firestore.TransactionOptions, com.google.firebase.firestore.Transaction.Function); + method public void setFirestoreSettings(com.google.firebase.firestore.FirebaseFirestoreSettings); + method @Deprecated @com.google.firebase.annotations.PreviewApi public com.google.android.gms.tasks.Task setIndexConfiguration(String); method public static void setLoggingEnabled(boolean); - method @NonNull public com.google.android.gms.tasks.Task terminate(); - method public void useEmulator(@NonNull String, int); - method @NonNull public com.google.android.gms.tasks.Task waitForPendingWrites(); + method public com.google.android.gms.tasks.Task terminate(); + method public void useEmulator(String, int); + method public com.google.android.gms.tasks.Task waitForPendingWrites(); } public class FirebaseFirestoreException extends com.google.firebase.FirebaseException { - ctor public FirebaseFirestoreException(@NonNull String, @NonNull com.google.firebase.firestore.FirebaseFirestoreException.Code); - ctor public FirebaseFirestoreException(@NonNull String, @NonNull com.google.firebase.firestore.FirebaseFirestoreException.Code, @Nullable Throwable); - method @NonNull public com.google.firebase.firestore.FirebaseFirestoreException.Code getCode(); + ctor public FirebaseFirestoreException(String, com.google.firebase.firestore.FirebaseFirestoreException.Code); + ctor public FirebaseFirestoreException(String, com.google.firebase.firestore.FirebaseFirestoreException.Code, Throwable?); + method public com.google.firebase.firestore.FirebaseFirestoreException.Code getCode(); } public enum FirebaseFirestoreException.Code { - method @NonNull public static com.google.firebase.firestore.FirebaseFirestoreException.Code fromValue(int); + method public static com.google.firebase.firestore.FirebaseFirestoreException.Code fromValue(int); method public int value(); enum_constant public static final com.google.firebase.firestore.FirebaseFirestoreException.Code ABORTED; enum_constant public static final com.google.firebase.firestore.FirebaseFirestoreException.Code ALREADY_EXISTS; @@ -244,9 +244,9 @@ package com.google.firebase.firestore { } public final class FirebaseFirestoreSettings { - method @Nullable public com.google.firebase.firestore.LocalCacheSettings getCacheSettings(); + method public com.google.firebase.firestore.LocalCacheSettings? getCacheSettings(); method @Deprecated public long getCacheSizeBytes(); - method @NonNull public String getHost(); + method public String getHost(); method @Deprecated public boolean isPersistenceEnabled(); method public boolean isSslEnabled(); field public static final long CACHE_SIZE_UNLIMITED = -1L; // 0xffffffffffffffffL @@ -254,48 +254,48 @@ package com.google.firebase.firestore { public static final class FirebaseFirestoreSettings.Builder { ctor public FirebaseFirestoreSettings.Builder(); - ctor public FirebaseFirestoreSettings.Builder(@NonNull com.google.firebase.firestore.FirebaseFirestoreSettings); - method @NonNull public com.google.firebase.firestore.FirebaseFirestoreSettings build(); + ctor public FirebaseFirestoreSettings.Builder(com.google.firebase.firestore.FirebaseFirestoreSettings); + method public com.google.firebase.firestore.FirebaseFirestoreSettings build(); method @Deprecated public long getCacheSizeBytes(); - method @NonNull public String getHost(); + method public String getHost(); method @Deprecated public boolean isPersistenceEnabled(); method public boolean isSslEnabled(); - method @Deprecated @NonNull public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setCacheSizeBytes(long); - method @NonNull public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setHost(@NonNull String); - method @NonNull public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setLocalCacheSettings(@NonNull com.google.firebase.firestore.LocalCacheSettings); - method @Deprecated @NonNull public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setPersistenceEnabled(boolean); - method @NonNull public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setSslEnabled(boolean); + method @Deprecated public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setCacheSizeBytes(long); + method public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setHost(String); + method public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setLocalCacheSettings(com.google.firebase.firestore.LocalCacheSettings); + method @Deprecated public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setPersistenceEnabled(boolean); + method public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setSslEnabled(boolean); } public final class FirestoreKt { - method public static inline kotlinx.coroutines.flow.Flow> dataObjects(@NonNull com.google.firebase.firestore.Query, @NonNull com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); - method public static inline kotlinx.coroutines.flow.Flow dataObjects(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestore firestore(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestore firestore(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String database); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestore firestore(@NonNull com.google.firebase.Firebase, @NonNull String database); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestoreSettings firestoreSettings(@NonNull kotlin.jvm.functions.Function1 init); - method public static inline T getField(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull String field); - method public static inline T getField(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull String field, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - method public static inline T getField(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull com.google.firebase.firestore.FieldPath fieldPath); - method public static inline T getField(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull com.google.firebase.firestore.FieldPath fieldPath, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - method @NonNull public static com.google.firebase.firestore.FirebaseFirestore getFirestore(@NonNull com.google.firebase.Firebase); - method @NonNull public static com.google.firebase.firestore.MemoryCacheSettings memoryCacheSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.firestore.MemoryEagerGcSettings memoryEagerGcSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.firestore.MemoryLruGcSettings memoryLruGcSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.firestore.PersistentCacheSettings persistentCacheSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static kotlinx.coroutines.flow.Flow snapshots(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); - method @NonNull public static kotlinx.coroutines.flow.Flow snapshots(@NonNull com.google.firebase.firestore.Query, @NonNull com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); - method public static inline T toObject(@NonNull com.google.firebase.firestore.DocumentSnapshot); - method public static inline T toObject(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - method public static inline T toObject(@NonNull com.google.firebase.firestore.QueryDocumentSnapshot); - method public static inline T toObject(@NonNull com.google.firebase.firestore.QueryDocumentSnapshot, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - method public static inline java.util.List toObjects(@NonNull com.google.firebase.firestore.QuerySnapshot); - method public static inline java.util.List toObjects(@NonNull com.google.firebase.firestore.QuerySnapshot, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - } - - public class GeoPoint implements java.lang.Comparable { + method public static inline kotlinx.coroutines.flow.Flow dataObjects(com.google.firebase.firestore.DocumentReference, com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); + method public static inline kotlinx.coroutines.flow.Flow> dataObjects(com.google.firebase.firestore.Query, com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); + method public static com.google.firebase.firestore.FirebaseFirestore firestore(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); + method public static com.google.firebase.firestore.FirebaseFirestore firestore(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app, String database); + method public static com.google.firebase.firestore.FirebaseFirestore firestore(com.google.firebase.Firebase, String database); + method public static com.google.firebase.firestore.FirebaseFirestoreSettings firestoreSettings(kotlin.jvm.functions.Function1 init); + method public static inline T? getField(com.google.firebase.firestore.DocumentSnapshot, com.google.firebase.firestore.FieldPath fieldPath); + method public static inline T? getField(com.google.firebase.firestore.DocumentSnapshot, com.google.firebase.firestore.FieldPath fieldPath, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method public static inline T? getField(com.google.firebase.firestore.DocumentSnapshot, String field); + method public static inline T? getField(com.google.firebase.firestore.DocumentSnapshot, String field, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method public static com.google.firebase.firestore.FirebaseFirestore getFirestore(com.google.firebase.Firebase); + method public static com.google.firebase.firestore.MemoryCacheSettings memoryCacheSettings(kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.firestore.MemoryEagerGcSettings memoryEagerGcSettings(kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.firestore.MemoryLruGcSettings memoryLruGcSettings(kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.firestore.PersistentCacheSettings persistentCacheSettings(kotlin.jvm.functions.Function1 init); + method public static kotlinx.coroutines.flow.Flow snapshots(com.google.firebase.firestore.DocumentReference, com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); + method public static kotlinx.coroutines.flow.Flow snapshots(com.google.firebase.firestore.Query, com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); + method public static inline T? toObject(com.google.firebase.firestore.DocumentSnapshot); + method public static inline T? toObject(com.google.firebase.firestore.DocumentSnapshot, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method public static inline T toObject(com.google.firebase.firestore.QueryDocumentSnapshot); + method public static inline T toObject(com.google.firebase.firestore.QueryDocumentSnapshot, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method public static inline java.util.List toObjects(com.google.firebase.firestore.QuerySnapshot); + method public static inline java.util.List toObjects(com.google.firebase.firestore.QuerySnapshot, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + } + + public class GeoPoint implements java.lang.Comparable { ctor public GeoPoint(double, double); - method public int compareTo(@NonNull com.google.firebase.firestore.GeoPoint); + method public int compareTo(com.google.firebase.firestore.GeoPoint); method public double getLatitude(); method public double getLongitude(); } @@ -312,41 +312,41 @@ package com.google.firebase.firestore { method public void remove(); } - public class LoadBundleTask extends com.google.android.gms.tasks.Task { - method @NonNull public com.google.android.gms.tasks.Task addOnCanceledListener(@NonNull com.google.android.gms.tasks.OnCanceledListener); - method @NonNull public com.google.android.gms.tasks.Task addOnCanceledListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.OnCanceledListener); - method @NonNull public com.google.android.gms.tasks.Task addOnCanceledListener(@NonNull android.app.Activity, @NonNull com.google.android.gms.tasks.OnCanceledListener); - method @NonNull public com.google.android.gms.tasks.Task addOnCompleteListener(@NonNull com.google.android.gms.tasks.OnCompleteListener); - method @NonNull public com.google.android.gms.tasks.Task addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.OnCompleteListener); - method @NonNull public com.google.android.gms.tasks.Task addOnCompleteListener(@NonNull android.app.Activity, @NonNull com.google.android.gms.tasks.OnCompleteListener); - method @NonNull public com.google.android.gms.tasks.Task addOnFailureListener(@NonNull com.google.android.gms.tasks.OnFailureListener); - method @NonNull public com.google.android.gms.tasks.Task addOnFailureListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.OnFailureListener); - method @NonNull public com.google.android.gms.tasks.Task addOnFailureListener(@NonNull android.app.Activity, @NonNull com.google.android.gms.tasks.OnFailureListener); - method @NonNull public com.google.firebase.firestore.LoadBundleTask addOnProgressListener(@NonNull com.google.firebase.firestore.OnProgressListener); - method @NonNull public com.google.firebase.firestore.LoadBundleTask addOnProgressListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.firestore.OnProgressListener); - method @NonNull public com.google.firebase.firestore.LoadBundleTask addOnProgressListener(@NonNull android.app.Activity, @NonNull com.google.firebase.firestore.OnProgressListener); - method @NonNull public com.google.android.gms.tasks.Task addOnSuccessListener(@NonNull com.google.android.gms.tasks.OnSuccessListener); - method @NonNull public com.google.android.gms.tasks.Task addOnSuccessListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.OnSuccessListener); - method @NonNull public com.google.android.gms.tasks.Task addOnSuccessListener(@NonNull android.app.Activity, @NonNull com.google.android.gms.tasks.OnSuccessListener); - method @NonNull public com.google.android.gms.tasks.Task continueWith(@NonNull com.google.android.gms.tasks.Continuation); - method @NonNull public com.google.android.gms.tasks.Task continueWith(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.Continuation); - method @NonNull public com.google.android.gms.tasks.Task continueWithTask(@NonNull com.google.android.gms.tasks.Continuation>); - method @NonNull public com.google.android.gms.tasks.Task continueWithTask(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.Continuation>); - method @Nullable public Exception getException(); - method @NonNull public com.google.firebase.firestore.LoadBundleTaskProgress getResult(); - method @NonNull public com.google.firebase.firestore.LoadBundleTaskProgress getResult(@NonNull Class) throws X; + public class LoadBundleTask extends com.google.android.gms.tasks.Task { + method public com.google.android.gms.tasks.Task addOnCanceledListener(android.app.Activity, com.google.android.gms.tasks.OnCanceledListener); + method public com.google.android.gms.tasks.Task addOnCanceledListener(com.google.android.gms.tasks.OnCanceledListener); + method public com.google.android.gms.tasks.Task addOnCanceledListener(java.util.concurrent.Executor, com.google.android.gms.tasks.OnCanceledListener); + method public com.google.android.gms.tasks.Task addOnCompleteListener(android.app.Activity, com.google.android.gms.tasks.OnCompleteListener); + method public com.google.android.gms.tasks.Task addOnCompleteListener(com.google.android.gms.tasks.OnCompleteListener); + method public com.google.android.gms.tasks.Task addOnCompleteListener(java.util.concurrent.Executor, com.google.android.gms.tasks.OnCompleteListener); + method public com.google.android.gms.tasks.Task addOnFailureListener(android.app.Activity, com.google.android.gms.tasks.OnFailureListener); + method public com.google.android.gms.tasks.Task addOnFailureListener(com.google.android.gms.tasks.OnFailureListener); + method public com.google.android.gms.tasks.Task addOnFailureListener(java.util.concurrent.Executor, com.google.android.gms.tasks.OnFailureListener); + method public com.google.firebase.firestore.LoadBundleTask addOnProgressListener(android.app.Activity, com.google.firebase.firestore.OnProgressListener); + method public com.google.firebase.firestore.LoadBundleTask addOnProgressListener(com.google.firebase.firestore.OnProgressListener); + method public com.google.firebase.firestore.LoadBundleTask addOnProgressListener(java.util.concurrent.Executor, com.google.firebase.firestore.OnProgressListener); + method public com.google.android.gms.tasks.Task addOnSuccessListener(android.app.Activity, com.google.android.gms.tasks.OnSuccessListener); + method public com.google.android.gms.tasks.Task addOnSuccessListener(com.google.android.gms.tasks.OnSuccessListener); + method public com.google.android.gms.tasks.Task addOnSuccessListener(java.util.concurrent.Executor, com.google.android.gms.tasks.OnSuccessListener); + method public com.google.android.gms.tasks.Task continueWith(com.google.android.gms.tasks.Continuation); + method public com.google.android.gms.tasks.Task continueWith(java.util.concurrent.Executor, com.google.android.gms.tasks.Continuation); + method public com.google.android.gms.tasks.Task continueWithTask(com.google.android.gms.tasks.Continuation!>); + method public com.google.android.gms.tasks.Task continueWithTask(java.util.concurrent.Executor, com.google.android.gms.tasks.Continuation!>); + method public Exception? getException(); + method public com.google.firebase.firestore.LoadBundleTaskProgress getResult(); + method public com.google.firebase.firestore.LoadBundleTaskProgress getResult(Class) throws X; method public boolean isCanceled(); method public boolean isComplete(); method public boolean isSuccessful(); - method @NonNull public com.google.android.gms.tasks.Task onSuccessTask(@NonNull com.google.android.gms.tasks.SuccessContinuation); - method @NonNull public com.google.android.gms.tasks.Task onSuccessTask(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.SuccessContinuation); + method public com.google.android.gms.tasks.Task onSuccessTask(com.google.android.gms.tasks.SuccessContinuation); + method public com.google.android.gms.tasks.Task onSuccessTask(java.util.concurrent.Executor, com.google.android.gms.tasks.SuccessContinuation); } public final class LoadBundleTaskProgress { method public long getBytesLoaded(); method public int getDocumentsLoaded(); - method @Nullable public Exception getException(); - method @NonNull public com.google.firebase.firestore.LoadBundleTaskProgress.TaskState getTaskState(); + method public Exception? getException(); + method public com.google.firebase.firestore.LoadBundleTaskProgress.TaskState getTaskState(); method public long getTotalBytes(); method public int getTotalDocuments(); } @@ -361,21 +361,21 @@ package com.google.firebase.firestore { } public final class MemoryCacheSettings implements com.google.firebase.firestore.LocalCacheSettings { - method @NonNull public com.google.firebase.firestore.MemoryGarbageCollectorSettings getGarbageCollectorSettings(); - method @NonNull public static com.google.firebase.firestore.MemoryCacheSettings.Builder newBuilder(); + method public com.google.firebase.firestore.MemoryGarbageCollectorSettings getGarbageCollectorSettings(); + method public static com.google.firebase.firestore.MemoryCacheSettings.Builder newBuilder(); } public static class MemoryCacheSettings.Builder { - method @NonNull public com.google.firebase.firestore.MemoryCacheSettings build(); - method @NonNull public com.google.firebase.firestore.MemoryCacheSettings.Builder setGcSettings(@NonNull com.google.firebase.firestore.MemoryGarbageCollectorSettings); + method public com.google.firebase.firestore.MemoryCacheSettings build(); + method public com.google.firebase.firestore.MemoryCacheSettings.Builder setGcSettings(com.google.firebase.firestore.MemoryGarbageCollectorSettings); } public final class MemoryEagerGcSettings implements com.google.firebase.firestore.MemoryGarbageCollectorSettings { - method @NonNull public static com.google.firebase.firestore.MemoryEagerGcSettings.Builder newBuilder(); + method public static com.google.firebase.firestore.MemoryEagerGcSettings.Builder newBuilder(); } public static class MemoryEagerGcSettings.Builder { - method @NonNull public com.google.firebase.firestore.MemoryEagerGcSettings build(); + method public com.google.firebase.firestore.MemoryEagerGcSettings build(); } public interface MemoryGarbageCollectorSettings { @@ -383,12 +383,12 @@ package com.google.firebase.firestore { public final class MemoryLruGcSettings implements com.google.firebase.firestore.MemoryGarbageCollectorSettings { method public long getSizeBytes(); - method @NonNull public static com.google.firebase.firestore.MemoryLruGcSettings.Builder newBuilder(); + method public static com.google.firebase.firestore.MemoryLruGcSettings.Builder newBuilder(); } public static class MemoryLruGcSettings.Builder { - method @NonNull public com.google.firebase.firestore.MemoryLruGcSettings build(); - method @NonNull public com.google.firebase.firestore.MemoryLruGcSettings.Builder setSizeBytes(long); + method public com.google.firebase.firestore.MemoryLruGcSettings build(); + method public com.google.firebase.firestore.MemoryLruGcSettings.Builder setSizeBytes(long); } public enum MetadataChanges { @@ -397,7 +397,7 @@ package com.google.firebase.firestore { } public interface OnProgressListener { - method public void onProgress(@NonNull ProgressT); + method public void onProgress(ProgressT); } public final class PersistentCacheIndexManager { @@ -408,12 +408,12 @@ package com.google.firebase.firestore { public final class PersistentCacheSettings implements com.google.firebase.firestore.LocalCacheSettings { method public long getSizeBytes(); - method @NonNull public static com.google.firebase.firestore.PersistentCacheSettings.Builder newBuilder(); + method public static com.google.firebase.firestore.PersistentCacheSettings.Builder newBuilder(); } public static class PersistentCacheSettings.Builder { - method @NonNull public com.google.firebase.firestore.PersistentCacheSettings build(); - method @NonNull public com.google.firebase.firestore.PersistentCacheSettings.Builder setSizeBytes(long); + method public com.google.firebase.firestore.PersistentCacheSettings build(); + method public com.google.firebase.firestore.PersistentCacheSettings.Builder setSizeBytes(long); } @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) public @interface PropertyName { @@ -421,53 +421,53 @@ package com.google.firebase.firestore { } public class Query { - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull android.app.Activity, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull android.app.Activity, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.SnapshotListenOptions, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.AggregateQuery aggregate(@NonNull com.google.firebase.firestore.AggregateField, @NonNull com.google.firebase.firestore.AggregateField...); - method @NonNull public com.google.firebase.firestore.AggregateQuery count(); - method @NonNull public com.google.firebase.firestore.Query endAt(@NonNull com.google.firebase.firestore.DocumentSnapshot); - method @NonNull public com.google.firebase.firestore.Query endAt(java.lang.Object...); - method @NonNull public com.google.firebase.firestore.Query endBefore(@NonNull com.google.firebase.firestore.DocumentSnapshot); - method @NonNull public com.google.firebase.firestore.Query endBefore(java.lang.Object...); - method @NonNull public com.google.android.gms.tasks.Task get(); - method @NonNull public com.google.android.gms.tasks.Task get(@NonNull com.google.firebase.firestore.Source); - method @NonNull public com.google.firebase.firestore.FirebaseFirestore getFirestore(); - method @NonNull public com.google.firebase.firestore.Query limit(long); - method @NonNull public com.google.firebase.firestore.Query limitToLast(long); - method @NonNull public com.google.firebase.firestore.Query orderBy(@NonNull String); - method @NonNull public com.google.firebase.firestore.Query orderBy(@NonNull com.google.firebase.firestore.FieldPath); - method @NonNull public com.google.firebase.firestore.Query orderBy(@NonNull String, @NonNull com.google.firebase.firestore.Query.Direction); - method @NonNull public com.google.firebase.firestore.Query orderBy(@NonNull com.google.firebase.firestore.FieldPath, @NonNull com.google.firebase.firestore.Query.Direction); - method @NonNull public com.google.firebase.firestore.Query startAfter(@NonNull com.google.firebase.firestore.DocumentSnapshot); - method @NonNull public com.google.firebase.firestore.Query startAfter(java.lang.Object...); - method @NonNull public com.google.firebase.firestore.Query startAt(@NonNull com.google.firebase.firestore.DocumentSnapshot); - method @NonNull public com.google.firebase.firestore.Query startAt(java.lang.Object...); - method @NonNull public com.google.firebase.firestore.Query where(@NonNull com.google.firebase.firestore.Filter); - method @NonNull public com.google.firebase.firestore.Query whereArrayContains(@NonNull String, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereArrayContains(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereArrayContainsAny(@NonNull String, @NonNull java.util.List); - method @NonNull public com.google.firebase.firestore.Query whereArrayContainsAny(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List); - method @NonNull public com.google.firebase.firestore.Query whereEqualTo(@NonNull String, @Nullable Object); - method @NonNull public com.google.firebase.firestore.Query whereEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public com.google.firebase.firestore.Query whereGreaterThan(@NonNull String, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereGreaterThan(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereGreaterThanOrEqualTo(@NonNull String, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereGreaterThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereIn(@NonNull String, @NonNull java.util.List); - method @NonNull public com.google.firebase.firestore.Query whereIn(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List); - method @NonNull public com.google.firebase.firestore.Query whereLessThan(@NonNull String, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereLessThan(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereLessThanOrEqualTo(@NonNull String, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereLessThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Query whereNotEqualTo(@NonNull String, @Nullable Object); - method @NonNull public com.google.firebase.firestore.Query whereNotEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object); - method @NonNull public com.google.firebase.firestore.Query whereNotIn(@NonNull String, @NonNull java.util.List); - method @NonNull public com.google.firebase.firestore.Query whereNotIn(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(android.app.Activity, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(android.app.Activity, com.google.firebase.firestore.MetadataChanges, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(com.google.firebase.firestore.MetadataChanges, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(com.google.firebase.firestore.SnapshotListenOptions, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(java.util.concurrent.Executor, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(java.util.concurrent.Executor, com.google.firebase.firestore.MetadataChanges, com.google.firebase.firestore.EventListener); + method public com.google.firebase.firestore.AggregateQuery aggregate(com.google.firebase.firestore.AggregateField, com.google.firebase.firestore.AggregateField!...); + method public com.google.firebase.firestore.AggregateQuery count(); + method public com.google.firebase.firestore.Query endAt(com.google.firebase.firestore.DocumentSnapshot); + method public com.google.firebase.firestore.Query endAt(java.lang.Object!...!); + method public com.google.firebase.firestore.Query endBefore(com.google.firebase.firestore.DocumentSnapshot); + method public com.google.firebase.firestore.Query endBefore(java.lang.Object!...!); + method public com.google.android.gms.tasks.Task get(); + method public com.google.android.gms.tasks.Task get(com.google.firebase.firestore.Source); + method public com.google.firebase.firestore.FirebaseFirestore getFirestore(); + method public com.google.firebase.firestore.Query limit(long); + method public com.google.firebase.firestore.Query limitToLast(long); + method public com.google.firebase.firestore.Query orderBy(com.google.firebase.firestore.FieldPath); + method public com.google.firebase.firestore.Query orderBy(com.google.firebase.firestore.FieldPath, com.google.firebase.firestore.Query.Direction); + method public com.google.firebase.firestore.Query orderBy(String); + method public com.google.firebase.firestore.Query orderBy(String, com.google.firebase.firestore.Query.Direction); + method public com.google.firebase.firestore.Query startAfter(com.google.firebase.firestore.DocumentSnapshot); + method public com.google.firebase.firestore.Query startAfter(java.lang.Object!...!); + method public com.google.firebase.firestore.Query startAt(com.google.firebase.firestore.DocumentSnapshot); + method public com.google.firebase.firestore.Query startAt(java.lang.Object!...!); + method public com.google.firebase.firestore.Query where(com.google.firebase.firestore.Filter); + method public com.google.firebase.firestore.Query whereArrayContains(com.google.firebase.firestore.FieldPath, Object); + method public com.google.firebase.firestore.Query whereArrayContains(String, Object); + method public com.google.firebase.firestore.Query whereArrayContainsAny(com.google.firebase.firestore.FieldPath, java.util.List); + method public com.google.firebase.firestore.Query whereArrayContainsAny(String, java.util.List); + method public com.google.firebase.firestore.Query whereEqualTo(com.google.firebase.firestore.FieldPath, Object?); + method public com.google.firebase.firestore.Query whereEqualTo(String, Object?); + method public com.google.firebase.firestore.Query whereGreaterThan(com.google.firebase.firestore.FieldPath, Object); + method public com.google.firebase.firestore.Query whereGreaterThan(String, Object); + method public com.google.firebase.firestore.Query whereGreaterThanOrEqualTo(com.google.firebase.firestore.FieldPath, Object); + method public com.google.firebase.firestore.Query whereGreaterThanOrEqualTo(String, Object); + method public com.google.firebase.firestore.Query whereIn(com.google.firebase.firestore.FieldPath, java.util.List); + method public com.google.firebase.firestore.Query whereIn(String, java.util.List); + method public com.google.firebase.firestore.Query whereLessThan(com.google.firebase.firestore.FieldPath, Object); + method public com.google.firebase.firestore.Query whereLessThan(String, Object); + method public com.google.firebase.firestore.Query whereLessThanOrEqualTo(com.google.firebase.firestore.FieldPath, Object); + method public com.google.firebase.firestore.Query whereLessThanOrEqualTo(String, Object); + method public com.google.firebase.firestore.Query whereNotEqualTo(com.google.firebase.firestore.FieldPath, Object?); + method public com.google.firebase.firestore.Query whereNotEqualTo(String, Object?); + method public com.google.firebase.firestore.Query whereNotIn(com.google.firebase.firestore.FieldPath, java.util.List); + method public com.google.firebase.firestore.Query whereNotIn(String, java.util.List); } public enum Query.Direction { @@ -478,43 +478,43 @@ package com.google.firebase.firestore { public class QueryDocumentSnapshot extends com.google.firebase.firestore.DocumentSnapshot { } - public class QuerySnapshot implements java.lang.Iterable { - method @NonNull public java.util.List getDocumentChanges(); - method @NonNull public java.util.List getDocumentChanges(@NonNull com.google.firebase.firestore.MetadataChanges); - method @NonNull public java.util.List getDocuments(); - method @NonNull public com.google.firebase.firestore.SnapshotMetadata getMetadata(); - method @NonNull public com.google.firebase.firestore.Query getQuery(); + public class QuerySnapshot implements java.lang.Iterable { + method public java.util.List getDocumentChanges(); + method public java.util.List getDocumentChanges(com.google.firebase.firestore.MetadataChanges); + method public java.util.List getDocuments(); + method public com.google.firebase.firestore.SnapshotMetadata getMetadata(); + method public com.google.firebase.firestore.Query getQuery(); method public boolean isEmpty(); - method @NonNull public java.util.Iterator iterator(); + method public java.util.Iterator iterator(); method public int size(); - method @NonNull public java.util.List toObjects(@NonNull Class); - method @NonNull public java.util.List toObjects(@NonNull Class, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); + method public java.util.List toObjects(Class); + method public java.util.List toObjects(Class, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior); } @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) public @interface ServerTimestamp { } public final class SetOptions { - method @NonNull public static com.google.firebase.firestore.SetOptions merge(); - method @NonNull public static com.google.firebase.firestore.SetOptions mergeFieldPaths(@NonNull java.util.List); - method @NonNull public static com.google.firebase.firestore.SetOptions mergeFields(@NonNull java.util.List); - method @NonNull public static com.google.firebase.firestore.SetOptions mergeFields(java.lang.String...); + method public static com.google.firebase.firestore.SetOptions merge(); + method public static com.google.firebase.firestore.SetOptions mergeFieldPaths(java.util.List); + method public static com.google.firebase.firestore.SetOptions mergeFields(java.lang.String!...!); + method public static com.google.firebase.firestore.SetOptions mergeFields(java.util.List); } public final class SnapshotListenOptions { - method @Nullable public android.app.Activity getActivity(); - method @NonNull public java.util.concurrent.Executor getExecutor(); - method @NonNull public com.google.firebase.firestore.MetadataChanges getMetadataChanges(); - method @NonNull public com.google.firebase.firestore.ListenSource getSource(); + method public android.app.Activity? getActivity(); + method public java.util.concurrent.Executor getExecutor(); + method public com.google.firebase.firestore.MetadataChanges getMetadataChanges(); + method public com.google.firebase.firestore.ListenSource getSource(); } public static class SnapshotListenOptions.Builder { ctor public SnapshotListenOptions.Builder(); - method @NonNull public com.google.firebase.firestore.SnapshotListenOptions build(); - method @NonNull public com.google.firebase.firestore.SnapshotListenOptions.Builder setActivity(@NonNull android.app.Activity); - method @NonNull public com.google.firebase.firestore.SnapshotListenOptions.Builder setExecutor(@NonNull java.util.concurrent.Executor); - method @NonNull public com.google.firebase.firestore.SnapshotListenOptions.Builder setMetadataChanges(@NonNull com.google.firebase.firestore.MetadataChanges); - method @NonNull public com.google.firebase.firestore.SnapshotListenOptions.Builder setSource(@NonNull com.google.firebase.firestore.ListenSource); + method public com.google.firebase.firestore.SnapshotListenOptions build(); + method public com.google.firebase.firestore.SnapshotListenOptions.Builder setActivity(android.app.Activity); + method public com.google.firebase.firestore.SnapshotListenOptions.Builder setExecutor(java.util.concurrent.Executor); + method public com.google.firebase.firestore.SnapshotListenOptions.Builder setMetadataChanges(com.google.firebase.firestore.MetadataChanges); + method public com.google.firebase.firestore.SnapshotListenOptions.Builder setSource(com.google.firebase.firestore.ListenSource); } public class SnapshotMetadata { @@ -532,17 +532,17 @@ package com.google.firebase.firestore { } public class Transaction { - method @NonNull public com.google.firebase.firestore.Transaction delete(@NonNull com.google.firebase.firestore.DocumentReference); - method @NonNull public com.google.firebase.firestore.DocumentSnapshot get(@NonNull com.google.firebase.firestore.DocumentReference) throws com.google.firebase.firestore.FirebaseFirestoreException; - method @NonNull public com.google.firebase.firestore.Transaction set(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull Object); - method @NonNull public com.google.firebase.firestore.Transaction set(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull Object, @NonNull com.google.firebase.firestore.SetOptions); - method @NonNull public com.google.firebase.firestore.Transaction update(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull java.util.Map); - method @NonNull public com.google.firebase.firestore.Transaction update(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull String, @Nullable Object, java.lang.Object...); - method @NonNull public com.google.firebase.firestore.Transaction update(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull com.google.firebase.firestore.FieldPath, @Nullable Object, java.lang.Object...); + method public com.google.firebase.firestore.Transaction delete(com.google.firebase.firestore.DocumentReference); + method public com.google.firebase.firestore.DocumentSnapshot get(com.google.firebase.firestore.DocumentReference) throws com.google.firebase.firestore.FirebaseFirestoreException; + method public com.google.firebase.firestore.Transaction set(com.google.firebase.firestore.DocumentReference, Object); + method public com.google.firebase.firestore.Transaction set(com.google.firebase.firestore.DocumentReference, Object, com.google.firebase.firestore.SetOptions); + method public com.google.firebase.firestore.Transaction update(com.google.firebase.firestore.DocumentReference, com.google.firebase.firestore.FieldPath, Object?, java.lang.Object!...!); + method public com.google.firebase.firestore.Transaction update(com.google.firebase.firestore.DocumentReference, String, Object?, java.lang.Object!...!); + method public com.google.firebase.firestore.Transaction update(com.google.firebase.firestore.DocumentReference, java.util.Map); } public static interface Transaction.Function { - method @Nullable public TResult apply(@NonNull com.google.firebase.firestore.Transaction) throws com.google.firebase.firestore.FirebaseFirestoreException; + method public TResult? apply(com.google.firebase.firestore.Transaction) throws com.google.firebase.firestore.FirebaseFirestoreException; } public final class TransactionOptions { @@ -551,27 +551,27 @@ package com.google.firebase.firestore { public static final class TransactionOptions.Builder { ctor public TransactionOptions.Builder(); - ctor public TransactionOptions.Builder(@NonNull com.google.firebase.firestore.TransactionOptions); - method @NonNull public com.google.firebase.firestore.TransactionOptions build(); - method @NonNull public com.google.firebase.firestore.TransactionOptions.Builder setMaxAttempts(int); + ctor public TransactionOptions.Builder(com.google.firebase.firestore.TransactionOptions); + method public com.google.firebase.firestore.TransactionOptions build(); + method public com.google.firebase.firestore.TransactionOptions.Builder setMaxAttempts(int); } public class VectorValue { - method @NonNull public double[] toArray(); + method public double[] toArray(); } public class WriteBatch { - method @NonNull public com.google.android.gms.tasks.Task commit(); - method @NonNull public com.google.firebase.firestore.WriteBatch delete(@NonNull com.google.firebase.firestore.DocumentReference); - method @NonNull public com.google.firebase.firestore.WriteBatch set(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull Object); - method @NonNull public com.google.firebase.firestore.WriteBatch set(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull Object, @NonNull com.google.firebase.firestore.SetOptions); - method @NonNull public com.google.firebase.firestore.WriteBatch update(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull java.util.Map); - method @NonNull public com.google.firebase.firestore.WriteBatch update(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull String, @Nullable Object, java.lang.Object...); - method @NonNull public com.google.firebase.firestore.WriteBatch update(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull com.google.firebase.firestore.FieldPath, @Nullable Object, java.lang.Object...); + method public com.google.android.gms.tasks.Task commit(); + method public com.google.firebase.firestore.WriteBatch delete(com.google.firebase.firestore.DocumentReference); + method public com.google.firebase.firestore.WriteBatch set(com.google.firebase.firestore.DocumentReference, Object); + method public com.google.firebase.firestore.WriteBatch set(com.google.firebase.firestore.DocumentReference, Object, com.google.firebase.firestore.SetOptions); + method public com.google.firebase.firestore.WriteBatch update(com.google.firebase.firestore.DocumentReference, com.google.firebase.firestore.FieldPath, Object?, java.lang.Object!...!); + method public com.google.firebase.firestore.WriteBatch update(com.google.firebase.firestore.DocumentReference, String, Object?, java.lang.Object!...!); + method public com.google.firebase.firestore.WriteBatch update(com.google.firebase.firestore.DocumentReference, java.util.Map); } public static interface WriteBatch.Function { - method public void apply(@NonNull com.google.firebase.firestore.WriteBatch); + method public void apply(com.google.firebase.firestore.WriteBatch); } } @@ -579,29 +579,29 @@ package com.google.firebase.firestore { package com.google.firebase.firestore.ktx { public final class FirestoreKt { - method @Deprecated public static inline kotlinx.coroutines.flow.Flow> dataObjects(@NonNull com.google.firebase.firestore.Query, @NonNull com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); - method @Deprecated public static inline kotlinx.coroutines.flow.Flow dataObjects(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); - method @Deprecated @NonNull public static com.google.firebase.firestore.FirebaseFirestore firestore(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @Deprecated @NonNull public static com.google.firebase.firestore.FirebaseFirestore firestore(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String database); - method @Deprecated @NonNull public static com.google.firebase.firestore.FirebaseFirestore firestore(@NonNull com.google.firebase.ktx.Firebase, @NonNull String database); - method @Deprecated @NonNull public static com.google.firebase.firestore.FirebaseFirestoreSettings firestoreSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated public static inline T getField(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull String field); - method @Deprecated public static inline T getField(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull String field, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - method @Deprecated public static inline T getField(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull com.google.firebase.firestore.FieldPath fieldPath); - method @Deprecated public static inline T getField(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull com.google.firebase.firestore.FieldPath fieldPath, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - method @Deprecated @NonNull public static com.google.firebase.firestore.FirebaseFirestore getFirestore(@NonNull com.google.firebase.ktx.Firebase); - method @NonNull public static com.google.firebase.firestore.MemoryCacheSettings memoryCacheSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.firestore.MemoryEagerGcSettings memoryEagerGcSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.firestore.MemoryLruGcSettings memoryLruGcSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.firestore.PersistentCacheSettings persistentCacheSettings(@NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated @NonNull public static kotlinx.coroutines.flow.Flow snapshots(@NonNull com.google.firebase.firestore.DocumentReference, @NonNull com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); - method @Deprecated @NonNull public static kotlinx.coroutines.flow.Flow snapshots(@NonNull com.google.firebase.firestore.Query, @NonNull com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); - method @Deprecated public static inline T toObject(@NonNull com.google.firebase.firestore.DocumentSnapshot); - method @Deprecated public static inline T toObject(@NonNull com.google.firebase.firestore.DocumentSnapshot, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - method @Deprecated public static inline T toObject(@NonNull com.google.firebase.firestore.QueryDocumentSnapshot); - method @Deprecated public static inline T toObject(@NonNull com.google.firebase.firestore.QueryDocumentSnapshot, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); - method @Deprecated public static inline java.util.List toObjects(@NonNull com.google.firebase.firestore.QuerySnapshot); - method @Deprecated public static inline java.util.List toObjects(@NonNull com.google.firebase.firestore.QuerySnapshot, @NonNull com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method @Deprecated public static inline kotlinx.coroutines.flow.Flow dataObjects(com.google.firebase.firestore.DocumentReference, com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); + method @Deprecated public static inline kotlinx.coroutines.flow.Flow> dataObjects(com.google.firebase.firestore.Query, com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); + method @Deprecated public static com.google.firebase.firestore.FirebaseFirestore firestore(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); + method @Deprecated public static com.google.firebase.firestore.FirebaseFirestore firestore(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app, String database); + method @Deprecated public static com.google.firebase.firestore.FirebaseFirestore firestore(com.google.firebase.ktx.Firebase, String database); + method @Deprecated public static com.google.firebase.firestore.FirebaseFirestoreSettings firestoreSettings(kotlin.jvm.functions.Function1 init); + method @Deprecated public static inline T? getField(com.google.firebase.firestore.DocumentSnapshot, com.google.firebase.firestore.FieldPath fieldPath); + method @Deprecated public static inline T? getField(com.google.firebase.firestore.DocumentSnapshot, com.google.firebase.firestore.FieldPath fieldPath, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method @Deprecated public static inline T? getField(com.google.firebase.firestore.DocumentSnapshot, String field); + method @Deprecated public static inline T? getField(com.google.firebase.firestore.DocumentSnapshot, String field, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method @Deprecated public static com.google.firebase.firestore.FirebaseFirestore getFirestore(com.google.firebase.ktx.Firebase); + method public static com.google.firebase.firestore.MemoryCacheSettings memoryCacheSettings(kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.firestore.MemoryEagerGcSettings memoryEagerGcSettings(kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.firestore.MemoryLruGcSettings memoryLruGcSettings(kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.firestore.PersistentCacheSettings persistentCacheSettings(kotlin.jvm.functions.Function1 init); + method @Deprecated public static kotlinx.coroutines.flow.Flow snapshots(com.google.firebase.firestore.DocumentReference, com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); + method @Deprecated public static kotlinx.coroutines.flow.Flow snapshots(com.google.firebase.firestore.Query, com.google.firebase.firestore.MetadataChanges metadataChanges = com.google.firebase.firestore.MetadataChanges.EXCLUDE); + method @Deprecated public static inline T? toObject(com.google.firebase.firestore.DocumentSnapshot); + method @Deprecated public static inline T? toObject(com.google.firebase.firestore.DocumentSnapshot, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method @Deprecated public static inline T toObject(com.google.firebase.firestore.QueryDocumentSnapshot); + method @Deprecated public static inline T toObject(com.google.firebase.firestore.QueryDocumentSnapshot, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); + method @Deprecated public static inline java.util.List toObjects(com.google.firebase.firestore.QuerySnapshot); + method @Deprecated public static inline java.util.List toObjects(com.google.firebase.firestore.QuerySnapshot, com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior); } } diff --git a/firebase-firestore/gradle.properties b/firebase-firestore/gradle.properties index e5f0deed10c..baa5399b1dc 100644 --- a/firebase-firestore/gradle.properties +++ b/firebase-firestore/gradle.properties @@ -1,2 +1,2 @@ -version=25.1.2 -latestReleasedVersion=25.1.1 +version=25.1.3 +latestReleasedVersion=25.1.2 diff --git a/firebase-firestore/ktx/api.txt b/firebase-firestore/ktx/api.txt index b8472c410b7..da4f6cc18fe 100644 --- a/firebase-firestore/ktx/api.txt +++ b/firebase-firestore/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.firestore.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/FirestoreTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/FirestoreTest.java index f975b637789..796632e192e 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/FirestoreTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/FirestoreTest.java @@ -1653,4 +1653,210 @@ public void sdkOrdersQueryByDocumentIdTheSameWayOnlineAndOffline() { // Run query with snapshot listener checkOnlineAndOfflineResultsMatch(orderedQuery, expectedDocIds.toArray(new String[0])); } + + @Test + public void snapshotListenerSortsUnicodeStringsAsServer() { + Map> testDocs = + map( + "a", map("value", "Łukasiewicz"), + "b", map("value", "Sierpiński"), + "c", map("value", "岩澤"), + "d", map("value", "🄟"), + "e", map("value", "P"), + "f", map("value", "︒"), + "g", map("value", "🐵")); + + CollectionReference colRef = testCollectionWithDocs(testDocs); + Query orderedQuery = colRef.orderBy("value"); + List expectedDocIds = Arrays.asList("b", "a", "c", "f", "e", "d", "g"); + + QuerySnapshot getSnapshot = waitFor(orderedQuery.get()); + List getSnapshotDocIds = + getSnapshot.getDocuments().stream().map(ds -> ds.getId()).collect(Collectors.toList()); + + EventAccumulator eventAccumulator = new EventAccumulator(); + ListenerRegistration registration = + orderedQuery.addSnapshotListener(eventAccumulator.listener()); + + List watchSnapshotDocIds = new ArrayList<>(); + try { + QuerySnapshot watchSnapshot = eventAccumulator.await(); + watchSnapshotDocIds = + watchSnapshot.getDocuments().stream() + .map(documentSnapshot -> documentSnapshot.getId()) + .collect(Collectors.toList()); + } finally { + registration.remove(); + } + + assertTrue(getSnapshotDocIds.equals(expectedDocIds)); + assertTrue(watchSnapshotDocIds.equals(expectedDocIds)); + + checkOnlineAndOfflineResultsMatch(orderedQuery, expectedDocIds.toArray(new String[0])); + } + + @Test + public void snapshotListenerSortsUnicodeStringsInArrayAsServer() { + Map> testDocs = + map( + "a", map("value", Arrays.asList("Łukasiewicz")), + "b", map("value", Arrays.asList("Sierpiński")), + "c", map("value", Arrays.asList("岩澤")), + "d", map("value", Arrays.asList("🄟")), + "e", map("value", Arrays.asList("P")), + "f", map("value", Arrays.asList("︒")), + "g", map("value", Arrays.asList("🐵"))); + + CollectionReference colRef = testCollectionWithDocs(testDocs); + Query orderedQuery = colRef.orderBy("value"); + List expectedDocIds = Arrays.asList("b", "a", "c", "f", "e", "d", "g"); + + QuerySnapshot getSnapshot = waitFor(orderedQuery.get()); + List getSnapshotDocIds = + getSnapshot.getDocuments().stream().map(ds -> ds.getId()).collect(Collectors.toList()); + + EventAccumulator eventAccumulator = new EventAccumulator(); + ListenerRegistration registration = + orderedQuery.addSnapshotListener(eventAccumulator.listener()); + + List watchSnapshotDocIds = new ArrayList<>(); + try { + QuerySnapshot watchSnapshot = eventAccumulator.await(); + watchSnapshotDocIds = + watchSnapshot.getDocuments().stream() + .map(documentSnapshot -> documentSnapshot.getId()) + .collect(Collectors.toList()); + } finally { + registration.remove(); + } + + assertTrue(getSnapshotDocIds.equals(expectedDocIds)); + assertTrue(watchSnapshotDocIds.equals(expectedDocIds)); + + checkOnlineAndOfflineResultsMatch(orderedQuery, expectedDocIds.toArray(new String[0])); + } + + @Test + public void snapshotListenerSortsUnicodeStringsInMapAsServer() { + Map> testDocs = + map( + "a", map("value", map("foo", "Łukasiewicz")), + "b", map("value", map("foo", "Sierpiński")), + "c", map("value", map("foo", "岩澤")), + "d", map("value", map("foo", "🄟")), + "e", map("value", map("foo", "P")), + "f", map("value", map("foo", "︒")), + "g", map("value", map("foo", "🐵"))); + + CollectionReference colRef = testCollectionWithDocs(testDocs); + Query orderedQuery = colRef.orderBy("value"); + List expectedDocIds = Arrays.asList("b", "a", "c", "f", "e", "d", "g"); + + QuerySnapshot getSnapshot = waitFor(orderedQuery.get()); + List getSnapshotDocIds = + getSnapshot.getDocuments().stream().map(ds -> ds.getId()).collect(Collectors.toList()); + + EventAccumulator eventAccumulator = new EventAccumulator(); + ListenerRegistration registration = + orderedQuery.addSnapshotListener(eventAccumulator.listener()); + + List watchSnapshotDocIds = new ArrayList<>(); + try { + QuerySnapshot watchSnapshot = eventAccumulator.await(); + watchSnapshotDocIds = + watchSnapshot.getDocuments().stream() + .map(documentSnapshot -> documentSnapshot.getId()) + .collect(Collectors.toList()); + } finally { + registration.remove(); + } + + assertTrue(getSnapshotDocIds.equals(expectedDocIds)); + assertTrue(watchSnapshotDocIds.equals(expectedDocIds)); + + checkOnlineAndOfflineResultsMatch(orderedQuery, expectedDocIds.toArray(new String[0])); + } + + @Test + public void snapshotListenerSortsUnicodeStringsInMapKeyAsServer() { + Map> testDocs = + map( + "a", map("value", map("Łukasiewicz", "foo")), + "b", map("value", map("Sierpiński", "foo")), + "c", map("value", map("岩澤", "foo")), + "d", map("value", map("🄟", "foo")), + "e", map("value", map("P", "foo")), + "f", map("value", map("︒", "foo")), + "g", map("value", map("🐵", "foo"))); + + CollectionReference colRef = testCollectionWithDocs(testDocs); + Query orderedQuery = colRef.orderBy("value"); + List expectedDocIds = Arrays.asList("b", "a", "c", "f", "e", "d", "g"); + + QuerySnapshot getSnapshot = waitFor(orderedQuery.get()); + List getSnapshotDocIds = + getSnapshot.getDocuments().stream().map(ds -> ds.getId()).collect(Collectors.toList()); + + EventAccumulator eventAccumulator = new EventAccumulator(); + ListenerRegistration registration = + orderedQuery.addSnapshotListener(eventAccumulator.listener()); + + List watchSnapshotDocIds = new ArrayList<>(); + try { + QuerySnapshot watchSnapshot = eventAccumulator.await(); + watchSnapshotDocIds = + watchSnapshot.getDocuments().stream() + .map(documentSnapshot -> documentSnapshot.getId()) + .collect(Collectors.toList()); + } finally { + registration.remove(); + } + + assertTrue(getSnapshotDocIds.equals(expectedDocIds)); + assertTrue(watchSnapshotDocIds.equals(expectedDocIds)); + + checkOnlineAndOfflineResultsMatch(orderedQuery, expectedDocIds.toArray(new String[0])); + } + + @Test + public void snapshotListenerSortsUnicodeStringsInDocumentKeyAsServer() { + Map> testDocs = + map( + "Łukasiewicz", map("value", "foo"), + "Sierpiński", map("value", "foo"), + "岩澤", map("value", "foo"), + "🄟", map("value", "foo"), + "P", map("value", "foo"), + "︒", map("value", "foo"), + "🐵", map("value", "foo")); + + CollectionReference colRef = testCollectionWithDocs(testDocs); + Query orderedQuery = colRef.orderBy(FieldPath.documentId()); + List expectedDocIds = + Arrays.asList("Sierpiński", "Łukasiewicz", "岩澤", "︒", "P", "🄟", "🐵"); + + QuerySnapshot getSnapshot = waitFor(orderedQuery.get()); + List getSnapshotDocIds = + getSnapshot.getDocuments().stream().map(ds -> ds.getId()).collect(Collectors.toList()); + + EventAccumulator eventAccumulator = new EventAccumulator(); + ListenerRegistration registration = + orderedQuery.addSnapshotListener(eventAccumulator.listener()); + + List watchSnapshotDocIds = new ArrayList<>(); + try { + QuerySnapshot watchSnapshot = eventAccumulator.await(); + watchSnapshotDocIds = + watchSnapshot.getDocuments().stream() + .map(documentSnapshot -> documentSnapshot.getId()) + .collect(Collectors.toList()); + } finally { + registration.remove(); + } + + assertTrue(getSnapshotDocIds.equals(expectedDocIds)); + assertTrue(watchSnapshotDocIds.equals(expectedDocIds)); + + checkOnlineAndOfflineResultsMatch(orderedQuery, expectedDocIds.toArray(new String[0])); + } } diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java new file mode 100644 index 00000000000..21968864d48 --- /dev/null +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java @@ -0,0 +1,832 @@ +// Copyright 2025 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.common.truth.Truth.assertThat; +import static com.google.firebase.firestore.pipeline.Function.add; +import static com.google.firebase.firestore.pipeline.Function.and; +import static com.google.firebase.firestore.pipeline.Function.arrayContains; +import static com.google.firebase.firestore.pipeline.Function.arrayContainsAny; +import static com.google.firebase.firestore.pipeline.Function.cosineDistance; +import static com.google.firebase.firestore.pipeline.Function.endsWith; +import static com.google.firebase.firestore.pipeline.Function.eq; +import static com.google.firebase.firestore.pipeline.Function.euclideanDistance; +import static com.google.firebase.firestore.pipeline.Function.gt; +import static com.google.firebase.firestore.pipeline.Function.logicalMax; +import static com.google.firebase.firestore.pipeline.Function.logicalMin; +import static com.google.firebase.firestore.pipeline.Function.lt; +import static com.google.firebase.firestore.pipeline.Function.lte; +import static com.google.firebase.firestore.pipeline.Function.mapGet; +import static com.google.firebase.firestore.pipeline.Function.neq; +import static com.google.firebase.firestore.pipeline.Function.not; +import static com.google.firebase.firestore.pipeline.Function.or; +import static com.google.firebase.firestore.pipeline.Function.startsWith; +import static com.google.firebase.firestore.pipeline.Function.strConcat; +import static com.google.firebase.firestore.pipeline.Function.subtract; +import static com.google.firebase.firestore.pipeline.Ordering.ascending; +import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor; +import static java.util.Map.entry; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.gms.tasks.Task; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.truth.Correspondence; +import com.google.firebase.firestore.pipeline.Accumulator; +import com.google.firebase.firestore.pipeline.AggregateStage; +import com.google.firebase.firestore.pipeline.Constant; +import com.google.firebase.firestore.pipeline.Field; +import com.google.firebase.firestore.pipeline.Function; +import com.google.firebase.firestore.testutil.IntegrationTestUtil; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class PipelineTest { + + private static final Correspondence> DATA_CORRESPONDENCE = + Correspondence.from( + (result, expected) -> { + assertThat(result.getData()) + .comparingValuesUsing( + Correspondence.from( + (x, y) -> { + if (x instanceof Long && y instanceof Integer) { + return (long) x == (long) (int) y; + } + if (x instanceof Double && y instanceof Integer) { + return (double) x == (double) (int) y; + } + return Objects.equals(x, y); + }, + "MapValueCompare")) + .containsExactlyEntriesIn(expected); + return true; + }, + "GetData"); + + private static final Correspondence ID_CORRESPONDENCE = + Correspondence.transforming(x -> x.getRef().getId(), "GetRefId"); + + private CollectionReference randomCol; + private FirebaseFirestore firestore; + + @After + public void tearDown() { + IntegrationTestUtil.tearDown(); + } + + private final Map> bookDocs = + mapOfEntries( + entry( + "book1", + mapOfEntries( + entry("title", "The Hitchhiker's Guide to the Galaxy"), + entry("author", "Douglas Adams"), + entry("genre", "Science Fiction"), + entry("published", 1979), + entry("rating", 4.2), + entry("tags", ImmutableList.of("comedy", "space", "adventure")), + entry("awards", ImmutableMap.of("hugo", true, "nebula", false)), + entry( + "nestedField", + ImmutableMap.of("level.1", ImmutableMap.of("level.2", true))))), + entry( + "book2", + mapOfEntries( + entry("title", "Pride and Prejudice"), + entry("author", "Jane Austen"), + entry("genre", "Romance"), + entry("published", 1813), + entry("rating", 4.5), + entry("tags", ImmutableList.of("classic", "social commentary", "love")), + entry("awards", ImmutableMap.of("none", true)))), + entry( + "book3", + mapOfEntries( + entry("title", "One Hundred Years of Solitude"), + entry("author", "Gabriel García Márquez"), + entry("genre", "Magical Realism"), + entry("published", 1967), + entry("rating", 4.3), + entry("tags", ImmutableList.of("family", "history", "fantasy")), + entry("awards", ImmutableMap.of("nobel", true, "nebula", false)))), + entry( + "book4", + mapOfEntries( + entry("title", "The Lord of the Rings"), + entry("author", "J.R.R. Tolkien"), + entry("genre", "Fantasy"), + entry("published", 1954), + entry("rating", 4.7), + entry("tags", ImmutableList.of("adventure", "magic", "epic")), + entry("awards", ImmutableMap.of("hugo", false, "nebula", false)))), + entry( + "book5", + mapOfEntries( + entry("title", "The Handmaid's Tale"), + entry("author", "Margaret Atwood"), + entry("genre", "Dystopian"), + entry("published", 1985), + entry("rating", 4.1), + entry("tags", ImmutableList.of("feminism", "totalitarianism", "resistance")), + entry( + "awards", ImmutableMap.of("arthur c. clarke", true, "booker prize", false)))), + entry( + "book6", + mapOfEntries( + entry("title", "Crime and Punishment"), + entry("author", "Fyodor Dostoevsky"), + entry("genre", "Psychological Thriller"), + entry("published", 1866), + entry("rating", 4.3), + entry("tags", ImmutableList.of("philosophy", "crime", "redemption")), + entry("awards", ImmutableMap.of("none", true)))), + entry( + "book7", + mapOfEntries( + entry("title", "To Kill a Mockingbird"), + entry("author", "Harper Lee"), + entry("genre", "Southern Gothic"), + entry("published", 1960), + entry("rating", 4.2), + entry("tags", ImmutableList.of("racism", "injustice", "coming-of-age")), + entry("awards", ImmutableMap.of("pulitzer", true)))), + entry( + "book8", + mapOfEntries( + entry("title", "1984"), + entry("author", "George Orwell"), + entry("genre", "Dystopian"), + entry("published", 1949), + entry("rating", 4.2), + entry("tags", ImmutableList.of("surveillance", "totalitarianism", "propaganda")), + entry("awards", ImmutableMap.of("prometheus", true)))), + entry( + "book9", + mapOfEntries( + entry("title", "The Great Gatsby"), + entry("author", "F. Scott Fitzgerald"), + entry("genre", "Modernist"), + entry("published", 1925), + entry("rating", 4.0), + entry("tags", ImmutableList.of("wealth", "american dream", "love")), + entry("awards", ImmutableMap.of("none", true)))), + entry( + "book10", + mapOfEntries( + entry("title", "Dune"), + entry("author", "Frank Herbert"), + entry("genre", "Science Fiction"), + entry("published", 1965), + entry("rating", 4.6), + entry("tags", ImmutableList.of("politics", "desert", "ecology")), + entry("awards", ImmutableMap.of("hugo", true, "nebula", true))))); + + @Before + public void setup() { + randomCol = IntegrationTestUtil.testCollectionWithDocs(bookDocs); + firestore = randomCol.firestore; + } + + @Test + public void emptyResults() { + Task execute = + firestore.pipeline().collection(randomCol.getPath()).limit(0).execute(); + assertThat(waitFor(execute).getResults()).isEmpty(); + } + + @Test + public void fullResults() { + Task execute = firestore.pipeline().collection(randomCol.getPath()).execute(); + assertThat(waitFor(execute).getResults()).hasSize(10); + } + + @Test + public void aggregateResultsCountAll() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .aggregate(Accumulator.countAll().as("count")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("count", 10)); + } + + @Test + @Ignore("Not supported yet") + public void aggregateResultsMany() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(Function.eq("genre", "Science Fiction")) + .aggregate( + Accumulator.countAll().as("count"), + Accumulator.avg("rating").as("avgRating"), + Field.of("rating").max().as("maxRating")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries(entry("count", 10), entry("avgRating", 4.4), entry("maxRating", 4.6))); + } + + @Test + public void groupAndAccumulateResults() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(lt(Field.of("published"), 1984)) + .aggregate( + AggregateStage.withAccumulators(Accumulator.avg("rating").as("avgRating")) + .withGroups("genre")) + .where(gt("avgRating", 4.3)) + .sort(Field.of("avgRating").descending()) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries(entry("avgRating", 4.7), entry("genre", "Fantasy")), + mapOfEntries(entry("avgRating", 4.5), entry("genre", "Romance")), + mapOfEntries(entry("avgRating", 4.4), entry("genre", "Science Fiction"))); + } + + @Test + @Ignore("Not supported yet") + public void minAndMaxAccumulations() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .aggregate( + Accumulator.countAll().as("count"), + Field.of("rating").max().as("maxRating"), + Field.of("published").min().as("minPublished")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries(entry("count", 10), entry("maxRating", 4.7), entry("minPublished", 1813))); + } + + @Test + public void canSelectFields() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .select("title", "author") + .sort(Field.of("author").ascending()) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries( + entry("title", "The Hitchhiker's Guide to the Galaxy"), + entry("author", "Douglas Adams")), + mapOfEntries( + entry("title", "The Great Gatsby"), entry("author", "F. Scott Fitzgerald")), + mapOfEntries(entry("title", "Dune"), entry("author", "Frank Herbert")), + mapOfEntries( + entry("title", "Crime and Punishment"), entry("author", "Fyodor Dostoevsky")), + mapOfEntries( + entry("title", "One Hundred Years of Solitude"), + entry("author", "Gabriel García Márquez")), + mapOfEntries(entry("title", "1984"), entry("author", "George Orwell")), + mapOfEntries(entry("title", "To Kill a Mockingbird"), entry("author", "Harper Lee")), + mapOfEntries( + entry("title", "The Lord of the Rings"), entry("author", "J.R.R. Tolkien")), + mapOfEntries(entry("title", "Pride and Prejudice"), entry("author", "Jane Austen")), + mapOfEntries(entry("title", "The Handmaid's Tale"), entry("author", "Margaret Atwood"))) + .inOrder(); + } + + @Test + public void whereWithAnd() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(and(gt("rating", 4.5), eq("genre", "Science Fiction"))) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(ID_CORRESPONDENCE) + .containsExactly("book10"); + } + + @Test + public void whereWithOr() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(or(eq("genre", "Romance"), eq("genre", "Dystopian"))) + .select("title") + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("title", "Pride and Prejudice"), + ImmutableMap.of("title", "The Handmaid's Tale"), + ImmutableMap.of("title", "1984")); + } + + @Test + public void offsetAndLimits() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .sort(ascending("author")) + .offset(5) + .limit(3) + .select("title", "author") + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries(entry("title", "1984"), entry("author", "George Orwell")), + mapOfEntries(entry("title", "To Kill a Mockingbird"), entry("author", "Harper Lee")), + mapOfEntries( + entry("title", "The Lord of the Rings"), entry("author", "J.R.R. Tolkien"))); + } + + @Test + public void arrayContainsWorks() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(arrayContains("tags", "comedy")) + .select("title") + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("title", "The Hitchhiker's Guide to the Galaxy")); + } + + @Test + public void arrayContainsAnyWorks() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(arrayContainsAny("tags", ImmutableList.of("comedy", "classic"))) + .select("title") + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("title", "The Hitchhiker's Guide to the Galaxy"), + ImmutableMap.of("title", "Pride and Prejudice")); + } + + @Test + public void arrayContainsAllWorks() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(Field.of("tags").arrayContainsAll(ImmutableList.of("adventure", "magic"))) + .select("title") + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("title", "The Lord of the Rings")); + } + + @Test + public void arrayLengthWorks() { + Task execute = + randomCol + .pipeline() + .select(Field.of("tags").arrayLength().as("tagsCount")) + .where(eq("tagsCount", 3)) + .execute(); + assertThat(waitFor(execute).getResults()).hasSize(10); + } + + @Test + @Ignore("Not supported yet") + public void arrayConcatWorks() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(eq("title", "The Hitchhiker's Guide to the Galaxy")) + .select( + Field.of("tags") + .arrayConcat(ImmutableList.of("newTag1", "newTag2")) + .as("modifiedTags")) + .limit(1) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of( + "modifiedTags", + ImmutableList.of("comedy", "space", "adventure", "newTag1", "newTag2"))); + } + + @Test + public void testStrConcat() { + Task execute = + randomCol + .pipeline() + .select(Field.of("author").strConcat(" - ", Field.of("title")).as("bookInfo")) + .limit(1) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("bookInfo", "Douglas Adams - The Hitchhiker's Guide to the Galaxy")); + } + + @Test + public void testStartsWith() { + Task execute = + randomCol + .pipeline() + .where(startsWith("title", "The")) + .select("title") + .sort(Field.of("title").ascending()) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("title", "The Great Gatsby"), + ImmutableMap.of("title", "The Handmaid's Tale"), + ImmutableMap.of("title", "The Hitchhiker's Guide to the Galaxy"), + ImmutableMap.of("title", "The Lord of the Rings")); + } + + @Test + public void testEndsWith() { + Task execute = + randomCol + .pipeline() + .where(endsWith("title", "y")) + .select("title") + .sort(Field.of("title").descending()) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("title", "The Hitchhiker's Guide to the Galaxy"), + ImmutableMap.of("title", "The Great Gatsby")); + } + + @Test + public void testLength() { + Task execute = + randomCol + .pipeline() + .select(Field.of("title").charLength().as("titleLength"), Field.of("title")) + .where(gt("titleLength", 20)) + .sort(Field.of("title").ascending()) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("titleLength", 29, "title", "One Hundred Years of Solitude"), + ImmutableMap.of("titleLength", 36, "title", "The Hitchhiker's Guide to the Galaxy"), + ImmutableMap.of("titleLength", 21, "title", "The Lord of the Rings"), + ImmutableMap.of("titleLength", 21, "title", "To Kill a Mockingbird")); + } + + @Test + @Ignore("Not supported yet") + public void testToLowercase() { + Task execute = + randomCol + .pipeline() + .select(Field.of("title").toLower().as("lowercaseTitle")) + .limit(1) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("lowercaseTitle", "the hitchhiker's guide to the galaxy")); + } + + @Test + @Ignore("Not supported yet") + public void testToUppercase() { + Task execute = + randomCol + .pipeline() + .select(Field.of("author").toLower().as("uppercaseAuthor")) + .limit(1) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("uppercaseAuthor", "DOUGLAS ADAMS")); + } + + @Test + @Ignore("Not supported yet") + public void testTrim() { + Task execute = + randomCol + .pipeline() + .addFields(strConcat(" ", Field.of("title"), " ").as("spacedTitle")) + .select(Field.of("spacedTitle").trim().as("trimmedTitle")) + .limit(1) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of( + "spacedTitle", + " The Hitchhiker's Guide to the Galaxy ", + "trimmedTitle", + "The Hitchhiker's Guide to the Galaxy")); + } + + @Test + public void testLike() { + Task execute = + randomCol.pipeline().where(Function.like("title", "%Guide%")).select("title").execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("title", "The Hitchhiker's Guide to the Galaxy")); + } + + @Test + public void testRegexContains() { + Task execute = + randomCol.pipeline().where(Function.regexContains("title", "(?i)(the|of)")).execute(); + assertThat(waitFor(execute).getResults()).hasSize(5); + } + + @Test + public void testRegexMatches() { + Task execute = + randomCol.pipeline().where(Function.regexContains("title", ".*(?i)(the|of).*")).execute(); + assertThat(waitFor(execute).getResults()).hasSize(5); + } + + @Test + public void testArithmeticOperations() { + Task execute = + randomCol + .pipeline() + .select( + add(Field.of("rating"), 1).as("ratingPlusOne"), + subtract(Field.of("published"), 1900).as("yearsSince1900"), + Field.of("rating").multiply(10).as("ratingTimesTen"), + Field.of("rating").divide(2).as("ratingDividedByTwo")) + .limit(1) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries( + entry("ratingPlusOne", 5.2), + entry("yearsSince1900", 79), + entry("ratingTimesTen", 42), + entry("ratingDividedByTwo", 2.1))); + } + + @Test + public void testComparisonOperators() { + Task execute = + randomCol + .pipeline() + .where( + and( + gt("rating", 4.2), + lte(Field.of("rating"), 4.5), + neq("genre", "Science Function"))) + .select("rating", "title") + .sort(Field.of("title").ascending()) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("rating", 4.3, "title", "Crime and Punishment"), + ImmutableMap.of("rating", 4.3, "title", "One Hundred Years of Solitude"), + ImmutableMap.of("rating", 4.5, "title", "Pride and Prejudice")); + } + + @Test + public void testLogicalOperators() { + Task execute = + randomCol + .pipeline() + .where( + or( + and(gt("rating", 4.5), eq("genre", "Science Fiction")), + lt(Field.of("published"), 1900))) + .select("title") + .sort(Field.of("title").ascending()) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("title", "Crime and Punishment"), + ImmutableMap.of("title", "Dune"), + ImmutableMap.of("title", "Pride and Prejudice")); + } + + @Test + public void testChecks() { + Task execute = + randomCol + .pipeline() + .where(not(Field.of("rating").isNan())) + .select( + Field.of("rating").isNull().as("ratingIsNull"), + Field.of("rating").eq(Constant.nullValue()).as("ratingEqNull"), + not(Field.of("rating").isNan()).as("ratingIsNotNan")) + .limit(1) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries( + entry("ratingIsNull", false), + entry("ratingEqNull", null), + entry("ratingIsNotNan", true))); + } + + @Test + @Ignore("Not supported yet") + public void testLogicalMax() { + Task execute = + randomCol + .pipeline() + .where(Field.of("author").eq("Douglas Adams")) + .select( + Field.of("rating").logicalMax(4.5).as("max_rating"), + logicalMax(Field.of("published"), 1900).as("max_published")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("max_rating", 4.5, "max_published", 1979)); + } + + @Test + @Ignore("Not supported yet") + public void testLogicalMin() { + Task execute = + randomCol + .pipeline() + .select( + Field.of("rating").logicalMin(4.5).as("min_rating"), + logicalMin(Field.of("published"), 1900).as("min_published")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("min_rating", 4.2, "min_published", 1900)); + } + + @Test + public void testMapGet() { + Task execute = + randomCol + .pipeline() + .select(Field.of("awards").mapGet("hugo").as("hugoAward"), Field.of("title")) + .where(eq("hugoAward", true)) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("hugoAward", true, "title", "The Hitchhiker's Guide to the Galaxy"), + ImmutableMap.of("hugoAward", true, "title", "Dune")); + } + + @Test + public void testDistanceFunctions() { + double[] sourceVector = {0.1, 0.1}; + double[] targetVector = {0.5, 0.8}; + Task execute = + randomCol + .pipeline() + .select( + cosineDistance(Constant.vector(sourceVector), targetVector).as("cosineDistance"), + Function.dotProduct(Constant.vector(sourceVector), targetVector) + .as("dotProductDistance"), + euclideanDistance(Constant.vector(sourceVector), targetVector) + .as("euclideanDistance")) + .limit(1) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of( + "cosineDistance", 0.02560880430538015, + "dotProductDistance", 0.13, + "euclideanDistance", 0.806225774829855)); + } + + @Test + public void testNestedFields() { + Task execute = + randomCol + .pipeline() + .where(eq("awards.hugo", true)) + .select("title", "awards.hugo") + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of("title", "The Hitchhiker's Guide to the Galaxy", "awards.hugo", true), + ImmutableMap.of("title", "Dune", "awards.hugo", true)); + } + + @Test + public void testMapGetWithFieldNameIncludingNotation() { + Task execute = + randomCol + .pipeline() + .where(eq("awards.hugo", true)) + .select( + "title", + Field.of("nestedField.level.1"), + mapGet("nestedField", "level.1").mapGet("level.2").as("nested")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries( + entry("title", "The Hitchhiker's Guide to the Galaxy"), + entry("nestedField.level.`1`", null), + entry("nested", true)), + mapOfEntries( + entry("title", "Dune"), + entry("nestedField.level.`1`", null), + entry("nested", null))); + } + + static Map.Entry entry(String key, T value) { + return new Map.Entry() { + private String k = key; + private T v = value; + + @Override + public String getKey() { + return k; + } + + @Override + public T getValue() { + return v; + } + + @Override + public T setValue(T value) { + T old = v; + v = value; + return old; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + Map.Entry that = (Map.Entry) o; + return com.google.common.base.Objects.equal(k, that.getKey()) + && com.google.common.base.Objects.equal(v, that.getValue()); + } + + @Override + public int hashCode() { + return com.google.common.base.Objects.hashCode(k, v); + } + }; + } + + @SafeVarargs + static Map mapOfEntries(Map.Entry... entries) { + Map res = new LinkedHashMap<>(); + for (Map.Entry entry : entries) { + res.put(entry.getKey(), entry.getValue()); + } + return Collections.unmodifiableMap(res); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/CollectionReference.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/CollectionReference.java index d0a358e2233..297d018c9d6 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/CollectionReference.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/CollectionReference.java @@ -21,6 +21,7 @@ import com.google.android.gms.tasks.Task; import com.google.firebase.firestore.model.DocumentKey; import com.google.firebase.firestore.model.ResourcePath; +import com.google.firebase.firestore.pipeline.CollectionSource; import com.google.firebase.firestore.util.Executors; import com.google.firebase.firestore.util.Util; @@ -127,4 +128,9 @@ public Task add(@NonNull Object data) { return ref; }); } + + @NonNull + public Pipeline pipeline() { + return new Pipeline(firestore, new CollectionSource(getPath())); + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentId.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentId.java index c1d9a870ab9..60787428e58 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentId.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentId.java @@ -43,6 +43,21 @@ * WriteBatch#set(DocumentReference, Object)}), the property annotated by {@code @DocumentId} is * ignored, which allows writing the POJO back to any document, even if it's not the origin of the * POJO. + * + *

Kotlin Note

+ * When applying this annotation to a property of a Kotlin class, the {@code @set} use-site target + * should always be used. There is no need to use the {@code @get} use-site target as this + * annotation is only considered when reading instances from Firestore, and is + * ignored when writing instances into Firestore. + *

+ * Here is an example of a class that can both be written into and read from Firestore whose + * {@code foo} property will be populated with the Document ID when being read and will be ignored + * when being written into Firestore: + *

+ * data class Pojo(@set:DocumentId var foo: String? = null) {
+ *   constructor() : this(null) // Used by Firestore to create new instances
+ * }
+ * 
*/ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentReference.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentReference.java index e3097d32b00..f31e3103060 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentReference.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentReference.java @@ -22,6 +22,7 @@ import android.app.Activity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.Tasks; @@ -33,6 +34,7 @@ import com.google.firebase.firestore.core.UserData.ParsedSetData; import com.google.firebase.firestore.core.UserData.ParsedUpdateData; import com.google.firebase.firestore.core.ViewSnapshot; +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.ResourcePath; @@ -57,7 +59,7 @@ * in test mocks. Subclassing is not supported in production code and new SDK releases may break * code that does so. */ -public class DocumentReference { +public final class DocumentReference { private final DocumentKey key; @@ -65,9 +67,7 @@ public class DocumentReference { DocumentReference(DocumentKey key, FirebaseFirestore firestore) { this.key = checkNotNull(key); - // TODO: We should checkNotNull(firestore), but tests are currently cheating - // and setting it to null. - this.firestore = firestore; + this.firestore = checkNotNull(firestore); } /** @hide */ @@ -120,6 +120,15 @@ public String getPath() { return key.getPath().canonicalString(); } + @RestrictTo(RestrictTo.Scope.LIBRARY) + @NonNull + public String getFullPath() { + DatabaseId databaseId = firestore.getDatabaseId(); + return String.format( + "projects/%s/databases/%s/documents/%s", + databaseId.getProjectId(), databaseId.getDatabaseId(), getPath()); + } + /** * Gets a {@code CollectionReference} instance that refers to the subcollection at the specified * path relative to this document. @@ -564,6 +573,12 @@ public int hashCode() { return result; } + @NonNull + @Override + public String toString() { + return "DocumentReference{" + "key=" + key + ", firestore=" + firestore + '}'; + } + private com.google.firebase.firestore.core.Query asQuery() { return com.google.firebase.firestore.core.Query.atPath(key.getPath()); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentSnapshot.java index 4540608fc48..5c978b8cce9 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentSnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentSnapshot.java @@ -555,6 +555,7 @@ public int hashCode() { return hash; } + @NonNull @Override public String toString() { return "DocumentSnapshot{" + "key=" + key + ", metadata=" + metadata + ", doc=" + doc + '}'; diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Exclude.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Exclude.java index 248bd856c41..010befccd68 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Exclude.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Exclude.java @@ -19,7 +19,31 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** Marks a field as excluded from the database instance. */ +/** + * Marks a field as excluded from the database instance. + * + *

Kotlin Note

+ * When applying this annotation to a property of a Kotlin class, the {@code @get} use-site target + * should always be used. There is no need to use the {@code @set} use-site target as this + * annotation is only considered when writing instances into Firestore, and is + * ignored when reading instances from Firestore. + *

+ * Here is an example of a class that can both be written into and read from Firestore whose + * {@code bar} property will never be written into Firestore: + *

+ * data class Pojo(var foo: String? = null, @get:Exclude var bar: String? = null) {
+ *   constructor() : this(null, null) // Used by Firestore to create new instances
+ * }
+ * 
+ *

+ * If the class only needs to be written into Firestore (and not read from Firestore) then + * the class can be simplified as follows: + *

+ * data class Pojo(val foo: String? = null, @get:Exclude val bar: String? = null)
+ * 
+ * That is, {@code var} can be tightened to {@code val} and the secondary no-argument constructor + * can be omitted. + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface Exclude {} 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 2b5302cff19..fe353b3391b 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 @@ -18,6 +18,7 @@ import static com.google.firebase.firestore.util.Preconditions.checkNotNull; import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; @@ -33,15 +34,18 @@ public final class FieldPath { private final com.google.firebase.firestore.model.FieldPath internalPath; - private FieldPath(List segments) { + private FieldPath(@NonNull List segments) { this.internalPath = com.google.firebase.firestore.model.FieldPath.fromSegments(segments); } - private FieldPath(com.google.firebase.firestore.model.FieldPath internalPath) { + private FieldPath(@NonNull com.google.firebase.firestore.model.FieldPath internalPath) { this.internalPath = internalPath; } - com.google.firebase.firestore.model.FieldPath getInternalPath() { + /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @NonNull + public com.google.firebase.firestore.model.FieldPath getInternalPath() { return internalPath; } @@ -78,7 +82,9 @@ public static FieldPath documentId() { } /** Parses a field path string into a {@code FieldPath}, treating dots as separators. */ - static FieldPath fromDotSeparatedPath(@NonNull String path) { + @RestrictTo(RestrictTo.Scope.LIBRARY) + @NonNull + public static FieldPath fromDotSeparatedPath(@NonNull String path) { checkNotNull(path, "Provided field path must not be null."); checkArgument( !RESERVED.matcher(path).find(), "Use FieldPath.of() for field names containing '~*/[]'."); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java index c1218829b8a..114fc18da95 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java @@ -850,6 +850,7 @@ T callClient(Function call) { return clientProvider.call(call); } + @NonNull DatabaseId getDatabaseId() { return databaseId; } @@ -881,4 +882,10 @@ void validateReference(DocumentReference docRef) { static void setClientLanguage(@NonNull String languageToken) { FirestoreChannel.setClientLanguage(languageToken); } + + @NonNull + public PipelineSource pipeline() { + clientProvider.ensureConfigured(); + return new PipelineSource(this); + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Firestore.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/Firestore.kt index 9f5027b5e29..e2ccf89637d 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Firestore.kt +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Firestore.kt @@ -21,7 +21,6 @@ import com.google.firebase.Firebase import com.google.firebase.FirebaseApp import com.google.firebase.components.Component import com.google.firebase.components.ComponentRegistrar -import com.google.firebase.firestore.* import com.google.firebase.firestore.util.Executors.BACKGROUND_EXECUTOR import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.awaitClose diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Pipeline.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/Pipeline.kt new file mode 100644 index 00000000000..6fa92b88b8b --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Pipeline.kt @@ -0,0 +1,222 @@ +// Copyright 2025 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.android.gms.tasks.Task +import com.google.android.gms.tasks.TaskCompletionSource +import com.google.common.collect.FluentIterable +import com.google.common.collect.ImmutableList +import com.google.firebase.firestore.model.DocumentKey +import com.google.firebase.firestore.model.SnapshotVersion +import com.google.firebase.firestore.pipeline.AccumulatorWithAlias +import com.google.firebase.firestore.pipeline.AddFieldsStage +import com.google.firebase.firestore.pipeline.AggregateStage +import com.google.firebase.firestore.pipeline.BooleanExpr +import com.google.firebase.firestore.pipeline.CollectionGroupSource +import com.google.firebase.firestore.pipeline.CollectionSource +import com.google.firebase.firestore.pipeline.DatabaseSource +import com.google.firebase.firestore.pipeline.DistinctStage +import com.google.firebase.firestore.pipeline.DocumentsSource +import com.google.firebase.firestore.pipeline.Field +import com.google.firebase.firestore.pipeline.LimitStage +import com.google.firebase.firestore.pipeline.OffsetStage +import com.google.firebase.firestore.pipeline.Ordering +import com.google.firebase.firestore.pipeline.RemoveFieldsStage +import com.google.firebase.firestore.pipeline.SelectStage +import com.google.firebase.firestore.pipeline.Selectable +import com.google.firebase.firestore.pipeline.SortStage +import com.google.firebase.firestore.pipeline.Stage +import com.google.firebase.firestore.pipeline.WhereStage +import com.google.firebase.firestore.util.Preconditions +import com.google.firestore.v1.ExecutePipelineRequest +import com.google.firestore.v1.StructuredPipeline +import com.google.firestore.v1.Value + +class Pipeline +internal constructor( + internal val firestore: FirebaseFirestore, + private val stages: FluentIterable +) { + internal constructor( + firestore: FirebaseFirestore, + stage: Stage + ) : this(firestore, FluentIterable.of(stage)) + + private fun append(stage: Stage): Pipeline { + return Pipeline(firestore, stages.append(stage)) + } + + fun execute(): Task { + val observerTask = ObserverSnapshotTask() + firestore.callClient { call -> call!!.executePipeline(toProto(), observerTask) } + return observerTask.task + } + + internal fun documentReference(key: DocumentKey): DocumentReference { + return DocumentReference(key, firestore) + } + + private fun toProto(): ExecutePipelineRequest { + val database = firestore.databaseId + val builder = ExecutePipelineRequest.newBuilder() + builder.database = "projects/${database.projectId}/databases/${database.databaseId}" + builder.structuredPipeline = toStructuredPipelineProto() + return builder.build() + } + + private fun toStructuredPipelineProto(): StructuredPipeline { + val builder = StructuredPipeline.newBuilder() + builder.pipeline = toPipelineProto() + return builder.build() + } + + internal fun toPipelineProto(): com.google.firestore.v1.Pipeline = + com.google.firestore.v1.Pipeline.newBuilder() + .addAllStages(stages.map(Stage::toProtoStage)) + .build() + + fun addFields(vararg fields: Selectable): Pipeline = append(AddFieldsStage(fields)) + + fun removeFields(vararg fields: Field): Pipeline = append(RemoveFieldsStage(fields)) + + fun removeFields(vararg fields: String): Pipeline = + append(RemoveFieldsStage(fields.map(Field::of).toTypedArray())) + + fun select(vararg fields: Selectable): Pipeline = append(SelectStage(fields)) + + fun select(vararg fields: String): Pipeline = + append(SelectStage(fields.map(Field::of).toTypedArray())) + + fun select(vararg fields: Any): Pipeline = + append(SelectStage(fields.map(Selectable::toSelectable).toTypedArray())) + + fun sort(vararg orders: Ordering): Pipeline = append(SortStage(orders)) + + fun where(condition: BooleanExpr): Pipeline = append(WhereStage(condition)) + + fun offset(offset: Long): Pipeline = append(OffsetStage(offset)) + + fun limit(limit: Long): Pipeline = append(LimitStage(limit)) + + fun distinct(vararg groups: Selectable): Pipeline = append(DistinctStage(groups)) + + fun distinct(vararg groups: String): Pipeline = + append(DistinctStage(groups.map(Field::of).toTypedArray())) + + fun distinct(vararg groups: Any): Pipeline = + append(DistinctStage(groups.map(Selectable::toSelectable).toTypedArray())) + + fun aggregate(vararg accumulators: AccumulatorWithAlias): Pipeline = + append(AggregateStage.withAccumulators(*accumulators)) + + fun aggregate(aggregateStage: AggregateStage): Pipeline = append(aggregateStage) + + private inner class ObserverSnapshotTask : PipelineResultObserver { + private val taskCompletionSource = TaskCompletionSource() + private val results: ImmutableList.Builder = ImmutableList.builder() + override fun onDocument(key: DocumentKey?, data: Map, version: SnapshotVersion) { + results.add( + PipelineResult( + firestore, + if (key == null) null else DocumentReference(key, firestore), + data, + version + ) + ) + } + + override fun onComplete(executionTime: SnapshotVersion) { + taskCompletionSource.setResult(PipelineSnapshot(executionTime, results.build())) + } + + override fun onError(exception: FirebaseFirestoreException) { + taskCompletionSource.setException(exception) + } + + val task: Task + get() = taskCompletionSource.task + } +} + +class PipelineSource internal constructor(private val firestore: FirebaseFirestore) { + fun collection(path: String): Pipeline { + // Validate path by converting to CollectionReference + return collection(firestore.collection(path)) + } + + fun collection(ref: CollectionReference): Pipeline { + if (ref.firestore.databaseId != firestore.databaseId) { + throw IllegalArgumentException( + "Provided collection reference is from a different Firestore instance." + ) + } + return Pipeline(firestore, CollectionSource(ref.path)) + } + + fun collectionGroup(collectionId: String): Pipeline { + Preconditions.checkNotNull(collectionId, "Provided collection ID must not be null.") + require(!collectionId.contains("/")) { + "Invalid collectionId '$collectionId'. Collection IDs must not contain '/'." + } + return Pipeline(firestore, CollectionGroupSource(collectionId)) + } + + fun database(): Pipeline = Pipeline(firestore, DatabaseSource()) + + fun documents(vararg documents: String): Pipeline { + // Validate document path by converting to DocumentReference + return documents(*documents.map(firestore::document).toTypedArray()) + } + + fun documents(vararg documents: DocumentReference): Pipeline { + val databaseId = firestore.databaseId + for (document in documents) { + if (document.firestore.databaseId != databaseId) { + throw IllegalArgumentException( + "Provided document reference is from a different Firestore instance." + ) + } + } + return Pipeline( + firestore, + DocumentsSource(documents.map { docRef -> "/" + docRef.path }.toTypedArray()) + ) + } +} + +class PipelineSnapshot +internal constructor(private val executionTime: SnapshotVersion, val results: List) + +class PipelineResult +internal constructor( + private val firestore: FirebaseFirestore, + val ref: DocumentReference?, + private val fields: Map, + private val version: SnapshotVersion, +) { + + fun getData(): Map = userDataWriter().convertObject(fields) + + private fun userDataWriter(): UserDataWriter = + UserDataWriter(firestore, DocumentSnapshot.ServerTimestampBehavior.DEFAULT) + + override fun toString() = "PipelineResult{ref=$ref, version=$version}, data=${getData()}" +} + +internal interface PipelineResultObserver { + fun onDocument(key: DocumentKey?, data: Map, version: SnapshotVersion) + fun onComplete(executionTime: SnapshotVersion) + fun onError(exception: FirebaseFirestoreException) +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/PropertyName.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/PropertyName.java index 7e46c0f4c0a..15fa188c026 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/PropertyName.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/PropertyName.java @@ -19,7 +19,30 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** Marks a field to be renamed when serialized. */ +/** + * Marks a field to be renamed when serialized. + * + *

Kotlin Note

+ * When applying this annotation to a property of a Kotlin class, both the {@code @get} and + * {@code @set} use-site targets should be used. + *

+ * Here is an example of a class that can both be written into and read from Firestore whose + * {@code foo} property will be stored into and read from a field named {@code my_foo} in the + * Firestore document: + *

+ * data class Pojo(@get:PropertyName("my_foo") @set:PropertyName("my_foo") var foo: String? = null) {
+ *   constructor() : this(null) // Used by Firestore to create new instances
+ * }
+ * 
+ *

+ * If the class only needs to be written into Firestore (and not read from Firestore) then + * the class can be simplified as follows: + *

+ * data class Pojo(@get:PropertyName("my_foo") val foo: String? = null)
+ * 
+ * That is, {@code var} can be tightened to {@code val}, the secondary no-argument constructor can + * be omitted, and the {@code @set} use-site target can be omitted. + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface PropertyName { diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/ServerTimestamp.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/ServerTimestamp.java index 72d5848a103..b41738af33a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/ServerTimestamp.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/ServerTimestamp.java @@ -23,6 +23,29 @@ * Annotation used to mark a timestamp field to be populated with a server timestamp. If a POJO * being written contains {@code null} for a @ServerTimestamp-annotated field, it will be replaced * with a server-generated timestamp. + * + *

Kotlin Note

+ * When applying this annotation to a property of a Kotlin class, the {@code @get} use-site target + * should always be used. There is no need to use the {@code @set} use-site target as this + * annotation is only considered when writing instances into Firestore, and is + * ignored when reading instances from Firestore. + *

+ * Here is an example of a class that can both be written into and read from Firestore whose + * {@code foo} property will be populated with the server timestamp in Firestore if its value is + * null: + *

+ * data class Pojo(@get:ServerTimestamp var foo: Timestamp? = null) {
+ *   constructor() : this(null) // Used by Firestore to create new instances
+ * }
+ * 
+ *

+ * If the class only needs to be written into Firestore (and not read from Firestore) then + * the class can be simplified as follows: + *

+ * data class Pojo(@get:ServerTimestamp val foo: Timestamp? = null)
+ * 
+ * That is, {@code var} can be tightened to {@code val} and the secondary no-argument constructor + * can be omitted. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/UserDataReader.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/UserDataReader.java index 297479d0262..b1462ed9f74 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/UserDataReader.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/UserDataReader.java @@ -19,7 +19,6 @@ import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; -import com.google.firebase.Timestamp; import com.google.firebase.firestore.FieldValue.ArrayRemoveFieldValue; import com.google.firebase.firestore.FieldValue.ArrayUnionFieldValue; import com.google.firebase.firestore.FieldValue.DeleteFieldValue; @@ -44,9 +43,7 @@ import com.google.firestore.v1.MapValue; import com.google.firestore.v1.Value; import com.google.protobuf.NullValue; -import com.google.type.LatLng; import java.util.ArrayList; -import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -387,92 +384,20 @@ private void parseSentinelFieldValue( * @return The parsed value, or {@code null} if the value was a FieldValue sentinel that should * not be included in the resulting parsed data. */ - private Value parseScalarValue(Object input, ParseContext context) { + public Value parseScalarValue(Object input, ParseContext context) { if (input == null) { - return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(); - } else if (input instanceof Integer) { - return Value.newBuilder().setIntegerValue((Integer) input).build(); - } else if (input instanceof Long) { - return Value.newBuilder().setIntegerValue((Long) input).build(); - } else if (input instanceof Float) { - return Value.newBuilder().setDoubleValue(((Float) input).doubleValue()).build(); - } else if (input instanceof Double) { - return Value.newBuilder().setDoubleValue((Double) input).build(); - } else if (input instanceof Boolean) { - return Value.newBuilder().setBooleanValue((Boolean) input).build(); - } else if (input instanceof String) { - return Value.newBuilder().setStringValue((String) input).build(); - } else if (input instanceof Date) { - Timestamp timestamp = new Timestamp((Date) input); - return parseTimestamp(timestamp); - } else if (input instanceof Timestamp) { - Timestamp timestamp = (Timestamp) input; - return parseTimestamp(timestamp); - } else if (input instanceof GeoPoint) { - GeoPoint geoPoint = (GeoPoint) input; - return Value.newBuilder() - .setGeoPointValue( - LatLng.newBuilder() - .setLatitude(geoPoint.getLatitude()) - .setLongitude(geoPoint.getLongitude())) - .build(); - } else if (input instanceof Blob) { - return Value.newBuilder().setBytesValue(((Blob) input).toByteString()).build(); - } else if (input instanceof DocumentReference) { - DocumentReference ref = (DocumentReference) input; - // TODO: Rework once pre-converter is ported to Android. - if (ref.getFirestore() != null) { - DatabaseId otherDb = ref.getFirestore().getDatabaseId(); - if (!otherDb.equals(databaseId)) { - throw context.createError( - String.format( - "Document reference is for database %s/%s but should be for database %s/%s", - otherDb.getProjectId(), - otherDb.getDatabaseId(), - databaseId.getProjectId(), - databaseId.getDatabaseId())); - } - } - return Value.newBuilder() - .setReferenceValue( - String.format( - "projects/%s/databases/%s/documents/%s", - databaseId.getProjectId(), - databaseId.getDatabaseId(), - ((DocumentReference) input).getPath())) - .build(); - } else if (input instanceof VectorValue) { - return parseVectorValue(((VectorValue) input), context); + return Values.NULL_VALUE; } else if (input.getClass().isArray()) { throw context.createError("Arrays are not supported; use a List instead"); } else { - throw context.createError("Unsupported type: " + Util.typeName(input)); + try { + return Values.encodeAnyValue(input); + } catch (IllegalArgumentException e) { + throw context.createError("Unsupported type: " + Util.typeName(input)); + } } } - private Value parseVectorValue(VectorValue vector, ParseContext context) { - MapValue.Builder mapBuilder = MapValue.newBuilder(); - - mapBuilder.putFields(Values.TYPE_KEY, Values.VECTOR_VALUE_TYPE); - mapBuilder.putFields(Values.VECTOR_MAP_VECTORS_KEY, parseData(vector.toList(), context)); - - return Value.newBuilder().setMapValue(mapBuilder).build(); - } - - private Value parseTimestamp(Timestamp timestamp) { - // Firestore backend truncates precision down to microseconds. To ensure offline mode works - // the same with regards to truncation, perform the truncation immediately without waiting for - // the backend to do that. - int truncatedNanoseconds = timestamp.getNanoseconds() / 1000 * 1000; - - return Value.newBuilder() - .setTimestampValue( - com.google.protobuf.Timestamp.newBuilder() - .setSeconds(timestamp.getSeconds()) - .setNanos(truncatedNanoseconds)) - .build(); - } - private List parseArrayTransformElements(List elements) { ParseAccumulator accumulator = new ParseAccumulator(UserData.Source.Argument); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/VectorValue.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/VectorValue.java index 2f355648376..efcefd45bf4 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/VectorValue.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/VectorValue.java @@ -16,9 +16,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; /** * Represent a vector type in Firestore documents. @@ -41,21 +39,6 @@ public double[] toArray() { return this.values.clone(); } - /** - * Package private. - * Returns a representation of the vector as a List. - * - * @return A representation of the vector as an List - */ - @NonNull - List toList() { - ArrayList result = new ArrayList(this.values.length); - for (int i = 0; i < this.values.length; i++) { - result.add(i, this.values[i]); - } - return result; - } - /** * Returns true if this VectorValue is equal to the provided object. * diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index 6e2d9b87b84..d54e3458d52 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -27,6 +27,7 @@ import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.FirebaseFirestoreException.Code; import com.google.firebase.firestore.LoadBundleTask; +import com.google.firebase.firestore.PipelineResultObserver; import com.google.firebase.firestore.TransactionOptions; import com.google.firebase.firestore.auth.CredentialsProvider; import com.google.firebase.firestore.auth.User; @@ -49,6 +50,7 @@ import com.google.firebase.firestore.util.AsyncQueue; import com.google.firebase.firestore.util.Function; import com.google.firebase.firestore.util.Logger; +import com.google.firestore.v1.ExecutePipelineRequest; import com.google.firestore.v1.Value; import java.io.InputStream; import java.util.List; @@ -249,6 +251,10 @@ public Task> runAggregateQuery( return result.getTask(); } + public void executePipeline(ExecutePipelineRequest request, PipelineResultObserver observer) { + asyncQueue.enqueueAndForget(() -> remoteStore.executePipeline(request, observer)); + } + /** * Returns a task resolves when all the pending writes at the time when this method is called * received server acknowledgement. An acknowledgement can be either acceptance or rejections. diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/BasePath.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/BasePath.java index 58fedecd7ad..66356e12595 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/BasePath.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/BasePath.java @@ -114,7 +114,7 @@ private static int compareSegments(String lhs, String rhs) { } else if (isLhsNumeric && isRhsNumeric) { // both numeric return Long.compare(extractNumericId(lhs), extractNumericId(rhs)); } else { // both string - return lhs.compareTo(rhs); + return Util.compareUtf8Strings(lhs, rhs); } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/FieldPath.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/FieldPath.java index c1de25410fe..051dfce922b 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/FieldPath.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/FieldPath.java @@ -14,6 +14,7 @@ package com.google.firebase.firestore.model; +import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -34,7 +35,8 @@ public static FieldPath fromSingleSegment(String fieldName) { } /** Creates a {@code FieldPath} from a list of parsed field path segments. */ - public static FieldPath fromSegments(List segments) { + @NonNull + public static FieldPath fromSegments(@NonNull List segments) { return segments.isEmpty() ? FieldPath.EMPTY_PATH : new FieldPath(segments); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/ResourcePath.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/ResourcePath.java index c96fcbdc3ee..776953d55d2 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/ResourcePath.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/ResourcePath.java @@ -14,6 +14,7 @@ package com.google.firebase.firestore.model; +import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -36,7 +37,7 @@ public static ResourcePath fromSegments(List segments) { return segments.isEmpty() ? ResourcePath.EMPTY : new ResourcePath(segments); } - public static ResourcePath fromString(String path) { + public static ResourcePath fromString(@NonNull String path) { // NOTE: The client is ignorant of any path segments containing escape // sequences (for example, __id123__) and just passes them through raw (they exist // for legacy reasons and should not be used frequently). @@ -65,13 +66,6 @@ public String canonicalString() { // NOTE: The client is ignorant of any path segments containing escape // sequences (for example, __id123__) and just passes them through raw (they exist // for legacy reasons and should not be used frequently). - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < segments.size(); i++) { - if (i > 0) { - builder.append("/"); - } - builder.append(segments.get(i)); - } - return builder.toString(); + return String.join("/", segments); } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/Values.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/Values.java deleted file mode 100644 index 26b06a5d2cb..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/Values.java +++ /dev/null @@ -1,615 +0,0 @@ -// Copyright 2020 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.model; - -import static com.google.firebase.firestore.model.ServerTimestamps.getLocalWriteTime; -import static com.google.firebase.firestore.model.ServerTimestamps.isServerTimestamp; -import static com.google.firebase.firestore.util.Assert.fail; -import static com.google.firebase.firestore.util.Assert.hardAssert; - -import androidx.annotation.Nullable; -import com.google.firebase.firestore.util.Util; -import com.google.firestore.v1.ArrayValue; -import com.google.firestore.v1.ArrayValueOrBuilder; -import com.google.firestore.v1.MapValue; -import com.google.firestore.v1.Value; -import com.google.protobuf.ByteString; -import com.google.protobuf.NullValue; -import com.google.protobuf.Timestamp; -import com.google.type.LatLng; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -public class Values { - public static final String TYPE_KEY = "__type__"; - public static final Value NAN_VALUE = Value.newBuilder().setDoubleValue(Double.NaN).build(); - public static final Value NULL_VALUE = - Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(); - public static final Value MIN_VALUE = NULL_VALUE; - public static final Value MAX_VALUE_TYPE = Value.newBuilder().setStringValue("__max__").build(); - public static final Value MAX_VALUE = - Value.newBuilder() - .setMapValue(MapValue.newBuilder().putFields(TYPE_KEY, MAX_VALUE_TYPE)) - .build(); - - public static final Value VECTOR_VALUE_TYPE = - Value.newBuilder().setStringValue("__vector__").build(); - public static final String VECTOR_MAP_VECTORS_KEY = "value"; - private static final Value MIN_VECTOR_VALUE = - Value.newBuilder() - .setMapValue( - MapValue.newBuilder() - .putFields(TYPE_KEY, VECTOR_VALUE_TYPE) - .putFields( - VECTOR_MAP_VECTORS_KEY, - Value.newBuilder().setArrayValue(ArrayValue.newBuilder()).build())) - .build(); - - /** - * The order of types in Firestore. This order is based on the backend's ordering, but modified to - * support server timestamps and {@link #MAX_VALUE}. - */ - public static final int TYPE_ORDER_NULL = 0; - - public static final int TYPE_ORDER_BOOLEAN = 1; - public static final int TYPE_ORDER_NUMBER = 2; - public static final int TYPE_ORDER_TIMESTAMP = 3; - public static final int TYPE_ORDER_SERVER_TIMESTAMP = 4; - public static final int TYPE_ORDER_STRING = 5; - public static final int TYPE_ORDER_BLOB = 6; - public static final int TYPE_ORDER_REFERENCE = 7; - public static final int TYPE_ORDER_GEOPOINT = 8; - public static final int TYPE_ORDER_ARRAY = 9; - public static final int TYPE_ORDER_VECTOR = 10; - public static final int TYPE_ORDER_MAP = 11; - - public static final int TYPE_ORDER_MAX_VALUE = Integer.MAX_VALUE; - - /** Returns the backend's type order of the given Value type. */ - public static int typeOrder(Value value) { - switch (value.getValueTypeCase()) { - case NULL_VALUE: - return TYPE_ORDER_NULL; - case BOOLEAN_VALUE: - return TYPE_ORDER_BOOLEAN; - case INTEGER_VALUE: - return TYPE_ORDER_NUMBER; - case DOUBLE_VALUE: - return TYPE_ORDER_NUMBER; - case TIMESTAMP_VALUE: - return TYPE_ORDER_TIMESTAMP; - case STRING_VALUE: - return TYPE_ORDER_STRING; - case BYTES_VALUE: - return TYPE_ORDER_BLOB; - case REFERENCE_VALUE: - return TYPE_ORDER_REFERENCE; - case GEO_POINT_VALUE: - return TYPE_ORDER_GEOPOINT; - case ARRAY_VALUE: - return TYPE_ORDER_ARRAY; - case MAP_VALUE: - if (isServerTimestamp(value)) { - return TYPE_ORDER_SERVER_TIMESTAMP; - } else if (isMaxValue(value)) { - return TYPE_ORDER_MAX_VALUE; - } else if (isVectorValue(value)) { - return TYPE_ORDER_VECTOR; - } else { - return TYPE_ORDER_MAP; - } - default: - throw fail("Invalid value type: " + value.getValueTypeCase()); - } - } - - public static boolean equals(Value left, Value right) { - if (left == right) { - return true; - } - - if (left == null || right == null) { - return false; - } - - int leftType = typeOrder(left); - int rightType = typeOrder(right); - if (leftType != rightType) { - return false; - } - - switch (leftType) { - case TYPE_ORDER_NUMBER: - return numberEquals(left, right); - case TYPE_ORDER_ARRAY: - return arrayEquals(left, right); - case TYPE_ORDER_VECTOR: - case TYPE_ORDER_MAP: - return objectEquals(left, right); - case TYPE_ORDER_SERVER_TIMESTAMP: - return getLocalWriteTime(left).equals(getLocalWriteTime(right)); - case TYPE_ORDER_MAX_VALUE: - return true; - default: - return left.equals(right); - } - } - - private static boolean numberEquals(Value left, Value right) { - if (left.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE - && right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) { - return left.getIntegerValue() == right.getIntegerValue(); - } else if (left.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE - && right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) { - return Double.doubleToLongBits(left.getDoubleValue()) - == Double.doubleToLongBits(right.getDoubleValue()); - } - - return false; - } - - private static boolean arrayEquals(Value left, Value right) { - ArrayValue leftArray = left.getArrayValue(); - ArrayValue rightArray = right.getArrayValue(); - - if (leftArray.getValuesCount() != rightArray.getValuesCount()) { - return false; - } - - for (int i = 0; i < leftArray.getValuesCount(); ++i) { - if (!equals(leftArray.getValues(i), rightArray.getValues(i))) { - return false; - } - } - - return true; - } - - private static boolean objectEquals(Value left, Value right) { - MapValue leftMap = left.getMapValue(); - MapValue rightMap = right.getMapValue(); - - if (leftMap.getFieldsCount() != rightMap.getFieldsCount()) { - return false; - } - - for (Map.Entry entry : leftMap.getFieldsMap().entrySet()) { - Value otherEntry = rightMap.getFieldsMap().get(entry.getKey()); - if (!equals(entry.getValue(), otherEntry)) { - return false; - } - } - - return true; - } - - /** Returns true if the Value list contains the specified element. */ - public static boolean contains(ArrayValueOrBuilder haystack, Value needle) { - for (Value haystackElement : haystack.getValuesList()) { - if (equals(haystackElement, needle)) { - return true; - } - } - return false; - } - - public static int compare(Value left, Value right) { - int leftType = typeOrder(left); - int rightType = typeOrder(right); - - if (leftType != rightType) { - return Util.compareIntegers(leftType, rightType); - } - - switch (leftType) { - case TYPE_ORDER_NULL: - case TYPE_ORDER_MAX_VALUE: - return 0; - case TYPE_ORDER_BOOLEAN: - return Util.compareBooleans(left.getBooleanValue(), right.getBooleanValue()); - case TYPE_ORDER_NUMBER: - return compareNumbers(left, right); - case TYPE_ORDER_TIMESTAMP: - return compareTimestamps(left.getTimestampValue(), right.getTimestampValue()); - case TYPE_ORDER_SERVER_TIMESTAMP: - return compareTimestamps(getLocalWriteTime(left), getLocalWriteTime(right)); - case TYPE_ORDER_STRING: - return left.getStringValue().compareTo(right.getStringValue()); - case TYPE_ORDER_BLOB: - return Util.compareByteStrings(left.getBytesValue(), right.getBytesValue()); - case TYPE_ORDER_REFERENCE: - return compareReferences(left.getReferenceValue(), right.getReferenceValue()); - case TYPE_ORDER_GEOPOINT: - return compareGeoPoints(left.getGeoPointValue(), right.getGeoPointValue()); - case TYPE_ORDER_ARRAY: - return compareArrays(left.getArrayValue(), right.getArrayValue()); - case TYPE_ORDER_MAP: - return compareMaps(left.getMapValue(), right.getMapValue()); - case TYPE_ORDER_VECTOR: - return compareVectors(left.getMapValue(), right.getMapValue()); - default: - throw fail("Invalid value type: " + leftType); - } - } - - public static int lowerBoundCompare( - Value left, boolean leftInclusive, Value right, boolean rightInclusive) { - int cmp = compare(left, right); - if (cmp != 0) { - return cmp; - } - - if (leftInclusive && !rightInclusive) { - return -1; - } else if (!leftInclusive && rightInclusive) { - return 1; - } - - return 0; - } - - public static int upperBoundCompare( - Value left, boolean leftInclusive, Value right, boolean rightInclusive) { - int cmp = compare(left, right); - if (cmp != 0) { - return cmp; - } - - if (leftInclusive && !rightInclusive) { - return 1; - } else if (!leftInclusive && rightInclusive) { - return -1; - } - - return 0; - } - - private static int compareNumbers(Value left, Value right) { - if (left.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) { - double leftDouble = left.getDoubleValue(); - if (right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) { - return Util.compareDoubles(leftDouble, right.getDoubleValue()); - } else if (right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) { - return Util.compareMixed(leftDouble, right.getIntegerValue()); - } - } else if (left.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) { - long leftLong = left.getIntegerValue(); - if (right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) { - return Util.compareLongs(leftLong, right.getIntegerValue()); - } else if (right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) { - return -1 * Util.compareMixed(right.getDoubleValue(), leftLong); - } - } - - throw fail("Unexpected values: %s vs %s", left, right); - } - - private static int compareTimestamps(Timestamp left, Timestamp right) { - int cmp = Util.compareLongs(left.getSeconds(), right.getSeconds()); - if (cmp != 0) { - return cmp; - } - return Util.compareIntegers(left.getNanos(), right.getNanos()); - } - - private static int compareReferences(String leftPath, String rightPath) { - String[] leftSegments = leftPath.split("/", -1); - String[] rightSegments = rightPath.split("/", -1); - - int minLength = Math.min(leftSegments.length, rightSegments.length); - for (int i = 0; i < minLength; i++) { - int cmp = leftSegments[i].compareTo(rightSegments[i]); - if (cmp != 0) { - return cmp; - } - } - return Util.compareIntegers(leftSegments.length, rightSegments.length); - } - - private static int compareGeoPoints(LatLng left, LatLng right) { - int comparison = Util.compareDoubles(left.getLatitude(), right.getLatitude()); - if (comparison == 0) { - return Util.compareDoubles(left.getLongitude(), right.getLongitude()); - } - return comparison; - } - - private static int compareArrays(ArrayValue left, ArrayValue right) { - int minLength = Math.min(left.getValuesCount(), right.getValuesCount()); - for (int i = 0; i < minLength; i++) { - int cmp = compare(left.getValues(i), right.getValues(i)); - if (cmp != 0) { - return cmp; - } - } - return Util.compareIntegers(left.getValuesCount(), right.getValuesCount()); - } - - private static int compareMaps(MapValue left, MapValue right) { - Iterator> iterator1 = - new TreeMap<>(left.getFieldsMap()).entrySet().iterator(); - Iterator> iterator2 = - new TreeMap<>(right.getFieldsMap()).entrySet().iterator(); - while (iterator1.hasNext() && iterator2.hasNext()) { - Map.Entry entry1 = iterator1.next(); - Map.Entry entry2 = iterator2.next(); - int keyCompare = entry1.getKey().compareTo(entry2.getKey()); - if (keyCompare != 0) { - return keyCompare; - } - int valueCompare = compare(entry1.getValue(), entry2.getValue()); - if (valueCompare != 0) { - return valueCompare; - } - } - - // Only equal if both iterators are exhausted. - return Util.compareBooleans(iterator1.hasNext(), iterator2.hasNext()); - } - - private static int compareVectors(MapValue left, MapValue right) { - Map leftMap = left.getFieldsMap(); - Map rightMap = right.getFieldsMap(); - - // The vector is a map, but only vector value is compared. - ArrayValue leftArrayValue = leftMap.get(Values.VECTOR_MAP_VECTORS_KEY).getArrayValue(); - ArrayValue rightArrayValue = rightMap.get(Values.VECTOR_MAP_VECTORS_KEY).getArrayValue(); - - int lengthCompare = - Util.compareIntegers(leftArrayValue.getValuesCount(), rightArrayValue.getValuesCount()); - if (lengthCompare != 0) { - return lengthCompare; - } - - return compareArrays(leftArrayValue, rightArrayValue); - } - - /** Generate the canonical ID for the provided field value (as used in Target serialization). */ - public static String canonicalId(Value value) { - StringBuilder builder = new StringBuilder(); - canonifyValue(builder, value); - return builder.toString(); - } - - private static void canonifyValue(StringBuilder builder, Value value) { - switch (value.getValueTypeCase()) { - case NULL_VALUE: - builder.append("null"); - break; - case BOOLEAN_VALUE: - builder.append(value.getBooleanValue()); - break; - case INTEGER_VALUE: - builder.append(value.getIntegerValue()); - break; - case DOUBLE_VALUE: - builder.append(value.getDoubleValue()); - break; - case TIMESTAMP_VALUE: - canonifyTimestamp(builder, value.getTimestampValue()); - break; - case STRING_VALUE: - builder.append(value.getStringValue()); - break; - case BYTES_VALUE: - builder.append(Util.toDebugString(value.getBytesValue())); - break; - case REFERENCE_VALUE: - canonifyReference(builder, value); - break; - case GEO_POINT_VALUE: - canonifyGeoPoint(builder, value.getGeoPointValue()); - break; - case ARRAY_VALUE: - canonifyArray(builder, value.getArrayValue()); - break; - case MAP_VALUE: - canonifyObject(builder, value.getMapValue()); - break; - default: - throw fail("Invalid value type: " + value.getValueTypeCase()); - } - } - - private static void canonifyTimestamp(StringBuilder builder, Timestamp timestamp) { - builder.append(String.format("time(%s,%s)", timestamp.getSeconds(), timestamp.getNanos())); - } - - private static void canonifyGeoPoint(StringBuilder builder, LatLng latLng) { - builder.append(String.format("geo(%s,%s)", latLng.getLatitude(), latLng.getLongitude())); - } - - private static void canonifyReference(StringBuilder builder, Value value) { - hardAssert(isReferenceValue(value), "Value should be a ReferenceValue"); - builder.append(DocumentKey.fromName(value.getReferenceValue())); - } - - private static void canonifyObject(StringBuilder builder, MapValue mapValue) { - // Even though MapValue are likely sorted correctly based on their insertion order (for example, - // when received from the backend), local modifications can bring elements out of order. We need - // to re-sort the elements to ensure that canonical IDs are independent of insertion order. - List keys = new ArrayList<>(mapValue.getFieldsMap().keySet()); - Collections.sort(keys); - - builder.append("{"); - boolean first = true; - for (String key : keys) { - if (!first) { - builder.append(","); - } else { - first = false; - } - builder.append(key).append(":"); - canonifyValue(builder, mapValue.getFieldsOrThrow(key)); - } - builder.append("}"); - } - - private static void canonifyArray(StringBuilder builder, ArrayValue arrayValue) { - builder.append("["); - for (int i = 0; i < arrayValue.getValuesCount(); ++i) { - canonifyValue(builder, arrayValue.getValues(i)); - if (i != arrayValue.getValuesCount() - 1) { - builder.append(","); - } - } - builder.append("]"); - } - - /** Returns true if `value` is a INTEGER_VALUE. */ - public static boolean isInteger(@Nullable Value value) { - return value != null && value.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE; - } - - /** Returns true if `value` is a DOUBLE_VALUE. */ - public static boolean isDouble(@Nullable Value value) { - return value != null && value.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE; - } - - /** Returns true if `value` is either a INTEGER_VALUE or a DOUBLE_VALUE. */ - public static boolean isNumber(@Nullable Value value) { - return isInteger(value) || isDouble(value); - } - - /** Returns true if `value` is an ARRAY_VALUE. */ - public static boolean isArray(@Nullable Value value) { - return value != null && value.getValueTypeCase() == Value.ValueTypeCase.ARRAY_VALUE; - } - - public static boolean isReferenceValue(@Nullable Value value) { - return value != null && value.getValueTypeCase() == Value.ValueTypeCase.REFERENCE_VALUE; - } - - public static boolean isNullValue(@Nullable Value value) { - return value != null && value.getValueTypeCase() == Value.ValueTypeCase.NULL_VALUE; - } - - public static boolean isNanValue(@Nullable Value value) { - return value != null && Double.isNaN(value.getDoubleValue()); - } - - public static boolean isMapValue(@Nullable Value value) { - return value != null && value.getValueTypeCase() == Value.ValueTypeCase.MAP_VALUE; - } - - public static Value refValue(DatabaseId databaseId, DocumentKey key) { - Value value = - Value.newBuilder() - .setReferenceValue( - String.format( - "projects/%s/databases/%s/documents/%s", - databaseId.getProjectId(), databaseId.getDatabaseId(), key.toString())) - .build(); - return value; - } - - public static Value MIN_BOOLEAN = Value.newBuilder().setBooleanValue(false).build(); - public static Value MIN_NUMBER = Value.newBuilder().setDoubleValue(Double.NaN).build(); - public static Value MIN_TIMESTAMP = - Value.newBuilder() - .setTimestampValue(Timestamp.newBuilder().setSeconds(Long.MIN_VALUE)) - .build(); - public static Value MIN_STRING = Value.newBuilder().setStringValue("").build(); - public static Value MIN_BYTES = Value.newBuilder().setBytesValue(ByteString.EMPTY).build(); - public static Value MIN_REFERENCE = refValue(DatabaseId.EMPTY, DocumentKey.empty()); - public static Value MIN_GEO_POINT = - Value.newBuilder() - .setGeoPointValue(LatLng.newBuilder().setLatitude(-90.0).setLongitude(-180.0)) - .build(); - public static Value MIN_ARRAY = - Value.newBuilder().setArrayValue(ArrayValue.getDefaultInstance()).build(); - public static Value MIN_MAP = - Value.newBuilder().setMapValue(MapValue.getDefaultInstance()).build(); - - /** Returns the lowest value for the given value type (inclusive). */ - public static Value getLowerBound(Value value) { - switch (value.getValueTypeCase()) { - case NULL_VALUE: - return Values.NULL_VALUE; - case BOOLEAN_VALUE: - return MIN_BOOLEAN; - case INTEGER_VALUE: - case DOUBLE_VALUE: - return MIN_NUMBER; - case TIMESTAMP_VALUE: - return MIN_TIMESTAMP; - case STRING_VALUE: - return MIN_STRING; - case BYTES_VALUE: - return MIN_BYTES; - case REFERENCE_VALUE: - return MIN_REFERENCE; - case GEO_POINT_VALUE: - return MIN_GEO_POINT; - case ARRAY_VALUE: - return MIN_ARRAY; - case MAP_VALUE: - // VectorValue sorts after ArrayValue and before an empty MapValue - if (isVectorValue(value)) { - return MIN_VECTOR_VALUE; - } - return MIN_MAP; - default: - throw new IllegalArgumentException("Unknown value type: " + value.getValueTypeCase()); - } - } - - /** Returns the largest value for the given value type (exclusive). */ - public static Value getUpperBound(Value value) { - switch (value.getValueTypeCase()) { - case NULL_VALUE: - return MIN_BOOLEAN; - case BOOLEAN_VALUE: - return MIN_NUMBER; - case INTEGER_VALUE: - case DOUBLE_VALUE: - return MIN_TIMESTAMP; - case TIMESTAMP_VALUE: - return MIN_STRING; - case STRING_VALUE: - return MIN_BYTES; - case BYTES_VALUE: - return MIN_REFERENCE; - case REFERENCE_VALUE: - return MIN_GEO_POINT; - case GEO_POINT_VALUE: - return MIN_ARRAY; - case ARRAY_VALUE: - return MIN_VECTOR_VALUE; - case MAP_VALUE: - // VectorValue sorts after ArrayValue and before an empty MapValue - if (isVectorValue(value)) { - return MIN_MAP; - } - return MAX_VALUE; - default: - throw new IllegalArgumentException("Unknown value type: " + value.getValueTypeCase()); - } - } - - /** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */ - public static boolean isMaxValue(Value value) { - return MAX_VALUE_TYPE.equals(value.getMapValue().getFieldsMap().get(TYPE_KEY)); - } - - /** Returns true if the Value represents a VectorValue . */ - public static boolean isVectorValue(Value value) { - return VECTOR_VALUE_TYPE.equals(value.getMapValue().getFieldsMap().get(TYPE_KEY)); - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/model/Values.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/Values.kt new file mode 100644 index 00000000000..3b9701bc4ec --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/Values.kt @@ -0,0 +1,684 @@ +// Copyright 2020 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.model + +import com.google.firebase.firestore.Blob +import com.google.firebase.firestore.DocumentReference +import com.google.firebase.firestore.GeoPoint +import com.google.firebase.firestore.VectorValue +import com.google.firebase.firestore.util.Assert +import com.google.firebase.firestore.util.Util +import com.google.firestore.v1.ArrayValue +import com.google.firestore.v1.ArrayValueOrBuilder +import com.google.firestore.v1.MapValue +import com.google.firestore.v1.Value +import com.google.firestore.v1.Value.ValueTypeCase +import com.google.protobuf.ByteString +import com.google.protobuf.NullValue +import com.google.protobuf.Timestamp +import com.google.type.LatLng +import java.lang.Double.doubleToLongBits +import java.util.Date +import java.util.TreeMap +import kotlin.math.min + +internal object Values { + const val TYPE_KEY: String = "__type__" + @JvmField val NAN_VALUE: Value = Value.newBuilder().setDoubleValue(Double.NaN).build() + @JvmField val NULL_VALUE: Value = Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build() + @JvmField val MIN_VALUE: Value = NULL_VALUE + @JvmField val MAX_VALUE_TYPE: Value = Value.newBuilder().setStringValue("__max__").build() + @JvmField + val MAX_VALUE: Value = + Value.newBuilder() + .setMapValue(MapValue.newBuilder().putFields(TYPE_KEY, MAX_VALUE_TYPE)) + .build() + + @JvmField val VECTOR_VALUE_TYPE: Value = Value.newBuilder().setStringValue("__vector__").build() + const val VECTOR_MAP_VECTORS_KEY: String = "value" + private val MIN_VECTOR_VALUE: Value = + Value.newBuilder() + .setMapValue( + MapValue.newBuilder() + .putFields(TYPE_KEY, VECTOR_VALUE_TYPE) + .putFields( + VECTOR_MAP_VECTORS_KEY, + Value.newBuilder().setArrayValue(ArrayValue.newBuilder()).build() + ) + ) + .build() + + /** + * The order of types in Firestore. This order is based on the backend's ordering, but modified to + * support server timestamps and [.MAX_VALUE]. + */ + const val TYPE_ORDER_NULL: Int = 0 + + const val TYPE_ORDER_BOOLEAN: Int = 1 + const val TYPE_ORDER_NUMBER: Int = 2 + const val TYPE_ORDER_TIMESTAMP: Int = 3 + const val TYPE_ORDER_SERVER_TIMESTAMP: Int = 4 + const val TYPE_ORDER_STRING: Int = 5 + const val TYPE_ORDER_BLOB: Int = 6 + const val TYPE_ORDER_REFERENCE: Int = 7 + const val TYPE_ORDER_GEOPOINT: Int = 8 + const val TYPE_ORDER_ARRAY: Int = 9 + const val TYPE_ORDER_VECTOR: Int = 10 + const val TYPE_ORDER_MAP: Int = 11 + + const val TYPE_ORDER_MAX_VALUE: Int = Int.MAX_VALUE + + /** Returns the backend's type order of the given Value type. */ + @JvmStatic + fun typeOrder(value: Value): Int { + return when (value.valueTypeCase) { + ValueTypeCase.NULL_VALUE -> TYPE_ORDER_NULL + ValueTypeCase.BOOLEAN_VALUE -> TYPE_ORDER_BOOLEAN + ValueTypeCase.INTEGER_VALUE -> TYPE_ORDER_NUMBER + ValueTypeCase.DOUBLE_VALUE -> TYPE_ORDER_NUMBER + ValueTypeCase.TIMESTAMP_VALUE -> TYPE_ORDER_TIMESTAMP + ValueTypeCase.STRING_VALUE -> TYPE_ORDER_STRING + ValueTypeCase.BYTES_VALUE -> TYPE_ORDER_BLOB + ValueTypeCase.REFERENCE_VALUE -> TYPE_ORDER_REFERENCE + ValueTypeCase.GEO_POINT_VALUE -> TYPE_ORDER_GEOPOINT + ValueTypeCase.ARRAY_VALUE -> TYPE_ORDER_ARRAY + ValueTypeCase.MAP_VALUE -> + if (ServerTimestamps.isServerTimestamp(value)) { + TYPE_ORDER_SERVER_TIMESTAMP + } else if (isMaxValue(value)) { + TYPE_ORDER_MAX_VALUE + } else if (isVectorValue(value)) { + TYPE_ORDER_VECTOR + } else { + TYPE_ORDER_MAP + } + else -> throw Assert.fail("Invalid value type: " + value.valueTypeCase) + } + } + + @JvmStatic + fun equals(left: Value?, right: Value?): Boolean { + if (left === right) { + return true + } + + if (left == null || right == null) { + return false + } + + val leftType = typeOrder(left) + val rightType = typeOrder(right) + if (leftType != rightType) { + return false + } + + return when (leftType) { + TYPE_ORDER_NUMBER -> numberEquals(left, right) + TYPE_ORDER_ARRAY -> arrayEquals(left, right) + TYPE_ORDER_VECTOR, + TYPE_ORDER_MAP -> objectEquals(left, right) + TYPE_ORDER_SERVER_TIMESTAMP -> + ServerTimestamps.getLocalWriteTime(left) == ServerTimestamps.getLocalWriteTime(right) + TYPE_ORDER_MAX_VALUE -> true + else -> left == right + } + } + + private fun numberEquals(left: Value, right: Value): Boolean { + if (left.valueTypeCase != right.valueTypeCase) { + return false + } + return when (left.valueTypeCase) { + ValueTypeCase.INTEGER_VALUE -> left.integerValue == right.integerValue + ValueTypeCase.DOUBLE_VALUE -> + doubleToLongBits(left.doubleValue) == doubleToLongBits(right.doubleValue) + else -> false + } + } + + private fun arrayEquals(left: Value, right: Value): Boolean { + val leftArray = left.arrayValue + val rightArray = right.arrayValue + + if (leftArray.valuesCount != rightArray.valuesCount) { + return false + } + + for (i in 0 until leftArray.valuesCount) { + if (!equals(leftArray.getValues(i), rightArray.getValues(i))) { + return false + } + } + + return true + } + + private fun objectEquals(left: Value, right: Value): Boolean { + val leftMap = left.mapValue + val rightMap = right.mapValue + + if (leftMap.fieldsCount != rightMap.fieldsCount) { + return false + } + + for ((key, value) in leftMap.fieldsMap) { + val otherEntry = rightMap.fieldsMap[key] + if (!equals(value, otherEntry)) { + return false + } + } + + return true + } + + /** Returns true if the Value list contains the specified element. */ + @JvmStatic + fun contains(haystack: ArrayValueOrBuilder, needle: Value?): Boolean { + for (haystackElement in haystack.valuesList) { + if (equals(haystackElement, needle)) { + return true + } + } + return false + } + + @JvmStatic + fun compare(left: Value, right: Value): Int { + val leftType = typeOrder(left) + val rightType = typeOrder(right) + + if (leftType != rightType) { + return Util.compareIntegers(leftType, rightType) + } + + return when (leftType) { + TYPE_ORDER_NULL, + TYPE_ORDER_MAX_VALUE -> 0 + TYPE_ORDER_BOOLEAN -> Util.compareBooleans(left.booleanValue, right.booleanValue) + TYPE_ORDER_NUMBER -> compareNumbers(left, right) + TYPE_ORDER_TIMESTAMP -> compareTimestamps(left.timestampValue, right.timestampValue) + TYPE_ORDER_SERVER_TIMESTAMP -> + compareTimestamps( + ServerTimestamps.getLocalWriteTime(left), + ServerTimestamps.getLocalWriteTime(right) + ) + TYPE_ORDER_STRING -> Util.compareUtf8Strings(left.stringValue, right.stringValue) + TYPE_ORDER_BLOB -> Util.compareByteStrings(left.bytesValue, right.bytesValue) + TYPE_ORDER_REFERENCE -> compareReferences(left.referenceValue, right.referenceValue) + TYPE_ORDER_GEOPOINT -> compareGeoPoints(left.geoPointValue, right.geoPointValue) + TYPE_ORDER_ARRAY -> compareArrays(left.arrayValue, right.arrayValue) + TYPE_ORDER_MAP -> compareMaps(left.mapValue, right.mapValue) + TYPE_ORDER_VECTOR -> compareVectors(left.mapValue, right.mapValue) + else -> throw Assert.fail("Invalid value type: $leftType") + } + } + + @JvmStatic + fun lowerBoundCompare( + left: Value, + leftInclusive: Boolean, + right: Value, + rightInclusive: Boolean + ): Int { + val cmp = compare(left, right) + if (cmp != 0) { + return cmp + } + + if (leftInclusive && !rightInclusive) { + return -1 + } else if (!leftInclusive && rightInclusive) { + return 1 + } + + return 0 + } + + @JvmStatic + fun upperBoundCompare( + left: Value, + leftInclusive: Boolean, + right: Value, + rightInclusive: Boolean + ): Int { + val cmp = compare(left, right) + if (cmp != 0) { + return cmp + } + + if (leftInclusive && !rightInclusive) { + return 1 + } else if (!leftInclusive && rightInclusive) { + return -1 + } + + return 0 + } + + private fun compareNumbers(left: Value, right: Value): Int { + if (left.valueTypeCase == ValueTypeCase.DOUBLE_VALUE) { + if (right.valueTypeCase == ValueTypeCase.DOUBLE_VALUE) { + return Util.compareDoubles(left.doubleValue, right.doubleValue) + } else if (right.valueTypeCase == ValueTypeCase.INTEGER_VALUE) { + return Util.compareMixed(left.doubleValue, right.integerValue) + } + } else if (left.valueTypeCase == ValueTypeCase.INTEGER_VALUE) { + if (right.valueTypeCase == ValueTypeCase.INTEGER_VALUE) { + return Util.compareLongs(left.integerValue, right.integerValue) + } else if (right.valueTypeCase == ValueTypeCase.DOUBLE_VALUE) { + return -1 * Util.compareMixed(right.doubleValue, left.integerValue) + } + } + + throw Assert.fail("Unexpected values: %s vs %s", left, right) + } + + private fun compareTimestamps(left: Timestamp, right: Timestamp): Int { + val cmp = Util.compareLongs(left.seconds, right.seconds) + if (cmp != 0) { + return cmp + } + return Util.compareIntegers(left.nanos, right.nanos) + } + + private fun compareReferences(leftPath: String, rightPath: String): Int { + val leftSegments = leftPath.split("/".toRegex()).toTypedArray() + val rightSegments = rightPath.split("/".toRegex()).toTypedArray() + + val minLength = min(leftSegments.size.toDouble(), rightSegments.size.toDouble()).toInt() + for (i in 0 until minLength) { + val cmp = leftSegments[i].compareTo(rightSegments[i]) + if (cmp != 0) { + return cmp + } + } + return Util.compareIntegers(leftSegments.size, rightSegments.size) + } + + private fun compareGeoPoints(left: LatLng, right: LatLng): Int { + val comparison = Util.compareDoubles(left.latitude, right.latitude) + if (comparison == 0) { + return Util.compareDoubles(left.longitude, right.longitude) + } + return comparison + } + + private fun compareArrays(left: ArrayValue, right: ArrayValue): Int { + val minLength = min(left.valuesCount.toDouble(), right.valuesCount.toDouble()).toInt() + for (i in 0 until minLength) { + val cmp = compare(left.getValues(i), right.getValues(i)) + if (cmp != 0) { + return cmp + } + } + return Util.compareIntegers(left.valuesCount, right.valuesCount) + } + + private fun compareMaps(left: MapValue, right: MapValue): Int { + val iterator1: Iterator> = TreeMap(left.fieldsMap).entries.iterator() + val iterator2: Iterator> = TreeMap(right.fieldsMap).entries.iterator() + while (iterator1.hasNext() && iterator2.hasNext()) { + val entry1 = iterator1.next() + val entry2 = iterator2.next() + val keyCompare = Util.compareUtf8Strings(entry1.key, entry2.key) + if (keyCompare != 0) { + return keyCompare + } + val valueCompare = compare(entry1.value, entry2.value) + if (valueCompare != 0) { + return valueCompare + } + } + + // Only equal if both iterators are exhausted. + return Util.compareBooleans(iterator1.hasNext(), iterator2.hasNext()) + } + + private fun compareVectors(left: MapValue, right: MapValue): Int { + val leftMap = left.fieldsMap + val rightMap = right.fieldsMap + + // The vector is a map, but only vector value is compared. + val leftArrayValue = leftMap[VECTOR_MAP_VECTORS_KEY]!!.arrayValue + val rightArrayValue = rightMap[VECTOR_MAP_VECTORS_KEY]!!.arrayValue + + val lengthCompare = + Util.compareIntegers(leftArrayValue.valuesCount, rightArrayValue.valuesCount) + if (lengthCompare != 0) { + return lengthCompare + } + + return compareArrays(leftArrayValue, rightArrayValue) + } + + /** Generate the canonical ID for the provided field value (as used in Target serialization). */ + @JvmStatic + fun canonicalId(value: Value): String { + val builder = StringBuilder() + canonifyValue(builder, value) + return builder.toString() + } + + private fun canonifyValue(builder: StringBuilder, value: Value) { + when (value.valueTypeCase) { + ValueTypeCase.NULL_VALUE -> builder.append("null") + ValueTypeCase.BOOLEAN_VALUE -> builder.append(value.booleanValue) + ValueTypeCase.INTEGER_VALUE -> builder.append(value.integerValue) + ValueTypeCase.DOUBLE_VALUE -> builder.append(value.doubleValue) + ValueTypeCase.TIMESTAMP_VALUE -> canonifyTimestamp(builder, value.timestampValue) + ValueTypeCase.STRING_VALUE -> builder.append(value.stringValue) + ValueTypeCase.BYTES_VALUE -> builder.append(Util.toDebugString(value.bytesValue)) + ValueTypeCase.REFERENCE_VALUE -> canonifyReference(builder, value) + ValueTypeCase.GEO_POINT_VALUE -> canonifyGeoPoint(builder, value.geoPointValue) + ValueTypeCase.ARRAY_VALUE -> canonifyArray(builder, value.arrayValue) + ValueTypeCase.MAP_VALUE -> canonifyObject(builder, value.mapValue) + else -> throw Assert.fail("Invalid value type: " + value.valueTypeCase) + } + } + + private fun canonifyTimestamp(builder: StringBuilder, timestamp: Timestamp) { + builder.append(String.format("time(%s,%s)", timestamp.seconds, timestamp.nanos)) + } + + private fun canonifyGeoPoint(builder: StringBuilder, latLng: LatLng) { + builder.append(String.format("geo(%s,%s)", latLng.latitude, latLng.longitude)) + } + + private fun canonifyReference(builder: StringBuilder, value: Value) { + Assert.hardAssert(isReferenceValue(value), "Value should be a ReferenceValue") + builder.append(DocumentKey.fromName(value.referenceValue)) + } + + private fun canonifyObject(builder: StringBuilder, mapValue: MapValue) { + // Even though MapValue are likely sorted correctly based on their insertion order (for example, + // when received from the backend), local modifications can bring elements out of order. We need + // to re-sort the elements to ensure that canonical IDs are independent of insertion order. + val keys = ArrayList(mapValue.fieldsMap.keys) + keys.sort() + + builder.append("{") + val iterator = keys.iterator() + while (iterator.hasNext()) { + val key = iterator.next() + builder.append(key).append(":") + canonifyValue(builder, mapValue.getFieldsOrThrow(key)) + if (iterator.hasNext()) { + builder.append(",") + } + } + builder.append("}") + } + + private fun canonifyArray(builder: StringBuilder, arrayValue: ArrayValue) { + builder.append("[") + if (arrayValue.valuesCount > 0) { + canonifyValue(builder, arrayValue.getValues(0)) + for (i in 1 until arrayValue.valuesCount) { + builder.append(",") + canonifyValue(builder, arrayValue.getValues(i)) + } + } + builder.append("]") + } + + /** Returns true if `value` is a INTEGER_VALUE. */ + @JvmStatic + fun isInteger(value: Value?): Boolean { + return value != null && value.valueTypeCase == ValueTypeCase.INTEGER_VALUE + } + + /** Returns true if `value` is a DOUBLE_VALUE. */ + @JvmStatic + fun isDouble(value: Value?): Boolean { + return value != null && value.valueTypeCase == ValueTypeCase.DOUBLE_VALUE + } + + /** Returns true if `value` is either a INTEGER_VALUE or a DOUBLE_VALUE. */ + @JvmStatic + fun isNumber(value: Value?): Boolean { + return isInteger(value) || isDouble(value) + } + + /** Returns true if `value` is an ARRAY_VALUE. */ + @JvmStatic + fun isArray(value: Value?): Boolean { + return value != null && value.valueTypeCase == ValueTypeCase.ARRAY_VALUE + } + + @JvmStatic + fun isReferenceValue(value: Value?): Boolean { + return value != null && value.valueTypeCase == ValueTypeCase.REFERENCE_VALUE + } + + @JvmStatic + fun isNullValue(value: Value?): Boolean { + return value != null && value.valueTypeCase == ValueTypeCase.NULL_VALUE + } + + @JvmStatic + fun isNanValue(value: Value?): Boolean { + return value != null && java.lang.Double.isNaN(value.doubleValue) + } + + @JvmStatic + fun isMapValue(value: Value?): Boolean { + return value != null && value.valueTypeCase == ValueTypeCase.MAP_VALUE + } + + @JvmStatic + fun refValue(databaseId: DatabaseId, key: DocumentKey): Value { + val value = + Value.newBuilder() + .setReferenceValue( + String.format( + "projects/%s/databases/%s/documents/%s", + databaseId.projectId, + databaseId.databaseId, + key.toString() + ) + ) + .build() + return value + } + + private val MIN_BOOLEAN: Value = Value.newBuilder().setBooleanValue(false).build() + private val MIN_NUMBER: Value = Value.newBuilder().setDoubleValue(Double.NaN).build() + private val MIN_TIMESTAMP: Value = + Value.newBuilder().setTimestampValue(Timestamp.newBuilder().setSeconds(Long.MIN_VALUE)).build() + private val MIN_STRING: Value = Value.newBuilder().setStringValue("").build() + private val MIN_BYTES: Value = Value.newBuilder().setBytesValue(ByteString.EMPTY).build() + private val MIN_REFERENCE: Value = refValue(DatabaseId.EMPTY, DocumentKey.empty()) + private val MIN_GEO_POINT: Value = + Value.newBuilder() + .setGeoPointValue(LatLng.newBuilder().setLatitude(-90.0).setLongitude(-180.0)) + .build() + private val MIN_ARRAY: Value = + Value.newBuilder().setArrayValue(ArrayValue.getDefaultInstance()).build() + private val MIN_MAP: Value = Value.newBuilder().setMapValue(MapValue.getDefaultInstance()).build() + + /** Returns the lowest value for the given value type (inclusive). */ + @JvmStatic + fun getLowerBound(value: Value): Value { + return when (value.valueTypeCase) { + ValueTypeCase.NULL_VALUE -> NULL_VALUE + ValueTypeCase.BOOLEAN_VALUE -> MIN_BOOLEAN + ValueTypeCase.INTEGER_VALUE, + ValueTypeCase.DOUBLE_VALUE -> MIN_NUMBER + ValueTypeCase.TIMESTAMP_VALUE -> MIN_TIMESTAMP + ValueTypeCase.STRING_VALUE -> MIN_STRING + ValueTypeCase.BYTES_VALUE -> MIN_BYTES + ValueTypeCase.REFERENCE_VALUE -> MIN_REFERENCE + ValueTypeCase.GEO_POINT_VALUE -> MIN_GEO_POINT + ValueTypeCase.ARRAY_VALUE -> MIN_ARRAY + // VectorValue sorts after ArrayValue and before an empty MapValue + ValueTypeCase.MAP_VALUE -> if (isVectorValue(value)) MIN_VECTOR_VALUE else MIN_MAP + else -> throw IllegalArgumentException("Unknown value type: " + value.valueTypeCase) + } + } + + /** Returns the largest value for the given value type (exclusive). */ + @JvmStatic + fun getUpperBound(value: Value): Value { + return when (value.valueTypeCase) { + ValueTypeCase.NULL_VALUE -> MIN_BOOLEAN + ValueTypeCase.BOOLEAN_VALUE -> MIN_NUMBER + ValueTypeCase.INTEGER_VALUE, + ValueTypeCase.DOUBLE_VALUE -> MIN_TIMESTAMP + ValueTypeCase.TIMESTAMP_VALUE -> MIN_STRING + ValueTypeCase.STRING_VALUE -> MIN_BYTES + ValueTypeCase.BYTES_VALUE -> MIN_REFERENCE + ValueTypeCase.REFERENCE_VALUE -> MIN_GEO_POINT + ValueTypeCase.GEO_POINT_VALUE -> MIN_ARRAY + ValueTypeCase.ARRAY_VALUE -> MIN_VECTOR_VALUE + // VectorValue sorts after ArrayValue and before an empty MapValue + ValueTypeCase.MAP_VALUE -> if (isVectorValue(value)) MIN_MAP else MAX_VALUE + else -> throw IllegalArgumentException("Unknown value type: " + value.valueTypeCase) + } + } + + /** Returns true if the Value represents the canonical [.MAX_VALUE] . */ + @JvmStatic + fun isMaxValue(value: Value): Boolean { + return MAX_VALUE_TYPE == value.mapValue.fieldsMap[TYPE_KEY] + } + + /** Returns true if the Value represents a VectorValue . */ + @JvmStatic + fun isVectorValue(value: Value): Boolean { + return VECTOR_VALUE_TYPE == value.mapValue.fieldsMap[TYPE_KEY] + } + + @JvmStatic + fun encodeValue(value: Long): Value { + return Value.newBuilder().setIntegerValue(value).build() + } + + @JvmStatic + fun encodeValue(value: Int): Value { + return Value.newBuilder().setIntegerValue(value.toLong()).build() + } + + @JvmStatic + fun encodeValue(value: Double): Value { + return Value.newBuilder().setDoubleValue(value).build() + } + + @JvmStatic + fun encodeValue(value: Float): Value { + return Value.newBuilder().setDoubleValue(value.toDouble()).build() + } + + @JvmStatic + fun encodeValue(value: Number): Value { + return when (value) { + is Long -> encodeValue(value) + is Int -> encodeValue(value) + is Double -> encodeValue(value) + is Float -> encodeValue(value) + else -> throw IllegalArgumentException("Unexpected number type: $value") + } + } + + @JvmStatic + fun encodeValue(value: String): Value { + return Value.newBuilder().setStringValue(value).build() + } + + @JvmStatic + fun encodeValue(date: Date): Value { + return encodeValue(com.google.firebase.Timestamp((date))) + } + + @JvmStatic + fun encodeValue(timestamp: com.google.firebase.Timestamp): Value { + // Firestore backend truncates precision down to microseconds. To ensure offline mode works + // the same with regards to truncation, perform the truncation immediately without waiting for + // the backend to do that. + val truncatedNanoseconds: Int = timestamp.nanoseconds / 1000 * 1000 + + return Value.newBuilder() + .setTimestampValue( + com.google.protobuf.Timestamp.newBuilder() + .setSeconds(timestamp.seconds) + .setNanos(truncatedNanoseconds) + ) + .build() + } + + @JvmStatic + fun encodeValue(value: Boolean): Value { + return Value.newBuilder().setBooleanValue(value).build() + } + + @JvmStatic + fun encodeValue(geoPoint: GeoPoint): Value { + return Value.newBuilder() + .setGeoPointValue( + LatLng.newBuilder().setLatitude(geoPoint.latitude).setLongitude(geoPoint.longitude) + ) + .build() + } + + @JvmStatic + fun encodeValue(value: Blob): Value { + return Value.newBuilder().setBytesValue(value.toByteString()).build() + } + + @JvmStatic + fun encodeValue(docRef: DocumentReference): Value { + return Value.newBuilder().setReferenceValue(docRef.fullPath).build() + } + + @JvmStatic + fun encodeValue(vector: VectorValue): Value { + return encodeVectorValue(vector.toArray()) + } + + @JvmStatic + fun encodeVectorValue(vector: DoubleArray): Value { + val listBuilder = ArrayValue.newBuilder() + for (value in vector) { + listBuilder.addValues(encodeValue(value)) + } + return Value.newBuilder() + .setMapValue( + MapValue.newBuilder() + .putFields(TYPE_KEY, VECTOR_VALUE_TYPE) + .putFields(VECTOR_MAP_VECTORS_KEY, Value.newBuilder().setArrayValue(listBuilder).build()) + ) + .build() + } + + @JvmStatic + fun encodeValue(map: Map): Value { + return Value.newBuilder().setMapValue(MapValue.newBuilder().putAllFields(map)).build() + } + + @JvmStatic + fun encodeAnyValue(value: Any?): Value { + return when (value) { + null -> NULL_VALUE + is String -> encodeValue(value) + is Number -> encodeValue(value) + is Date -> encodeValue(value) + is com.google.firebase.Timestamp -> encodeValue(value) + is Boolean -> encodeValue(value) + is GeoPoint -> encodeValue(value) + is Blob -> encodeValue(value) + is DocumentReference -> encodeValue(value) + is VectorValue -> encodeValue(value) + else -> throw IllegalArgumentException("Unexpected type: $value") + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/Constant.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/Constant.kt new file mode 100644 index 00000000000..bc9a39a50d4 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/Constant.kt @@ -0,0 +1,112 @@ +// Copyright 2025 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.pipeline + +import com.google.firebase.Timestamp +import com.google.firebase.firestore.Blob +import com.google.firebase.firestore.DocumentReference +import com.google.firebase.firestore.GeoPoint +import com.google.firebase.firestore.VectorValue +import com.google.firebase.firestore.model.Values +import com.google.firebase.firestore.model.Values.encodeValue +import com.google.firestore.v1.Value +import java.util.Date + +class Constant internal constructor(val value: Value) : Expr() { + + companion object { + internal val NULL = Constant(Values.NULL_VALUE) + + fun of(value: Any): Constant { + return when (value) { + is String -> of(value) + is Number -> of(value) + is Date -> of(value) + is Timestamp -> of(value) + is Boolean -> of(value) + is GeoPoint -> of(value) + is Blob -> of(value) + is DocumentReference -> of(value) + is Value -> of(value) + is VectorValue -> of(value) + else -> throw IllegalArgumentException("Unknown type: $value") + } + } + + @JvmStatic + fun of(value: String): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun of(value: Number): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun of(value: Date): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun of(value: Timestamp): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun of(value: Boolean): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun of(value: GeoPoint): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun of(value: Blob): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun of(value: DocumentReference): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun of(value: VectorValue): Constant { + return Constant(encodeValue(value)) + } + + @JvmStatic + fun nullValue(): Constant { + return NULL + } + + @JvmStatic + fun vector(value: DoubleArray): Constant { + return Constant(Values.encodeVectorValue(value)) + } + + @JvmStatic + fun vector(value: VectorValue): Constant { + return Constant(encodeValue(value)) + } + } + + override fun toProto(): Value { + return value + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/accumulators.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/accumulators.kt new file mode 100644 index 00000000000..7ea8668b1b2 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/accumulators.kt @@ -0,0 +1,64 @@ +// Copyright 2025 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.pipeline + +import com.google.firestore.v1.Value + +class AccumulatorWithAlias +internal constructor(internal val alias: String, internal val accumulator: Accumulator) + +class Accumulator +private constructor(private val name: String, private val params: Array) { + private constructor(name: String) : this(name, emptyArray()) + private constructor(name: String, expr: Expr) : this(name, arrayOf(expr)) + private constructor(name: String, fieldName: String) : this(name, Field.of(fieldName)) + + companion object { + @JvmStatic fun countAll() = Accumulator("count") + + @JvmStatic fun count(fieldName: String) = Accumulator("count", fieldName) + + @JvmStatic fun count(expr: Expr) = Accumulator("count", expr) + + @JvmStatic fun countIf(condition: BooleanExpr) = Accumulator("countIf", condition) + + @JvmStatic fun sum(fieldName: String) = Accumulator("sum", fieldName) + + @JvmStatic fun sum(expr: Expr) = Accumulator("sum", expr) + + @JvmStatic fun avg(fieldName: String) = Accumulator("avg", fieldName) + + @JvmStatic fun avg(expr: Expr) = Accumulator("avg", expr) + + @JvmStatic fun min(fieldName: String) = Accumulator("min", fieldName) + + @JvmStatic fun min(expr: Expr) = Accumulator("min", expr) + + @JvmStatic fun max(fieldName: String) = Accumulator("max", fieldName) + + @JvmStatic fun max(expr: Expr) = Accumulator("max", expr) + } + + fun `as`(alias: String) = AccumulatorWithAlias(alias, this) + + fun toProto(): Value { + val builder = com.google.firestore.v1.Function.newBuilder() + builder.setName(name) + for (param in params) { + builder.addArgs(param.toProto()) + } + return Value.newBuilder().setFunctionValue(builder).build() + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expression.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expression.kt new file mode 100644 index 00000000000..afc796742b0 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expression.kt @@ -0,0 +1,833 @@ +// Copyright 2025 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.pipeline + +import com.google.firebase.firestore.FieldPath +import com.google.firebase.firestore.VectorValue +import com.google.firebase.firestore.model.DocumentKey +import com.google.firebase.firestore.model.FieldPath as ModelFieldPath +import com.google.firebase.firestore.model.Values.encodeValue +import com.google.firestore.v1.ArrayValue +import com.google.firestore.v1.MapValue +import com.google.firestore.v1.Value + +abstract class Expr protected constructor() { + internal companion object { + internal fun toExprOrConstant(value: Any): Expr { + return when (value) { + is Expr -> value + else -> Constant.of(value) + } + } + + internal fun toArrayOfExprOrConstant(others: Iterable): Array = + others.map(::toExprOrConstant).toTypedArray() + + internal fun toArrayOfExprOrConstant(others: Array): Array = + others.map(::toExprOrConstant).toTypedArray() + } + + /** + * Assigns an alias to this expression. + * + *

Aliases are useful for renaming fields in the output of a stage or for giving meaningful + * names to calculated values. + * + *

Example: + * + *

{@code // Calculate the total price and assign it the alias "totalPrice" and add it to the
+   * output. firestore.pipeline().collection("items")
+   * .addFields(Field.of("price").multiply(Field.of("quantity")).as("totalPrice")); }
+ * + * @param alias The alias to assign to this expression. + * @return A new {@code Selectable} (typically an {@link ExprWithAlias}) that wraps this + * ``` + * expression and associates it with the provided alias. + * ``` + */ + open fun `as`(alias: String) = ExprWithAlias(alias, this) + + /** + * Creates an expression that this expression to another expression. + * + *

Example: + * + *

{@code // Add the value of the 'quantity' field and the 'reserve' field.
+   * Field.of("quantity").add(Field.of("reserve")); }
+ * + * @param other The expression to add to this expression. + * @return A new {@code Expr} representing the addition operation. + */ + fun add(other: Expr) = Function.add(this, other) + + /** + * Creates an expression that this expression to another expression. + * + *

Example: + * + *

{@code // Add the value of the 'quantity' field and the 'reserve' field.
+   * Field.of("quantity").add(Field.of("reserve")); }
+ * + * @param other The constant value to add to this expression. + * @return A new {@code Expr} representing the addition operation. + */ + fun add(other: Any) = Function.add(this, other) + + fun subtract(other: Expr) = Function.subtract(this, other) + + fun subtract(other: Any) = Function.subtract(this, other) + + fun multiply(other: Expr) = Function.multiply(this, other) + + fun multiply(other: Any) = Function.multiply(this, other) + + fun divide(other: Expr) = Function.divide(this, other) + + fun divide(other: Any) = Function.divide(this, other) + + fun mod(other: Expr) = Function.mod(this, other) + + fun mod(other: Any) = Function.mod(this, other) + + fun inAny(values: List) = Function.inAny(this, values) + + fun notInAny(values: List) = Function.notInAny(this, values) + + fun isNan() = Function.isNan(this) + + fun isNull() = Function.isNull(this) + + fun replaceFirst(find: Expr, replace: Expr) = Function.replaceFirst(this, find, replace) + + fun replaceFirst(find: String, replace: String) = Function.replaceFirst(this, find, replace) + + fun replaceAll(find: Expr, replace: Expr) = Function.replaceAll(this, find, replace) + + fun replaceAll(find: String, replace: String) = Function.replaceAll(this, find, replace) + + fun charLength() = Function.charLength(this) + + fun byteLength() = Function.byteLength(this) + + fun like(pattern: Expr) = Function.like(this, pattern) + + fun like(pattern: String) = Function.like(this, pattern) + + fun regexContains(pattern: Expr) = Function.regexContains(this, pattern) + + fun regexContains(pattern: String) = Function.regexContains(this, pattern) + + fun regexMatch(pattern: Expr) = Function.regexMatch(this, pattern) + + fun regexMatch(pattern: String) = Function.regexMatch(this, pattern) + + fun logicalMax(other: Expr) = Function.logicalMax(this, other) + + fun logicalMax(other: Any) = Function.logicalMax(this, other) + + fun logicalMin(other: Expr) = Function.logicalMin(this, other) + + fun logicalMin(other: Any) = Function.logicalMin(this, other) + + fun reverse() = Function.reverse(this) + + fun strContains(substring: Expr) = Function.strContains(this, substring) + + fun strContains(substring: String) = Function.strContains(this, substring) + + fun startsWith(prefix: Expr) = Function.startsWith(this, prefix) + + fun startsWith(prefix: String) = Function.startsWith(this, prefix) + + fun endsWith(suffix: Expr) = Function.endsWith(this, suffix) + + fun endsWith(suffix: String) = Function.endsWith(this, suffix) + + fun toLower() = Function.toLower(this) + + fun toUpper() = Function.toUpper(this) + + fun trim() = Function.trim(this) + + fun strConcat(vararg expr: Expr) = Function.strConcat(this, *expr) + + fun strConcat(vararg string: String) = Function.strConcat(this, *string) + + fun strConcat(vararg string: Any) = Function.strConcat(this, *string) + + fun mapGet(key: Expr) = Function.mapGet(this, key) + + fun mapGet(key: String) = Function.mapGet(this, key) + + fun cosineDistance(vector: Expr) = Function.cosineDistance(this, vector) + + fun cosineDistance(vector: DoubleArray) = Function.cosineDistance(this, vector) + + fun cosineDistance(vector: VectorValue) = Function.cosineDistance(this, vector) + + fun dotProduct(vector: Expr) = Function.dotProduct(this, vector) + + fun dotProduct(vector: DoubleArray) = Function.dotProduct(this, vector) + + fun dotProduct(vector: VectorValue) = Function.dotProduct(this, vector) + + fun euclideanDistance(vector: Expr) = Function.euclideanDistance(this, vector) + + fun euclideanDistance(vector: DoubleArray) = Function.euclideanDistance(this, vector) + + fun euclideanDistance(vector: VectorValue) = Function.euclideanDistance(this, vector) + + fun vectorLength() = Function.vectorLength(this) + + fun unixMicrosToTimestamp() = Function.unixMicrosToTimestamp(this) + + fun timestampToUnixMicros() = Function.timestampToUnixMicros(this) + + fun unixMillisToTimestamp() = Function.unixMillisToTimestamp(this) + + fun timestampToUnixMillis() = Function.timestampToUnixMillis(this) + + fun unixSecondsToTimestamp() = Function.unixSecondsToTimestamp(this) + + fun timestampToUnixSeconds() = Function.timestampToUnixSeconds(this) + + fun timestampAdd(unit: Expr, amount: Expr) = Function.timestampAdd(this, unit, amount) + + fun timestampAdd(unit: String, amount: Double) = Function.timestampAdd(this, unit, amount) + + fun timestampSub(unit: Expr, amount: Expr) = Function.timestampSub(this, unit, amount) + + fun timestampSub(unit: String, amount: Double) = Function.timestampSub(this, unit, amount) + + fun arrayConcat(vararg arrays: Expr) = Function.arrayConcat(this, *arrays) + + fun arrayConcat(arrays: List) = Function.arrayConcat(this, arrays) + + fun arrayReverse() = Function.arrayReverse(this) + + fun arrayContains(value: Expr) = Function.arrayContains(this, value) + + fun arrayContains(value: Any) = Function.arrayContains(this, value) + + fun arrayContainsAll(values: List) = Function.arrayContainsAll(this, values) + + fun arrayContainsAny(values: List) = Function.arrayContainsAny(this, values) + + fun arrayLength() = Function.arrayLength(this) + + fun sum() = Accumulator.sum(this) + + fun avg() = Accumulator.avg(this) + + fun min() = Accumulator.min(this) + + fun max() = Accumulator.max(this) + + fun ascending() = Ordering.ascending(this) + + fun descending() = Ordering.descending(this) + + fun eq(other: Expr) = Function.eq(this, other) + + fun eq(other: Any) = Function.eq(this, other) + + fun neq(other: Expr) = Function.neq(this, other) + + fun neq(other: Any) = Function.neq(this, other) + + fun gt(other: Expr) = Function.gt(this, other) + + fun gt(other: Any) = Function.gt(this, other) + + fun gte(other: Expr) = Function.gte(this, other) + + fun gte(other: Any) = Function.gte(this, other) + + fun lt(other: Expr) = Function.lt(this, other) + + fun lt(other: Any) = Function.lt(this, other) + + fun lte(other: Expr) = Function.lte(this, other) + + fun lte(other: Any) = Function.lte(this, other) + + internal abstract fun toProto(): Value +} + +abstract class Selectable : Expr() { + internal abstract fun getAlias(): String + + internal companion object { + fun toSelectable(o: Any): Selectable { + return when (o) { + is Selectable -> o + is String -> Field.of(o) + is FieldPath -> Field.of(o) + else -> throw IllegalArgumentException("Unknown Selectable type: $o") + } + } + } +} + +open class ExprWithAlias internal constructor(private val alias: String, private val expr: Expr) : + Selectable() { + override fun getAlias() = alias + override fun toProto(): Value = expr.toProto() +} + +class Field private constructor(private val fieldPath: ModelFieldPath) : + Selectable() { + companion object { + + @JvmStatic + fun of(name: String): Field { + if (name == DocumentKey.KEY_FIELD_NAME) { + return Field(ModelFieldPath.KEY_PATH) + } + return Field(FieldPath.fromDotSeparatedPath(name).internalPath) + } + + @JvmStatic + fun of(fieldPath: FieldPath): Field { + if (fieldPath == FieldPath.documentId()) { + return Field(FieldPath.documentId().internalPath) + } + return Field(fieldPath.internalPath) + } + } + + override fun getAlias(): String = fieldPath.canonicalString() + + override fun toProto() = + Value.newBuilder().setFieldReferenceValue(fieldPath.canonicalString()).build() +} + +class ListOfExprs(private val expressions: Array) : Expr() { + override fun toProto(): Value { + val builder = ArrayValue.newBuilder() + for (expr in expressions) { + builder.addValues(expr.toProto()) + } + return Value.newBuilder().setArrayValue(builder).build() + } +} + +open class Function +protected constructor(private val name: String, private val params: Array) : Expr() { + private constructor(name: String, param: Expr, vararg params: Any) : this(name, arrayOf(param, *toArrayOfExprOrConstant(params))) + private constructor(name: String, fieldName: String, vararg params: Any) : this(name, arrayOf(Field.of(fieldName), *toArrayOfExprOrConstant(params))) + companion object { + @JvmStatic + fun and(condition: BooleanExpr, vararg conditions: BooleanExpr) = BooleanExpr("and", condition, *conditions) + + @JvmStatic + fun or(condition: BooleanExpr, vararg conditions: BooleanExpr) = BooleanExpr("or", condition, *conditions) + + @JvmStatic + fun xor(condition: BooleanExpr, vararg conditions: BooleanExpr) = BooleanExpr("xor", condition, *conditions) + + @JvmStatic fun not(condition: BooleanExpr) = BooleanExpr("not", condition) + + @JvmStatic fun add(left: Expr, right: Expr) = Function("add", left, right) + + @JvmStatic fun add(left: Expr, right: Any) = Function("add", left, right) + + @JvmStatic fun add(fieldName: String, other: Expr) = Function("add", fieldName, other) + + @JvmStatic fun add(fieldName: String, other: Any) = Function("add", fieldName, other) + + @JvmStatic fun subtract(left: Expr, right: Expr) = Function("subtract", left, right) + + @JvmStatic fun subtract(left: Expr, right: Any) = Function("subtract", left, right) + + @JvmStatic fun subtract(fieldName: String, other: Expr) = Function("subtract", fieldName, other) + + @JvmStatic fun subtract(fieldName: String, other: Any) = Function("subtract", fieldName, other) + + @JvmStatic fun multiply(left: Expr, right: Expr) = Function("multiply", left, right) + + @JvmStatic fun multiply(left: Expr, right: Any) = Function("multiply", left, right) + + @JvmStatic fun multiply(fieldName: String, other: Expr) = Function("multiply", fieldName, other) + + @JvmStatic fun multiply(fieldName: String, other: Any) = Function("multiply", fieldName, other) + + @JvmStatic fun divide(left: Expr, right: Expr) = Function("divide", left, right) + + @JvmStatic fun divide(left: Expr, right: Any) = Function("divide", left, right) + + @JvmStatic fun divide(fieldName: String, other: Expr) = Function("divide", fieldName, other) + + @JvmStatic fun divide(fieldName: String, other: Any) = Function("divide", fieldName, other) + + @JvmStatic fun mod(left: Expr, right: Expr) = Function("mod", left, right) + + @JvmStatic fun mod(left: Expr, right: Any) = Function("mod", left, right) + + @JvmStatic fun mod(fieldName: String, other: Expr) = Function("mod", fieldName, other) + + @JvmStatic fun mod(fieldName: String, other: Any) = Function("mod", fieldName, other) + + @JvmStatic fun inAny(array: Expr, values: List) = BooleanExpr("in", array, ListOfExprs(toArrayOfExprOrConstant(values))) + + @JvmStatic fun inAny(fieldName: String, values: List) = BooleanExpr("in", fieldName, ListOfExprs(toArrayOfExprOrConstant(values))) + + @JvmStatic fun notInAny(array: Expr, values: List) = not(inAny(array, values)) + + @JvmStatic fun notInAny(fieldName: String, values: List) = not(inAny(fieldName, values)) + + @JvmStatic fun isNan(expr: Expr) = BooleanExpr("is_nan", expr) + + @JvmStatic fun isNan(fieldName: String) = BooleanExpr("is_nan", fieldName) + + @JvmStatic fun isNull(expr: Expr) = BooleanExpr("is_null", expr) + + @JvmStatic fun isNull(fieldName: String) = BooleanExpr("is_null", fieldName) + + @JvmStatic + fun replaceFirst(value: Expr, find: Expr, replace: Expr) = Function("replace_first", value, find, replace) + + @JvmStatic + fun replaceFirst(value: Expr, find: String, replace: String) = + Function("replace_first", value, find, replace) + + @JvmStatic + fun replaceFirst(fieldName: String, find: String, replace: String) = + Function("replace_first", fieldName, find, replace) + + @JvmStatic + fun replaceAll(value: Expr, find: Expr, replace: Expr) = Function("replace_all", value, find, replace) + + @JvmStatic + fun replaceAll(value: Expr, find: String, replace: String) = Function("replace_all", value, find, replace) + + @JvmStatic + fun replaceAll(fieldName: String, find: String, replace: String) = + Function("replace_all", fieldName, find, replace) + + @JvmStatic fun charLength(value: Expr) = Function("char_length", value) + + @JvmStatic fun charLength(fieldName: String) = Function("char_length", fieldName) + + @JvmStatic fun byteLength(value: Expr) = Function("byte_length", value) + + @JvmStatic fun byteLength(fieldName: String) = Function("byte_length", fieldName) + + @JvmStatic fun like(expr: Expr, pattern: Expr) = BooleanExpr("like", expr, pattern) + + @JvmStatic fun like(expr: Expr, pattern: String) = BooleanExpr("like", expr, pattern) + + @JvmStatic fun like(fieldName: String, pattern: Expr) = BooleanExpr("like", fieldName, pattern) + + @JvmStatic fun like(fieldName: String, pattern: String) = BooleanExpr("like", fieldName, pattern) + + @JvmStatic fun regexContains(expr: Expr, pattern: Expr) = BooleanExpr("regex_contains", expr, pattern) + + @JvmStatic fun regexContains(expr: Expr, pattern: String) = BooleanExpr("regex_contains", expr, pattern) + + @JvmStatic + fun regexContains(fieldName: String, pattern: Expr) = BooleanExpr("regex_contains", fieldName, pattern) + + @JvmStatic + fun regexContains(fieldName: String, pattern: String) = BooleanExpr("regex_contains", fieldName, pattern) + + @JvmStatic fun regexMatch(expr: Expr, pattern: Expr) = BooleanExpr("regex_match", expr, pattern) + + @JvmStatic fun regexMatch(expr: Expr, pattern: String) = BooleanExpr("regex_match", expr, pattern) + + @JvmStatic fun regexMatch(fieldName: String, pattern: Expr) = BooleanExpr("regex_match", fieldName, pattern) + + @JvmStatic fun regexMatch(fieldName: String, pattern: String) = BooleanExpr("regex_match", fieldName, pattern) + + @JvmStatic fun logicalMax(left: Expr, right: Expr) = Function("logical_max", left, right) + + @JvmStatic fun logicalMax(left: Expr, right: Any) = Function("logical_max", left, right) + + @JvmStatic fun logicalMax(fieldName: String, other: Expr) = Function("logical_max", fieldName, other) + + @JvmStatic fun logicalMax(fieldName: String, other: Any) = Function("logical_max", fieldName, other) + + @JvmStatic fun logicalMin(left: Expr, right: Expr) = Function("logical_min", left, right) + + @JvmStatic fun logicalMin(left: Expr, right: Any) = Function("logical_min", left, right) + + @JvmStatic fun logicalMin(fieldName: String, other: Expr) = Function("logical_min", fieldName, other) + + @JvmStatic fun logicalMin(fieldName: String, other: Any) = Function("logical_min", fieldName, other) + + @JvmStatic fun reverse(expr: Expr) = Function("reverse", expr) + + @JvmStatic fun reverse(fieldName: String) = Function("reverse", fieldName) + + @JvmStatic fun strContains(expr: Expr, substring: Expr) = BooleanExpr("str_contains", expr, substring) + + @JvmStatic fun strContains(expr: Expr, substring: String) = BooleanExpr("str_contains", expr, substring) + + @JvmStatic + fun strContains(fieldName: String, substring: Expr) = BooleanExpr("str_contains", fieldName, substring) + + @JvmStatic + fun strContains(fieldName: String, substring: String) = BooleanExpr("str_contains", fieldName, substring) + + @JvmStatic fun startsWith(expr: Expr, prefix: Expr) = BooleanExpr("starts_with", expr, prefix) + + @JvmStatic fun startsWith(expr: Expr, prefix: String) = BooleanExpr("starts_with", expr, prefix) + + @JvmStatic fun startsWith(fieldName: String, prefix: Expr) = BooleanExpr("starts_with", fieldName, prefix) + + @JvmStatic fun startsWith(fieldName: String, prefix: String) = BooleanExpr("starts_with", fieldName, prefix) + + @JvmStatic fun endsWith(expr: Expr, suffix: Expr) = BooleanExpr("ends_with", expr, suffix) + + @JvmStatic fun endsWith(expr: Expr, suffix: String) = BooleanExpr("ends_with", expr, suffix) + + @JvmStatic fun endsWith(fieldName: String, suffix: Expr) = BooleanExpr("ends_with", fieldName, suffix) + + @JvmStatic fun endsWith(fieldName: String, suffix: String) = BooleanExpr("ends_with", fieldName, suffix) + + @JvmStatic fun toLower(expr: Expr) = Function("to_lower", expr) + + @JvmStatic + fun toLower( + fieldName: String, + ) = Function("to_lower", fieldName) + + @JvmStatic fun toUpper(expr: Expr) = Function("to_upper", expr) + + @JvmStatic + fun toUpper( + fieldName: String, + ) = Function("to_upper", fieldName) + + @JvmStatic fun trim(expr: Expr) = Function("trim", expr) + + @JvmStatic fun trim(fieldName: String) = Function("trim", fieldName) + + @JvmStatic fun strConcat(first: Expr, vararg rest: Expr) = Function("str_concat", first, *rest) + + @JvmStatic fun strConcat(first: Expr, vararg rest: Any) = Function("str_concat", first, *rest) + + @JvmStatic fun strConcat(fieldName: String, vararg rest: Expr) = Function("str_concat", fieldName, *rest) + + @JvmStatic fun strConcat(fieldName: String, vararg rest: Any) = Function("str_concat", fieldName, *rest) + + @JvmStatic fun mapGet(map: Expr, key: Expr) = Function("map_get", map, key) + + @JvmStatic fun mapGet(map: Expr, key: String) = Function("map_get", map, key) + + @JvmStatic fun mapGet(fieldName: String, key: Expr) = Function("map_get", fieldName, key) + + @JvmStatic fun mapGet(fieldName: String, key: String) = Function("map_get", fieldName, key) + + @JvmStatic fun cosineDistance(vector1: Expr, vector2: Expr) = Function("cosine_distance", vector1, vector2) + + @JvmStatic + fun cosineDistance(vector1: Expr, vector2: DoubleArray) = Function("cosine_distance", vector1, Constant.vector(vector2)) + + @JvmStatic + fun cosineDistance(vector1: Expr, vector2: VectorValue) = Function("cosine_distance", vector1, vector2) + + @JvmStatic + fun cosineDistance(fieldName: String, vector: Expr) = Function("cosine_distance", fieldName, vector) + + @JvmStatic + fun cosineDistance(fieldName: String, vector: DoubleArray) = Function("cosine_distance", fieldName, Constant.vector(vector)) + + @JvmStatic + fun cosineDistance(fieldName: String, vector: VectorValue) = Function("cosine_distance", fieldName, vector) + + @JvmStatic fun dotProduct(vector1: Expr, vector2: Expr) = Function("dot_product", vector1, vector2) + + @JvmStatic fun dotProduct(vector1: Expr, vector2: DoubleArray) = Function("dot_product", vector1, Constant.vector(vector2)) + + @JvmStatic fun dotProduct(vector1: Expr, vector2: VectorValue) = Function("dot_product", vector1, vector2) + + @JvmStatic fun dotProduct(fieldName: String, vector: Expr) = Function("dot_product", fieldName, vector) + + @JvmStatic + fun dotProduct(fieldName: String, vector: DoubleArray) = Function("dot_product", fieldName, Constant.vector(vector)) + + @JvmStatic + fun dotProduct(fieldName: String, vector: VectorValue) = Function("dot_product", fieldName, vector) + + @JvmStatic + fun euclideanDistance(vector1: Expr, vector2: Expr) = Function("euclidean_distance", vector1, vector2) + + @JvmStatic + fun euclideanDistance(vector1: Expr, vector2: DoubleArray) = Function("euclidean_distance", vector1, Constant.vector(vector2)) + + @JvmStatic + fun euclideanDistance(vector1: Expr, vector2: VectorValue) = Function("euclidean_distance", vector1, vector2) + + @JvmStatic + fun euclideanDistance(fieldName: String, vector: Expr) = Function("euclidean_distance", fieldName, vector) + + @JvmStatic + fun euclideanDistance(fieldName: String, vector: DoubleArray) = + Function("euclidean_distance", fieldName, Constant.vector(vector)) + + @JvmStatic + fun euclideanDistance(fieldName: String, vector: VectorValue) = + Function("euclidean_distance", fieldName, vector) + + @JvmStatic fun vectorLength(vector: Expr) = Function("vector_length", vector) + + @JvmStatic fun vectorLength(fieldName: String) = Function("vector_length", fieldName) + + @JvmStatic fun unixMicrosToTimestamp(input: Expr) = Function("unix_micros_to_timestamp", input) + + @JvmStatic fun unixMicrosToTimestamp(fieldName: String) = Function("unix_micros_to_timestamp", fieldName) + + @JvmStatic fun timestampToUnixMicros(input: Expr) = Function("timestamp_to_unix_micros", input) + + @JvmStatic fun timestampToUnixMicros(fieldName: String) = Function("timestamp_to_unix_micros", fieldName) + + @JvmStatic fun unixMillisToTimestamp(input: Expr) = Function("unix_millis_to_timestamp", input) + + @JvmStatic fun unixMillisToTimestamp(fieldName: String) = Function("unix_millis_to_timestamp", fieldName) + + @JvmStatic fun timestampToUnixMillis(input: Expr) = Function("timestamp_to_unix_millis", input) + + @JvmStatic fun timestampToUnixMillis(fieldName: String) = Function("timestamp_to_unix_millis", fieldName) + + @JvmStatic fun unixSecondsToTimestamp(input: Expr) = Function("unix_seconds_to_timestamp", input) + + @JvmStatic fun unixSecondsToTimestamp(fieldName: String) = Function("unix_seconds_to_timestamp", fieldName) + + @JvmStatic fun timestampToUnixSeconds(input: Expr) = Function("timestamp_to_unix_seconds", input) + + @JvmStatic fun timestampToUnixSeconds(fieldName: String) = Function("timestamp_to_unix_seconds", fieldName) + + @JvmStatic + fun timestampAdd(timestamp: Expr, unit: Expr, amount: Expr) = + Function("timestamp_add", timestamp, unit, amount) + + @JvmStatic + fun timestampAdd(timestamp: Expr, unit: String, amount: Double) = + Function("timestamp_add", timestamp, unit, amount) + + @JvmStatic + fun timestampAdd(fieldName: String, unit: Expr, amount: Expr) = + Function("timestamp_add", fieldName, unit, amount) + + @JvmStatic + fun timestampAdd(fieldName: String, unit: String, amount: Double) = + Function("timestamp_add", fieldName, unit, amount) + + @JvmStatic + fun timestampSub(timestamp: Expr, unit: Expr, amount: Expr) = + Function("timestamp_sub", timestamp, unit, amount) + + @JvmStatic + fun timestampSub(timestamp: Expr, unit: String, amount: Double) = + Function("timestamp_sub", timestamp, unit, amount) + + @JvmStatic + fun timestampSub(fieldName: String, unit: Expr, amount: Expr) = + Function("timestamp_sub", fieldName, unit, amount) + + @JvmStatic + fun timestampSub(fieldName: String, unit: String, amount: Double) = + Function("timestamp_sub", fieldName, unit, amount) + + @JvmStatic fun eq(left: Expr, right: Expr) = BooleanExpr("eq", left, right) + + @JvmStatic fun eq(left: Expr, right: Any) = BooleanExpr("eq", left, right) + + @JvmStatic fun eq(fieldName: String, right: Expr) = BooleanExpr("eq", fieldName, right) + + @JvmStatic fun eq(fieldName: String, right: Any) = BooleanExpr("eq", fieldName, right) + + @JvmStatic fun neq(left: Expr, right: Expr) = BooleanExpr("neq", left, right) + + @JvmStatic fun neq(left: Expr, right: Any) = BooleanExpr("neq", left, right) + + @JvmStatic fun neq(fieldName: String, right: Expr) = BooleanExpr("neq", fieldName, right) + + @JvmStatic fun neq(fieldName: String, right: Any) = BooleanExpr("neq", fieldName, right) + + @JvmStatic fun gt(left: Expr, right: Expr) = BooleanExpr("gt", left, right) + + @JvmStatic fun gt(left: Expr, right: Any) = BooleanExpr("gt", left, right) + + @JvmStatic fun gt(fieldName: String, right: Expr) = BooleanExpr("gt", fieldName, right) + + @JvmStatic fun gt(fieldName: String, right: Any) = BooleanExpr("gt", fieldName, right) + + @JvmStatic fun gte(left: Expr, right: Expr) = BooleanExpr("gte", left, right) + + @JvmStatic fun gte(left: Expr, right: Any) = BooleanExpr("gte", left, right) + + @JvmStatic fun gte(fieldName: String, right: Expr) = BooleanExpr("gte", fieldName, right) + + @JvmStatic fun gte(fieldName: String, right: Any) = BooleanExpr("gte", fieldName, right) + + @JvmStatic fun lt(left: Expr, right: Expr) = BooleanExpr("lt", left, right) + + @JvmStatic fun lt(left: Expr, right: Any) = BooleanExpr("lt", left, right) + + @JvmStatic fun lt(fieldName: String, right: Expr) = BooleanExpr("lt", fieldName, right) + + @JvmStatic fun lt(fieldName: String, right: Any) = BooleanExpr("lt", fieldName, right) + + @JvmStatic fun lte(left: Expr, right: Expr) = BooleanExpr("lte", left, right) + + @JvmStatic fun lte(left: Expr, right: Any) = BooleanExpr("lte", left, right) + + @JvmStatic fun lte(fieldName: String, right: Expr) = BooleanExpr("lte", fieldName, right) + + @JvmStatic fun lte(fieldName: String, right: Any) = BooleanExpr("lte", fieldName, right) + + @JvmStatic fun arrayConcat(array: Expr, vararg arrays: Expr) = Function("array_concat", array, *arrays) + + @JvmStatic + fun arrayConcat(fieldName: String, vararg arrays: Expr) = Function("array_concat", fieldName, *arrays) + + @JvmStatic fun arrayConcat(array: Expr, arrays: List) = Function("array_concat", array, ListOfExprs(toArrayOfExprOrConstant(arrays))) + + @JvmStatic + fun arrayConcat(fieldName: String, arrays: List) = Function("array_concat", fieldName, ListOfExprs(toArrayOfExprOrConstant(arrays))) + + @JvmStatic fun arrayReverse(array: Expr) = Function("array_reverse", array) + + @JvmStatic fun arrayReverse(fieldName: String) = Function("array_reverse", fieldName) + + @JvmStatic fun arrayContains(array: Expr, value: Expr) = BooleanExpr("array_contains", array, value) + + @JvmStatic fun arrayContains(fieldName: String, value: Expr) = BooleanExpr("array_contains", fieldName, value) + + @JvmStatic fun arrayContains(array: Expr, value: Any) = BooleanExpr("array_contains", array, value) + + @JvmStatic fun arrayContains(fieldName: String, value: Any) = BooleanExpr("array_contains", fieldName, value) + + @JvmStatic + fun arrayContainsAll(array: Expr, values: List) = BooleanExpr("array_contains_all", array, ListOfExprs(toArrayOfExprOrConstant(values))) + + @JvmStatic + fun arrayContainsAll(fieldName: String, values: List) = BooleanExpr("array_contains_all", fieldName, ListOfExprs(toArrayOfExprOrConstant(values))) + + @JvmStatic + fun arrayContainsAny(array: Expr, values: List) = BooleanExpr("array_contains_any", array, ListOfExprs(toArrayOfExprOrConstant(values))) + + @JvmStatic + fun arrayContainsAny(fieldName: String, values: List) = BooleanExpr("array_contains_any", fieldName, ListOfExprs(toArrayOfExprOrConstant(values))) + + @JvmStatic fun arrayLength(array: Expr) = Function("array_length", array) + + @JvmStatic fun arrayLength(fieldName: String) = Function("array_length", fieldName) + + @JvmStatic fun ifThen(condition: BooleanExpr, then: Expr) = + Function("if", condition, then, Constant.NULL) + + @JvmStatic fun ifThen(condition: BooleanExpr, then: Any) = Function("if", condition, then, Constant.NULL) + + @JvmStatic fun ifThenElse(condition: BooleanExpr, then: Expr, `else`: Expr) = Function("if", condition, then, `else`) + + @JvmStatic fun ifThenElse(condition: BooleanExpr, then: Any, `else`: Any) = Function("if", condition, then, `else`) + } + + override fun toProto(): Value { + val builder = com.google.firestore.v1.Function.newBuilder() + builder.setName(name) + for (param in params) { + builder.addArgs(param.toProto()) + } + return Value.newBuilder().setFunctionValue(builder).build() + } +} + +class BooleanExpr internal constructor(name: String, params: Array) : + Function(name, params) { + internal constructor(name: String, param: Expr, vararg params: Any) : this(name, arrayOf(param, *toArrayOfExprOrConstant(params))) + internal constructor(name: String, fieldName: String, vararg params: Any) : this(name, arrayOf(Field.of(fieldName), *toArrayOfExprOrConstant(params))) + + fun not() = not(this) + + fun countIf(): Accumulator = Accumulator.countIf(this) + + fun ifThen(then: Expr) = ifThen(this, then) + + fun ifThen(then: Any) = ifThen(this, then) + + fun ifThenElse(then: Expr, `else`: Expr) = ifThenElse(this, then, `else`) + + fun ifThenElse(then: Any, `else`: Any) = ifThenElse(this, then, `else`) +} + +class Ordering private constructor(private val expr: Expr, private val dir: Direction) { + companion object { + @JvmStatic fun ascending(expr: Expr): Ordering = Ordering(expr, Direction.ASCENDING) + + @JvmStatic + fun ascending(fieldName: String): Ordering = Ordering(Field.of(fieldName), Direction.ASCENDING) + + @JvmStatic fun descending(expr: Expr): Ordering = Ordering(expr, Direction.DESCENDING) + + @JvmStatic + fun descending(fieldName: String): Ordering = + Ordering(Field.of(fieldName), Direction.DESCENDING) + } + private class Direction private constructor(val proto: Value) { + private constructor(protoString: String) : this(encodeValue(protoString)) + companion object { + val ASCENDING = Direction("ascending") + val DESCENDING = Direction("descending") + } + } + internal fun toProto(): Value = + Value.newBuilder() + .setMapValue( + MapValue.newBuilder() + .putFields("direction", dir.proto) + .putFields("expression", expr.toProto()) + ) + .build() +} + +// class BitAnd(left: Expr, right: Expr) : Function("bit_and", left, right) { +// constructor(left: Expr, right: Any) : this(left, castToExprOrConvertToConstant(right)) +// constructor(fieldName: String, right: Expr) : this(Field.of(fieldName), right) +// constructor(fieldName: String, right: Any) : this(Field.of(fieldName), right) +// } + +// class BitOr(left: Expr, right: Expr) : Function("bit_or", left, right) { +// constructor(left: Expr, right: Any) : this(left, castToExprOrConvertToConstant(right)) +// constructor(fieldName: String, right: Expr) : this(Field.of(fieldName), right) +// constructor(fieldName: String, right: Any) : this(Field.of(fieldName), right) +// } + +// class BitXor(left: Expr, right: Expr) : Function("bit_xor", left, right) { +// constructor(left: Expr, right: Any) : this(left, castToExprOrConvertToConstant(right)) +// constructor(fieldName: String, right: Expr) : this(Field.of(fieldName), right) +// constructor(fieldName: String, right: Any) : this(Field.of(fieldName), right) +// } + +// class BitNot(left: Expr, right: Expr) : Function("bit_not", left, right) { +// constructor(left: Expr, right: Any) : this(left, castToExprOrConvertToConstant(right)) +// constructor(fieldName: String, right: Expr) : this(Field.of(fieldName), right) +// constructor(fieldName: String, right: Any) : this(Field.of(fieldName), right) +// } + +// class BitLeftShift(left: Expr, right: Expr) : Function("bit_left_shift", left, right) { +// constructor(left: Expr, right: Any) : this(left, castToExprOrConvertToConstant(right)) +// constructor(fieldName: String, right: Expr) : this(Field.of(fieldName), right) +// constructor(fieldName: String, right: Any) : this(Field.of(fieldName), right) +// } + +// class BitRightShift(left: Expr, right: Expr) : Function("bit_right_shift", left, right) { +// constructor(left: Expr, right: Any) : this(left, castToExprOrConvertToConstant(right)) +// constructor(fieldName: String, right: Expr) : this(Field.of(fieldName), right) +// constructor(fieldName: String, right: Any) : this(Field.of(fieldName), right) +// } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/stage.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/stage.kt new file mode 100644 index 00000000000..81fc2ef7499 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/stage.kt @@ -0,0 +1,202 @@ +// Copyright 2025 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.pipeline + +import com.google.common.collect.ImmutableMap +import com.google.firebase.firestore.model.Values.encodeValue +import com.google.firebase.firestore.model.Values.encodeVectorValue +import com.google.firestore.v1.Pipeline +import com.google.firestore.v1.Value + +abstract class Stage +internal constructor(private val name: String, private val options: Map) { + internal constructor(name: String) : this(name, emptyMap()) + internal fun toProtoStage(): Pipeline.Stage { + val builder = Pipeline.Stage.newBuilder() + builder.setName(name) + args().forEach { arg -> builder.addArgs(arg) } + builder.putAllOptions(options) + return builder.build() + } + protected abstract fun args(): Sequence +} + +class DatabaseSource : Stage("database") { + override fun args(): Sequence = emptySequence() +} + +class CollectionSource internal constructor(path: String) : Stage("collection") { + private val path: String = if (path.startsWith("/")) path else "/" + path + override fun args(): Sequence = + sequenceOf(Value.newBuilder().setReferenceValue(path).build()) +} + +class CollectionGroupSource internal constructor(val collectionId: String) : + Stage("collection_group") { + override fun args(): Sequence = + sequenceOf(Value.newBuilder().setReferenceValue("").build(), encodeValue(collectionId)) +} + +class DocumentsSource internal constructor(private val documents: Array) : + Stage("documents") { + override fun args(): Sequence = documents.asSequence().map(::encodeValue) +} + +class AddFieldsStage internal constructor(private val fields: Array) : + Stage("add_fields") { + override fun args(): Sequence = + sequenceOf(encodeValue(fields.associate { it.getAlias() to it.toProto() })) +} + +class AggregateStage +internal constructor( + private val accumulators: Map, + private val groups: Map +) : Stage("aggregate") { + private constructor(accumulators: Map) : this(accumulators, emptyMap()) + companion object { + @JvmStatic + fun withAccumulators(vararg accumulators: AccumulatorWithAlias): AggregateStage { + if (accumulators.isEmpty()) { + throw IllegalArgumentException( + "Must specify at least one accumulator for aggregate() stage. There is a distinct() stage if only distinct group values are needed." + ) + } + return AggregateStage(accumulators.associate { it.alias to it.accumulator }) + } + } + + fun withGroups(vararg groups: Selectable) = + AggregateStage(accumulators, groups.associateBy(Selectable::getAlias)) + + fun withGroups(vararg fields: String) = + AggregateStage(accumulators, fields.associateWith(Field::of)) + + fun withGroups(vararg selectable: Any) = + AggregateStage( + accumulators, + selectable.map(Selectable::toSelectable).associateBy(Selectable::getAlias) + ) + + override fun args(): Sequence = + sequenceOf( + encodeValue(accumulators.mapValues { entry -> entry.value.toProto() }), + encodeValue(groups.mapValues { entry -> entry.value.toProto() }) + ) +} + +class WhereStage internal constructor(private val condition: BooleanExpr) : Stage("where") { + override fun args(): Sequence = sequenceOf(condition.toProto()) +} + +class FindNearestStage +internal constructor( + private val property: Expr, + private val vector: DoubleArray, + private val distanceMeasure: DistanceMeasure, + private val options: FindNearestOptions +) : Stage("find_nearest", options.toProto()) { + + class DistanceMeasure private constructor(internal val proto: Value) { + private constructor(protoString: String) : this(encodeValue(protoString)) + companion object { + val EUCLIDEAN = DistanceMeasure("euclidean") + val COSINE = DistanceMeasure("cosine") + val DOT_PRODUCT = DistanceMeasure("dot_product") + } + } + + override fun args(): Sequence = + sequenceOf(property.toProto(), encodeVectorValue(vector), distanceMeasure.proto) +} + +class FindNearestOptions +internal constructor(private val limit: Long?, private val distanceField: Field?) { + fun toProto(): Map { + val builder = ImmutableMap.builder() + if (limit != null) { + builder.put("limit", encodeValue(limit)) + } + if (distanceField != null) { + builder.put("distance_field", distanceField.toProto()) + } + return builder.build() + } +} + +class LimitStage internal constructor(private val limit: Long) : Stage("limit") { + override fun args(): Sequence = sequenceOf(encodeValue(limit)) +} + +class OffsetStage internal constructor(private val offset: Long) : Stage("offset") { + override fun args(): Sequence = sequenceOf(encodeValue(offset)) +} + +class SelectStage internal constructor(private val fields: Array) : + Stage("select") { + override fun args(): Sequence = + sequenceOf(encodeValue(fields.associate { it.getAlias() to it.toProto() })) +} + +class SortStage internal constructor(private val orders: Array) : Stage("sort") { + override fun args(): Sequence = orders.asSequence().map(Ordering::toProto) +} + +class DistinctStage internal constructor(private val groups: Array) : + Stage("distinct") { + override fun args(): Sequence = + sequenceOf(encodeValue(groups.associate { it.getAlias() to it.toProto() })) +} + +class RemoveFieldsStage internal constructor(private val fields: Array) : + Stage("remove_fields") { + override fun args(): Sequence = fields.asSequence().map(Field::toProto) +} + +class ReplaceStage internal constructor(private val field: Selectable, private val mode: Mode) : + Stage("replace") { + class Mode private constructor(internal val proto: Value) { + private constructor(protoString: String) : this(encodeValue(protoString)) + companion object { + val FULL_REPLACE = Mode("full_replace") + val MERGE_PREFER_NEXT = Mode("merge_prefer_nest") + val MERGE_PREFER_PARENT = Mode("merge_prefer_parent") + } + } + override fun args(): Sequence = sequenceOf(field.toProto(), mode.proto) +} + +class SampleStage internal constructor(private val size: Number, private val mode: Mode) : + Stage("sample") { + class Mode private constructor(internal val proto: Value) { + private constructor(protoString: String) : this(encodeValue(protoString)) + companion object { + val DOCUMENTS = Mode("documents") + val PERCENT = Mode("percent") + } + } + override fun args(): Sequence = sequenceOf(encodeValue(size), mode.proto) +} + +class UnionStage internal constructor(private val other: com.google.firebase.firestore.Pipeline) : + Stage("union") { + override fun args(): Sequence = + sequenceOf(Value.newBuilder().setPipelineValue(other.toPipelineProto()).build()) +} + +class UnnestStage internal constructor(private val selectable: Selectable) : Stage("unnest") { + override fun args(): Sequence = + sequenceOf(encodeValue(selectable.getAlias()), selectable.toProto()) +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java index 87365361be4..91086531db1 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java @@ -21,8 +21,10 @@ import androidx.annotation.VisibleForTesting; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.common.base.Strings; import com.google.firebase.firestore.AggregateField; import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.PipelineResultObserver; import com.google.firebase.firestore.core.Query; import com.google.firebase.firestore.model.DocumentKey; import com.google.firebase.firestore.model.MutableDocument; @@ -34,6 +36,9 @@ import com.google.firestore.v1.BatchGetDocumentsResponse; import com.google.firestore.v1.CommitRequest; import com.google.firestore.v1.CommitResponse; +import com.google.firestore.v1.Document; +import com.google.firestore.v1.ExecutePipelineRequest; +import com.google.firestore.v1.ExecutePipelineResponse; import com.google.firestore.v1.FirestoreGrpc; import com.google.firestore.v1.RunAggregationQueryRequest; import com.google.firestore.v1.RunAggregationQueryResponse; @@ -237,6 +242,48 @@ public Task> runAggregateQuery( }); } + public void executePipeline(ExecutePipelineRequest request, PipelineResultObserver observer) { + channel.runStreamingResponseRpc( + FirestoreGrpc.getExecutePipelineMethod(), + request, + new FirestoreChannel.StreamingListener() { + + private SnapshotVersion executionTime = SnapshotVersion.NONE; + + @Override + public void onMessage(ExecutePipelineResponse message) { + setExecutionTime(serializer.decodeVersion(message.getExecutionTime())); + for (Document document : message.getResultsList()) { + String documentName = document.getName(); + observer.onDocument( + Strings.isNullOrEmpty(documentName) ? null : serializer.decodeKey(documentName), + document.getFieldsMap(), + serializer.decodeVersion(document.getUpdateTime())); + } + } + + @Override + public void onClose(Status status) { + if (status.isOk()) { + observer.onComplete(executionTime); + } else { + FirebaseFirestoreException exception = exceptionFromStatus(status); + if (exception.getCode() == FirebaseFirestoreException.Code.UNAUTHENTICATED) { + channel.invalidateToken(); + } + observer.onError(exception); + } + } + + private void setExecutionTime(SnapshotVersion executionTime) { + if (executionTime.equals(SnapshotVersion.NONE)) { + return; + } + this.executionTime = executionTime; + } + }); + } + /** * Determines whether the given status has an error code that represents a permanent error when * received in response to a non-write operation. diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index d2a139d4b6f..05f2bfa9837 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -23,6 +23,7 @@ import com.google.firebase.database.collection.ImmutableSortedSet; import com.google.firebase.firestore.AggregateField; import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.PipelineResultObserver; import com.google.firebase.firestore.core.OnlineState; import com.google.firebase.firestore.core.Query; import com.google.firebase.firestore.core.Transaction; @@ -43,6 +44,7 @@ import com.google.firebase.firestore.util.AsyncQueue; import com.google.firebase.firestore.util.Logger; import com.google.firebase.firestore.util.Util; +import com.google.firestore.v1.ExecutePipelineRequest; import com.google.firestore.v1.Value; import com.google.protobuf.ByteString; import io.grpc.Status; @@ -777,4 +779,14 @@ public Task> runAggregateQuery( "Failed to get result from server.", FirebaseFirestoreException.Code.UNAVAILABLE)); } } + + public void executePipeline(ExecutePipelineRequest request, PipelineResultObserver observer) { + if (canUseNetwork()) { + datastore.executePipeline(request, observer); + } else { + observer.onError( + new FirebaseFirestoreException( + "Failed to get result from server.", FirebaseFirestoreException.Code.UNAVAILABLE)); + } + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java index eba5e4e5500..543da11e7d3 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java @@ -85,6 +85,13 @@ public static int compareIntegers(int i1, int i2) { } } + /** Compare strings in UTF-8 encoded byte order */ + public static int compareUtf8Strings(String left, String right) { + ByteString leftBytes = ByteString.copyFromUtf8(left); + ByteString rightBytes = ByteString.copyFromUtf8(right); + return compareByteStrings(leftBytes, rightBytes); + } + /** * Utility function to compare longs. Note that we can't use Long.compare because it's only * available after Android 19. diff --git a/firebase-firestore/src/proto/google/firestore/v1/document.proto b/firebase-firestore/src/proto/google/firestore/v1/document.proto index 52dc85ca9df..9947a289a1e 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/document.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/document.proto @@ -129,6 +129,50 @@ message Value { // A map value. MapValue map_value = 6; + + + // Value which references a field. + // + // This is considered relative (vs absolute) since it only refers to a field + // and not a field within a particular document. + // + // **Requires:** + // + // * Must follow [field reference][FieldReference.field_path] limitations. + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): long term, there is no reason this type should not be + // allowed to be used on the write path. --) + string field_reference_value = 19; + + // A value that represents an unevaluated expression. + // + // **Requires:** + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): similar to above, there is no reason to not allow + // storing expressions into the database, just no plan to support in + // the near term. + // + // This would actually be an interesting way to represent user-defined + // functions or more expressive rules-based systems. --) + Function function_value = 20; + + // A value that represents an unevaluated pipeline. + // + // **Requires:** + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): similar to above, there is no reason to not allow + // storing expressions into the database, just no plan to support in + // the near term. + // + // This would actually be an interesting way to represent user-defined + // functions or more expressive rules-based systems. --) + Pipeline pipeline_value = 21; } } @@ -148,3 +192,73 @@ message MapValue { // not exceed 1,500 bytes and cannot be empty. map fields = 1; } + +// Represents an unevaluated scalar expression. +// +// For example, the expression `like(user_name, "%alice%")` is represented as: +// +// ``` +// name: "like" +// args { field_reference: "user_name" } +// args { string_value: "%alice%" } +// ``` +// +// (-- api-linter: core::0123::resource-annotation=disabled +// aip.dev/not-precedent: this is not a One Platform API resource. --) +message Function { + // The name of the function to evaluate. + // + // **Requires:** + // + // * must be in snake case (lower case with underscore separator). + // + string name = 1; + + // Ordered list of arguments the given function expects. + repeated Value args = 2; + + // Optional named arguments that certain functions may support. + map options = 3; +} + +// A Firestore query represented as an ordered list of operations / stages. +message Pipeline { + // A single operation within a pipeline. + // + // A stage is made up of a unique name, and a list of arguments. The exact + // number of arguments & types is dependent on the stage type. + // + // To give an example, the stage `filter(state = "MD")` would be encoded as: + // + // ``` + // name: "filter" + // args { + // function_value { + // name: "eq" + // args { field_reference_value: "state" } + // args { string_value: "MD" } + // } + // } + // ``` + // + // See public documentation for the full list. + message Stage { + // The name of the stage to evaluate. + // + // **Requires:** + // + // * must be in snake case (lower case with underscore separator). + // + string name = 1; + + // Ordered list of arguments the given stage expects. + repeated Value args = 2; + + // Optional named arguments that certain functions may support. + map options = 3; + } + + // Ordered list of stages to evaluate. + repeated Stage stages = 1; +} + diff --git a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto index 9ea56429afc..d59c9e2decb 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto @@ -22,6 +22,7 @@ import "google/api/field_behavior.proto"; import "google/firestore/v1/aggregation_result.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; +import "google/firestore/v1/pipeline.proto"; import "google/firestore/v1/query.proto"; import "google/firestore/v1/write.proto"; import "google/protobuf/empty.proto"; @@ -139,6 +140,15 @@ service Firestore { }; } + // Executes a pipeline query. + rpc ExecutePipeline(ExecutePipelineRequest) + returns (stream ExecutePipelineResponse) { + option (google.api.http) = { + post: "/v1beta1/{database=projects/*/databases/*}:executePipeline" + body: "*" + }; + } + // Runs an aggregation query. // // Rather than producing [Document][google.firestore.v1.Document] results like @@ -574,6 +584,80 @@ message RunQueryResponse { int32 skipped_results = 4; } +// The request for [Firestore.ExecutePipeline][]. +message ExecutePipelineRequest { + // Database identifier, in the form `projects/{project}/databases/{database}`. + string database = 1; + + oneof pipeline_type { + // A pipelined operation. + StructuredPipeline structured_pipeline = 2; + } + + // Optional consistency arguments, defaults to strong consistency. + oneof consistency_selector { + // Run the query within an already active transaction. + // + // The value here is the opaque transaction ID to execute the query in. + bytes transaction = 5; + + // Execute the pipeline in a new transaction. + // + // The identifier of the newly created transaction will be returned in the + // first response on the stream. This defaults to a read-only transaction. + TransactionOptions new_transaction = 6; + + // Execute the pipeline in a snapshot transaction at the given time. + // + // This must be a microsecond precision timestamp within the past one hour, + // or if Point-in-Time Recovery is enabled, can additionally be a whole + // minute timestamp within the past 7 days. + google.protobuf.Timestamp read_time = 7; + } + + // Explain / analyze options for the pipeline. + // ExplainOptions explain_options = 8 [(google.api.field_behavior) = OPTIONAL]; +} + +// The response for [Firestore.Execute][]. +message ExecutePipelineResponse { + // Newly created transaction identifier. + // + // This field is only specified on the first response from the server when + // the request specified [ExecuteRequest.new_transaction][]. + bytes transaction = 1; + + // An ordered batch of results returned executing a pipeline. + // + // The batch size is variable, and can even be zero for when only a partial + // progress message is returned. + // + // The fields present in the returned documents are only those that were + // explicitly requested in the pipeline, this include those like + // [`__name__`][Document.name] & [`__update_time__`][Document.update_time]. + // This is explicitly a divergence from `Firestore.RunQuery` / + // `Firestore.GetDocument` RPCs which always return such fields even when they + // are not specified in the [`mask`][DocumentMask]. + repeated Document results = 2; + + // The time at which the document(s) were read. + // + // This may be monotonically increasing; in this case, the previous documents + // in the result stream are guaranteed not to have changed between their + // `execution_time` and this one. + // + // If the query returns no results, a response with `execution_time` and no + // `results` will be sent, and this represents the time at which the operation + // was run. + google.protobuf.Timestamp execution_time = 3; + + // Query explain metrics. + // + // Set on the last response when [ExecutePipelineRequest.explain_options][] + // was specified on the request. + // ExplainMetrics explain_metrics = 4; +} + // The request for [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. message RunAggregationQueryRequest { // Required. The parent resource name. In the format: diff --git a/firebase-firestore/src/proto/google/firestore/v1/pipeline.proto b/firebase-firestore/src/proto/google/firestore/v1/pipeline.proto new file mode 100644 index 00000000000..f425ec6911a --- /dev/null +++ b/firebase-firestore/src/proto/google/firestore/v1/pipeline.proto @@ -0,0 +1,40 @@ +// Copyright 2024 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. + +syntax = "proto3"; +package google.firestore.v1; +import "google/firestore/v1/document.proto"; +option csharp_namespace = "Google.Cloud.Firestore.V1"; +option php_namespace = "Google\\Cloud\\Firestore\\V1"; +option ruby_package = "Google::Cloud::Firestore::V1"; +option java_multiple_files = true; +option java_package = "com.google.firestore.v1"; +option java_outer_classname = "PipelineProto"; +option objc_class_prefix = "GCFS"; +// A Firestore query represented as an ordered list of operations / stages. +// +// This is considered the top-level function which plans & executes a query. +// It is logically equivalent to `query(stages, options)`, but prevents the +// client from having to build a function wrapper. +message StructuredPipeline { + // The pipeline query to execute. + Pipeline pipeline = 1; + // Optional query-level arguments. + // + // (-- Think query statement hints. --) + // + // (-- TODO(batchik): define the api contract of using an unsupported hint --) + map options = 2; +} + diff --git a/firebase-firestore/src/testUtil/java/com/google/firebase/firestore/TestAccessHelper.java b/firebase-firestore/src/testUtil/java/com/google/firebase/firestore/TestAccessHelper.java index bab88979493..b62ec1b9933 100644 --- a/firebase-firestore/src/testUtil/java/com/google/firebase/firestore/TestAccessHelper.java +++ b/firebase-firestore/src/testUtil/java/com/google/firebase/firestore/TestAccessHelper.java @@ -14,14 +14,20 @@ package com.google.firebase.firestore; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.firebase.firestore.model.DatabaseId; 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); + // We can use mock here because the tests only use this as a wrapper for documentKeys. + FirebaseFirestore mock = mock(FirebaseFirestore.class); + when(mock.getDatabaseId()).thenReturn(DatabaseId.forProject("project")); + return new DocumentReference(documentKey, mock); } /** Makes the getKey() method accessible. */ diff --git a/firebase-functions/CHANGELOG.md b/firebase-functions/CHANGELOG.md index c26be0a15b6..e9fe66c897d 100644 --- a/firebase-functions/CHANGELOG.md +++ b/firebase-functions/CHANGELOG.md @@ -1,4 +1,6 @@ # Unreleased +* [fixed] Resolve Kotlin migration visibility issues + ([#6522](//github.com/firebase/firebase-android-sdk/pull/6522)) # 21.1.0 diff --git a/firebase-functions/api.txt b/firebase-functions/api.txt index 323ba72e22f..a9a05c703a8 100644 --- a/firebase-functions/api.txt +++ b/firebase-functions/api.txt @@ -1,41 +1,37 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.functions { public final class FirebaseFunctions { - method @NonNull public com.google.firebase.functions.HttpsCallableReference getHttpsCallable(@NonNull String name); - method @NonNull public com.google.firebase.functions.HttpsCallableReference getHttpsCallable(@NonNull String name, @NonNull com.google.firebase.functions.HttpsCallableOptions options); - method @NonNull public com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(@NonNull java.net.URL url); - method @NonNull public com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(@NonNull java.net.URL url, @NonNull com.google.firebase.functions.HttpsCallableOptions options); - method @NonNull public static com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull com.google.firebase.FirebaseApp app, @NonNull String regionOrCustomDomain); - method @NonNull public static com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull String regionOrCustomDomain); - method @NonNull public static com.google.firebase.functions.FirebaseFunctions getInstance(); - method public void useEmulator(@NonNull String host, int port); - method @Deprecated public void useFunctionsEmulator(@NonNull String origin); - field @NonNull public static final com.google.firebase.functions.FirebaseFunctions.Companion Companion; + method public com.google.firebase.functions.HttpsCallableReference getHttpsCallable(String name); + method public com.google.firebase.functions.HttpsCallableReference getHttpsCallable(String name, com.google.firebase.functions.HttpsCallableOptions options); + method public com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(java.net.URL url); + method public com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(java.net.URL url, com.google.firebase.functions.HttpsCallableOptions options); + method public static com.google.firebase.functions.FirebaseFunctions getInstance(); + method public static com.google.firebase.functions.FirebaseFunctions getInstance(com.google.firebase.FirebaseApp app); + method public static com.google.firebase.functions.FirebaseFunctions getInstance(com.google.firebase.FirebaseApp app, String regionOrCustomDomain); + method public static com.google.firebase.functions.FirebaseFunctions getInstance(String regionOrCustomDomain); + method public void useEmulator(String host, int port); + method @Deprecated public void useFunctionsEmulator(String origin); + field public static final com.google.firebase.functions.FirebaseFunctions.Companion Companion; } public static final class FirebaseFunctions.Companion { - method @NonNull public com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull com.google.firebase.FirebaseApp app, @NonNull String regionOrCustomDomain); - method @NonNull public com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull com.google.firebase.FirebaseApp app); - method @NonNull public com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull String regionOrCustomDomain); - method @NonNull public com.google.firebase.functions.FirebaseFunctions getInstance(); + method public com.google.firebase.functions.FirebaseFunctions getInstance(); + method public com.google.firebase.functions.FirebaseFunctions getInstance(com.google.firebase.FirebaseApp app); + method public com.google.firebase.functions.FirebaseFunctions getInstance(com.google.firebase.FirebaseApp app, String regionOrCustomDomain); + method public com.google.firebase.functions.FirebaseFunctions getInstance(String regionOrCustomDomain); } public final class FirebaseFunctionsException extends com.google.firebase.FirebaseException { - method @Nullable public static com.google.firebase.functions.FirebaseFunctionsException fromResponse(@NonNull com.google.firebase.functions.FirebaseFunctionsException.Code code, @Nullable String body, @NonNull com.google.firebase.functions.Serializer serializer); - method @NonNull public com.google.firebase.functions.FirebaseFunctionsException.Code getCode(); - method @Nullable public Object getDetails(); - property @NonNull public final com.google.firebase.functions.FirebaseFunctionsException.Code code; - property @Nullable public final Object details; - field @NonNull public static final com.google.firebase.functions.FirebaseFunctionsException.Companion Companion; + method public com.google.firebase.functions.FirebaseFunctionsException.Code getCode(); + method public Object? getDetails(); + property public final com.google.firebase.functions.FirebaseFunctionsException.Code code; + property public final Object? details; } public enum FirebaseFunctionsException.Code { - method @NonNull public static final com.google.firebase.functions.FirebaseFunctionsException.Code fromHttpStatus(int status); - method @NonNull public static final com.google.firebase.functions.FirebaseFunctionsException.Code fromValue(int value); - method @NonNull public static com.google.firebase.functions.FirebaseFunctionsException.Code valueOf(@NonNull String name) throws java.lang.IllegalArgumentException; - method @NonNull public static com.google.firebase.functions.FirebaseFunctionsException.Code[] values(); + method public static com.google.firebase.functions.FirebaseFunctionsException.Code fromHttpStatus(int status); + method public static com.google.firebase.functions.FirebaseFunctionsException.Code fromValue(int value); enum_constant public static final com.google.firebase.functions.FirebaseFunctionsException.Code ABORTED; enum_constant public static final com.google.firebase.functions.FirebaseFunctionsException.Code ALREADY_EXISTS; enum_constant public static final com.google.firebase.functions.FirebaseFunctionsException.Code CANCELLED; @@ -53,35 +49,21 @@ package com.google.firebase.functions { enum_constant public static final com.google.firebase.functions.FirebaseFunctionsException.Code UNAVAILABLE; enum_constant public static final com.google.firebase.functions.FirebaseFunctionsException.Code UNIMPLEMENTED; enum_constant public static final com.google.firebase.functions.FirebaseFunctionsException.Code UNKNOWN; - field @NonNull public static final com.google.firebase.functions.FirebaseFunctionsException.Code.Companion Companion; + field public static final com.google.firebase.functions.FirebaseFunctionsException.Code.Companion Companion; } public static final class FirebaseFunctionsException.Code.Companion { - method @NonNull public com.google.firebase.functions.FirebaseFunctionsException.Code fromHttpStatus(int status); - method @NonNull public com.google.firebase.functions.FirebaseFunctionsException.Code fromValue(int value); - } - - public static final class FirebaseFunctionsException.Companion { - method @Nullable public com.google.firebase.functions.FirebaseFunctionsException fromResponse(@NonNull com.google.firebase.functions.FirebaseFunctionsException.Code code, @Nullable String body, @NonNull com.google.firebase.functions.Serializer serializer); + method public com.google.firebase.functions.FirebaseFunctionsException.Code fromHttpStatus(int status); + method public com.google.firebase.functions.FirebaseFunctionsException.Code fromValue(int value); } public final class FunctionsKt { - method @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.Firebase, @NonNull String regionOrCustomDomain); - method @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String regionOrCustomDomain); - method @NonNull public static com.google.firebase.functions.FirebaseFunctions getFunctions(@NonNull com.google.firebase.Firebase); - method @NonNull public static com.google.firebase.functions.HttpsCallableReference getHttpsCallable(@NonNull com.google.firebase.functions.FirebaseFunctions, @NonNull String name, @NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(@NonNull com.google.firebase.functions.FirebaseFunctions, @NonNull java.net.URL url, @NonNull kotlin.jvm.functions.Function1 init); - } - - public final class HttpsCallOptions { - ctor public HttpsCallOptions(@NonNull com.google.firebase.functions.HttpsCallableOptions publicCallableOptions); - ctor public HttpsCallOptions(); - method @NonNull public okhttp3.OkHttpClient apply(@NonNull okhttp3.OkHttpClient client); - method public boolean getLimitedUseAppCheckTokens(); - method public long getTimeout(); - method public void setTimeout(long timeout, @NonNull java.util.concurrent.TimeUnit units); - field public final boolean limitedUseAppCheckTokens; + method public static com.google.firebase.functions.FirebaseFunctions functions(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); + method public static com.google.firebase.functions.FirebaseFunctions functions(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app, String regionOrCustomDomain); + method public static com.google.firebase.functions.FirebaseFunctions functions(com.google.firebase.Firebase, String regionOrCustomDomain); + method public static com.google.firebase.functions.FirebaseFunctions getFunctions(com.google.firebase.Firebase); + method public static com.google.firebase.functions.HttpsCallableReference getHttpsCallable(com.google.firebase.functions.FirebaseFunctions, String name, kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(com.google.firebase.functions.FirebaseFunctions, java.net.URL url, kotlin.jvm.functions.Function1 init); } public final class HttpsCallableOptions { @@ -91,38 +73,24 @@ package com.google.firebase.functions { public static final class HttpsCallableOptions.Builder { ctor public HttpsCallableOptions.Builder(); - method @NonNull public com.google.firebase.functions.HttpsCallableOptions build(); + method public com.google.firebase.functions.HttpsCallableOptions build(); method public boolean getLimitedUseAppCheckTokens(); - method @NonNull public com.google.firebase.functions.HttpsCallableOptions.Builder setLimitedUseAppCheckTokens(boolean limitedUse); + method public com.google.firebase.functions.HttpsCallableOptions.Builder setLimitedUseAppCheckTokens(boolean limitedUse); field public boolean limitedUseAppCheckTokens; } public final class HttpsCallableReference { - method @NonNull public com.google.android.gms.tasks.Task call(@Nullable Object data); - method @NonNull public com.google.android.gms.tasks.Task call(); - method @NonNull public com.google.firebase.functions.HttpsCallOptions getOptions(); + method public com.google.android.gms.tasks.Task call(); + method public com.google.android.gms.tasks.Task call(Object? data); method public long getTimeout(); - method public void setTimeout(long timeout, @NonNull java.util.concurrent.TimeUnit units); - method @NonNull public com.google.firebase.functions.HttpsCallableReference withTimeout(long timeout, @NonNull java.util.concurrent.TimeUnit units); - property @NonNull public final com.google.firebase.functions.HttpsCallOptions options; + method public void setTimeout(long timeout, java.util.concurrent.TimeUnit units); + method public com.google.firebase.functions.HttpsCallableReference withTimeout(long timeout, java.util.concurrent.TimeUnit units); property public final long timeout; } public final class HttpsCallableResult { - method @Nullable public Object getData(); - field @Nullable public final Object data; - } - - public final class Serializer { - ctor public Serializer(); - method @Nullable public Object decode(@NonNull Object obj); - method @NonNull public Object encode(@Nullable Object obj); - field @NonNull public static final com.google.firebase.functions.Serializer.Companion Companion; - field @NonNull @VisibleForTesting public static final String LONG_TYPE = "type.googleapis.com/google.protobuf.Int64Value"; - field @NonNull @VisibleForTesting public static final String UNSIGNED_LONG_TYPE = "type.googleapis.com/google.protobuf.UInt64Value"; - } - - public static final class Serializer.Companion { + method public Object? getData(); + field public final Object? data; } } @@ -130,12 +98,12 @@ package com.google.firebase.functions { package com.google.firebase.functions.ktx { public final class FunctionsKt { - method @Deprecated @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.ktx.Firebase, @NonNull String regionOrCustomDomain); - method @Deprecated @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @Deprecated @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String regionOrCustomDomain); - method @Deprecated @NonNull public static com.google.firebase.functions.FirebaseFunctions getFunctions(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @NonNull public static com.google.firebase.functions.HttpsCallableReference getHttpsCallable(@NonNull com.google.firebase.functions.FirebaseFunctions, @NonNull String name, @NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated @NonNull public static com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(@NonNull com.google.firebase.functions.FirebaseFunctions, @NonNull java.net.URL url, @NonNull kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.firebase.functions.FirebaseFunctions functions(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); + method @Deprecated public static com.google.firebase.functions.FirebaseFunctions functions(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app, String regionOrCustomDomain); + method @Deprecated public static com.google.firebase.functions.FirebaseFunctions functions(com.google.firebase.ktx.Firebase, String regionOrCustomDomain); + method @Deprecated public static com.google.firebase.functions.FirebaseFunctions getFunctions(com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.functions.HttpsCallableReference getHttpsCallable(com.google.firebase.functions.FirebaseFunctions, String name, kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(com.google.firebase.functions.FirebaseFunctions, java.net.URL url, kotlin.jvm.functions.Function1 init); } } diff --git a/firebase-functions/ktx/api.txt b/firebase-functions/ktx/api.txt index 60c7053d554..da4f6cc18fe 100644 --- a/firebase-functions/ktx/api.txt +++ b/firebase-functions/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.functions.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-functions/ktx/src/androidTest/kotlin/com/google/firebase/functions/ktx/CallTests.kt b/firebase-functions/ktx/src/androidTest/kotlin/com/google/firebase/functions/ktx/CallTests.kt deleted file mode 100644 index 87ca19f8348..00000000000 --- a/firebase-functions/ktx/src/androidTest/kotlin/com/google/firebase/functions/ktx/CallTests.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.functions.ktx - -import androidx.test.InstrumentationRegistry -import androidx.test.runner.AndroidJUnit4 -import com.google.android.gms.tasks.Tasks -import com.google.common.truth.Truth.assertThat -import com.google.firebase.FirebaseApp -import com.google.firebase.ktx.Firebase -import com.google.firebase.ktx.app -import com.google.firebase.ktx.initialize -import org.junit.AfterClass -import org.junit.BeforeClass -import org.junit.Test -import org.junit.runner.RunWith - -const val APP_ID = "APP_ID" -const val API_KEY = "API_KEY" - -@RunWith(AndroidJUnit4::class) -class CallTests { - companion object { - lateinit var app: FirebaseApp - - @BeforeClass - @JvmStatic - fun setup() { - app = Firebase.initialize(InstrumentationRegistry.getContext())!! - } - - @AfterClass - @JvmStatic - fun cleanup() { - app.delete() - } - } - - @Test - fun testDataCall() { - val functions = Firebase.functions(app) - val input = - hashMapOf( - "bool" to true, - "int" to 2, - "long" to 3L, - "string" to "four", - "array" to listOf(5, 6), - "null" to null - ) - - var function = functions.getHttpsCallable("dataTest") - val actual = Tasks.await(function.call(input)).getData() - - assertThat(actual).isInstanceOf(Map::class.java) - @Suppress("UNCHECKED_CAST") val map = actual as Map - assertThat(map["message"]).isEqualTo("stub response") - assertThat(map["code"]).isEqualTo(42) - assertThat(map["long"]).isEqualTo(420L) - } - - @Test - fun testNullDataCall() { - val functions = Firebase.functions(app) - var function = functions.getHttpsCallable("nullTest") - val actual = Tasks.await(function.call(null)).getData() - - assertThat(actual).isNull() - } - - @Test - fun testEmptyDataCall() { - val functions = Firebase.functions(app) - var function = functions.getHttpsCallable("nullTest") - val actual = Tasks.await(function.call()).getData() - - assertThat(actual).isNull() - } -} diff --git a/firebase-functions/ktx/src/test/kotlin/com/google/firebase/functions/TestVisibilityUtil.kt b/firebase-functions/ktx/src/test/kotlin/com/google/firebase/functions/TestVisibilityUtil.kt deleted file mode 100644 index 9a8be4b4f9c..00000000000 --- a/firebase-functions/ktx/src/test/kotlin/com/google/firebase/functions/TestVisibilityUtil.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2023 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.functions - -/** - * Returns true if the {@link HttpsCallableReference} is configured to use FAC limited-use tokens. - */ -fun HttpsCallableReference.usesLimitedUseFacTokens() = options.getLimitedUseAppCheckTokens() diff --git a/firebase-functions/ktx/src/test/kotlin/com/google/firebase/functions/ktx/FunctionsTests.kt b/firebase-functions/ktx/src/test/kotlin/com/google/firebase/functions/ktx/FunctionsTests.kt deleted file mode 100644 index 34e845c7927..00000000000 --- a/firebase-functions/ktx/src/test/kotlin/com/google/firebase/functions/ktx/FunctionsTests.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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.functions.ktx - -import androidx.test.core.app.ApplicationProvider -import com.google.common.truth.Truth.assertThat -import com.google.firebase.FirebaseApp -import com.google.firebase.FirebaseOptions -import com.google.firebase.functions.FirebaseFunctions -import com.google.firebase.functions.usesLimitedUseFacTokens -import com.google.firebase.ktx.Firebase -import com.google.firebase.ktx.app -import com.google.firebase.ktx.initialize -import com.google.firebase.platforminfo.UserAgentPublisher -import java.net.URL -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -const val APP_ID = "APP_ID" -const val API_KEY = "API_KEY" - -const val EXISTING_APP = "existing" - -abstract class BaseTestCase { - @Before - fun setUp() { - Firebase.initialize( - ApplicationProvider.getApplicationContext(), - FirebaseOptions.Builder() - .setApplicationId(APP_ID) - .setApiKey(API_KEY) - .setProjectId("123") - .build() - ) - - Firebase.initialize( - ApplicationProvider.getApplicationContext(), - FirebaseOptions.Builder() - .setApplicationId(APP_ID) - .setApiKey(API_KEY) - .setProjectId("123") - .build(), - EXISTING_APP - ) - } - - @After - fun cleanUp() { - FirebaseApp.clearInstancesForTest() - } -} - -@RunWith(RobolectricTestRunner::class) -class FunctionsTests : BaseTestCase() { - - @Test - fun `functions should delegate to FirebaseFunctions#getInstance()`() { - assertThat(Firebase.functions).isSameInstanceAs(FirebaseFunctions.getInstance()) - } - - @Test - fun `FirebaseApp#functions should delegate to FirebaseFunctions#getInstance(FirebaseApp)`() { - val app = Firebase.app(EXISTING_APP) - assertThat(Firebase.functions(app)).isSameInstanceAs(FirebaseFunctions.getInstance(app)) - } - - @Test - fun `Firebase#functions should delegate to FirebaseFunctions#getInstance(region)`() { - val region = "valid_region" - assertThat(Firebase.functions(region)).isSameInstanceAs(FirebaseFunctions.getInstance(region)) - } - - @Test - fun `Firebase#functions should delegate to FirebaseFunctions#getInstance(FirebaseApp, region)`() { - val app = Firebase.app(EXISTING_APP) - val region = "valid_region" - assertThat(Firebase.functions(app, region)) - .isSameInstanceAs(FirebaseFunctions.getInstance(app, region)) - } -} - -@RunWith(RobolectricTestRunner::class) -class LibraryVersionTest : BaseTestCase() { - @Test - fun `library version should be registered with runtime`() { - val publisher = Firebase.app.get(UserAgentPublisher::class.java) - assertThat(publisher.userAgent).contains(LIBRARY_NAME) - } -} - -@RunWith(RobolectricTestRunner::class) -class AppCheckLimitedUseTest : BaseTestCase() { - @Test - fun `FirebaseFunctions#getHttpsCallable should not use limited-use tokens by default`() { - val callable = Firebase.functions.getHttpsCallable("function") - assertThat(callable.usesLimitedUseFacTokens()).isFalse() - } - - @Test - fun `FirebaseFunctions#getHttpsCallable should build callable with FAC settings (when true)`() { - val callable = - Firebase.functions.getHttpsCallable("function") { limitedUseAppCheckTokens = true } - assertThat(callable.usesLimitedUseFacTokens()).isTrue() - } - - @Test - fun `FirebaseFunctions#getHttpsCallable should build callable with FAC settings (when false)`() { - val callable = - Firebase.functions.getHttpsCallable("function") { limitedUseAppCheckTokens = false } - assertThat(callable.usesLimitedUseFacTokens()).isFalse() - } - - @Test - fun `FirebaseFunctions#getHttpsCallableFromUrl should not use limited-use tokens by default`() { - val callable = Firebase.functions.getHttpsCallableFromUrl(URL("https://functions.test")) - assertThat(callable.usesLimitedUseFacTokens()).isFalse() - } - - @Test - fun `FirebaseFunctions#getHttpsCallableFromUrl callable with FAC settings (when true)`() { - val callable = - Firebase.functions.getHttpsCallableFromUrl(URL("https://functions.test")) { - limitedUseAppCheckTokens = true - } - assertThat(callable.usesLimitedUseFacTokens()).isTrue() - } - - @Test - fun `FirebaseFunctions#getHttpsCallableFromUrl callable with FAC settings (when false)`() { - val callable = - Firebase.functions.getHttpsCallableFromUrl(URL("https://functions.test")) { - limitedUseAppCheckTokens = false - } - assertThat(callable.usesLimitedUseFacTokens()).isFalse() - } -} diff --git a/firebase-functions/src/main/java/com/google/firebase/functions/FirebaseFunctions.kt b/firebase-functions/src/main/java/com/google/firebase/functions/FirebaseFunctions.kt index 3c0e7d6553e..824670c4346 100644 --- a/firebase-functions/src/main/java/com/google/firebase/functions/FirebaseFunctions.kt +++ b/firebase-functions/src/main/java/com/google/firebase/functions/FirebaseFunctions.kt @@ -146,7 +146,7 @@ internal constructor( } } - @Deprecated("Use {@link #useEmulator(String, int)} to connect to the emulator. ") + @Deprecated("Use useEmulator to connect to the emulator.") public fun useFunctionsEmulator(origin: String) { Preconditions.checkNotNull(origin, "origin cannot be null") urlFormat = "$origin/%2\$s/%1\$s/%3\$s" diff --git a/firebase-functions/src/main/java/com/google/firebase/functions/FirebaseFunctionsException.kt b/firebase-functions/src/main/java/com/google/firebase/functions/FirebaseFunctionsException.kt index 87102e0fc7b..06a2818ea1c 100644 --- a/firebase-functions/src/main/java/com/google/firebase/functions/FirebaseFunctionsException.kt +++ b/firebase-functions/src/main/java/com/google/firebase/functions/FirebaseFunctionsException.kt @@ -29,8 +29,8 @@ public class FirebaseFunctionsException : FirebaseException { */ public enum class Code(@Suppress("unused") private val value: Int) { /** - * The operation completed successfully. FirebaseFunctionsException will never have a status of - * OK. + * The operation completed successfully. `FirebaseFunctionsException` will never have a status + * of `OK`. */ OK(0), @@ -41,9 +41,9 @@ public class FirebaseFunctionsException : FirebaseException { UNKNOWN(2), /** - * Client specified an invalid argument. Note that this differs from FAILED_PRECONDITION. - * INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the - * system (e.g., an invalid field name). + * Client specified an invalid argument. Note that this differs from `FAILED_PRECONDITION`. + * `INVALID_ARGUMENT` indicates arguments that are problematic regardless of the state of the + * system (For example, an invalid field name). */ INVALID_ARGUMENT(3), @@ -126,12 +126,12 @@ public class FirebaseFunctionsException : FirebaseException { } /** - * Takes an HTTP status code and returns the corresponding FUNErrorCode error code. This is - * the standard HTTP status code -> error mapping defined in: + * Takes an HTTP status code and returns the corresponding [Code] error code. This is the + * standard HTTP status code -> error mapping defined in: * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto * * @param status An HTTP status code. - * @return The corresponding Code, or Code.UNKNOWN if none. + * @return The corresponding `Code`, or `Code.UNKNOWN` if none. */ @JvmStatic public fun fromHttpStatus(status: Int): Code { @@ -157,7 +157,7 @@ public class FirebaseFunctionsException : FirebaseException { /** * Gets the error code for the operation that failed. * - * @return the code for the FirebaseFunctionsException + * @return the code for the `FirebaseFunctionsException` */ public val code: Code @@ -183,7 +183,7 @@ public class FirebaseFunctionsException : FirebaseException { this.details = details } - public companion object { + internal companion object { /** * Takes an HTTP response and returns the corresponding Exception if any. * @@ -193,7 +193,7 @@ public class FirebaseFunctionsException : FirebaseException { * @return The corresponding Exception, or null if none. */ @JvmStatic - public fun fromResponse( + internal fun fromResponse( code: Code, body: String?, serializer: Serializer diff --git a/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallOptions.kt b/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallOptions.kt index 6e36efffe18..f6b0e3f07c3 100644 --- a/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallOptions.kt +++ b/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallOptions.kt @@ -17,22 +17,22 @@ import java.util.concurrent.TimeUnit import okhttp3.OkHttpClient /** An internal class for keeping track of options applied to an HttpsCallableReference. */ -public class HttpsCallOptions { +internal class HttpsCallOptions { // The timeout to use for calls from references created by this Functions. private var timeout = DEFAULT_TIMEOUT private var timeoutUnits = DEFAULT_TIMEOUT_UNITS @JvmField public val limitedUseAppCheckTokens: Boolean /** Creates an (internal) HttpsCallOptions from the (external) [HttpsCallableOptions]. */ - public constructor(publicCallableOptions: HttpsCallableOptions) { + internal constructor(publicCallableOptions: HttpsCallableOptions) { limitedUseAppCheckTokens = publicCallableOptions.limitedUseAppCheckTokens } - public constructor() { + internal constructor() { limitedUseAppCheckTokens = false } - public fun getLimitedUseAppCheckTokens(): Boolean { + internal fun getLimitedUseAppCheckTokens(): Boolean { return limitedUseAppCheckTokens } @@ -42,7 +42,7 @@ public class HttpsCallOptions { * @param timeout The length of the timeout, in the given units. * @param units The units for the specified timeout. */ - public fun setTimeout(timeout: Long, units: TimeUnit) { + internal fun setTimeout(timeout: Long, units: TimeUnit) { this.timeout = timeout timeoutUnits = units } @@ -52,12 +52,12 @@ public class HttpsCallOptions { * * @return The timeout, in milliseconds. */ - public fun getTimeout(): Long { + internal fun getTimeout(): Long { return timeoutUnits.toMillis(timeout) } /** Creates a new OkHttpClient with these options applied to it. */ - public fun apply(client: OkHttpClient): OkHttpClient { + internal fun apply(client: OkHttpClient): OkHttpClient { return client .newBuilder() .callTimeout(timeout, timeoutUnits) diff --git a/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableOptions.kt b/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableOptions.kt index 32b05afded2..63aa4547e64 100644 --- a/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableOptions.kt +++ b/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableOptions.kt @@ -31,7 +31,7 @@ private constructor( return limitedUseAppCheckTokens } - /** Builder class for [com.google.firebase.functions.HttpsCallableOptions] */ + /** A builder for creating [com.google.firebase.functions.HttpsCallableOptions]. */ public class Builder { @JvmField public var limitedUseAppCheckTokens: Boolean = false diff --git a/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableReference.kt b/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableReference.kt index 90bdb63221b..88db9db4ee4 100644 --- a/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableReference.kt +++ b/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableReference.kt @@ -32,7 +32,7 @@ public class HttpsCallableReference { private val url: URL? // Options for how to do the HTTPS call. - @VisibleForTesting public val options: HttpsCallOptions + @VisibleForTesting internal val options: HttpsCallOptions /** Creates a new reference with the given options. */ internal constructor( @@ -81,7 +81,7 @@ public class HttpsCallableReference { * Auth, an auth token for the user will also be automatically included. * * Firebase Instance ID sends data to the Firebase backend periodically to collect information - * regarding the app instance. To stop this, see [ ] + * regarding the app instance. To stop this, see * [com.google.firebase.iid.FirebaseInstanceId.deleteInstanceId]. It will resume with a new * Instance ID the next time you call this method. * @@ -111,7 +111,7 @@ public class HttpsCallableReference { * Auth, an auth token for the user will also be automatically included. * * Firebase Instance ID sends data to the Firebase backend periodically to collect information - * regarding the app instance. To stop this, see [ ] + * regarding the app instance. To stop this, see * [com.google.firebase.iid.FirebaseInstanceId.deleteInstanceId]. It will resume with a new * Instance ID the next time you call this method. * diff --git a/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableResult.kt b/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableResult.kt index 19fec093d2d..ac4a10638a2 100644 --- a/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableResult.kt +++ b/firebase-functions/src/main/java/com/google/firebase/functions/HttpsCallableResult.kt @@ -13,7 +13,7 @@ // limitations under the License. package com.google.firebase.functions -/** The result of calling a HttpsCallableReference function. */ +/** The result of calling a `HttpsCallableReference` function. */ public class HttpsCallableResult internal constructor( // The actual result data, as generic types decoded from JSON. /** diff --git a/firebase-functions/src/main/java/com/google/firebase/functions/Serializer.kt b/firebase-functions/src/main/java/com/google/firebase/functions/Serializer.kt index 619ae2d75f1..3ab2ebb0756 100644 --- a/firebase-functions/src/main/java/com/google/firebase/functions/Serializer.kt +++ b/firebase-functions/src/main/java/com/google/firebase/functions/Serializer.kt @@ -23,7 +23,7 @@ import org.json.JSONException import org.json.JSONObject /** Converts raw Java types into JSON objects. */ -public class Serializer { +internal class Serializer { private val dateFormat: DateFormat init { @@ -168,11 +168,12 @@ public class Serializer { throw IllegalArgumentException("Object cannot be decoded from JSON: $obj") } - public companion object { + internal companion object { @VisibleForTesting - public const val LONG_TYPE: String = "type.googleapis.com/google.protobuf.Int64Value" + internal const val LONG_TYPE: String = "type.googleapis.com/google.protobuf.Int64Value" @VisibleForTesting - public const val UNSIGNED_LONG_TYPE: String = "type.googleapis.com/google.protobuf.UInt64Value" + internal const val UNSIGNED_LONG_TYPE: String = + "type.googleapis.com/google.protobuf.UInt64Value" } } diff --git a/firebase-inappmessaging-display/api.txt b/firebase-inappmessaging-display/api.txt index 3d413271c9a..4243fd46210 100644 --- a/firebase-inappmessaging-display/api.txt +++ b/firebase-inappmessaging-display/api.txt @@ -1,20 +1,20 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.inappmessaging.display { public class FirebaseInAppMessagingDisplay implements android.app.Application.ActivityLifecycleCallbacks com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplay { - method public void displayMessage(com.google.firebase.inappmessaging.model.InAppMessage, com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks); - method @NonNull public static com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay getInstance(); - method public void onActivityCreated(android.app.Activity, android.os.Bundle); - method public void onActivityDestroyed(android.app.Activity); - method public void onActivityPaused(android.app.Activity); - method public void onActivityResumed(android.app.Activity); - method public void onActivitySaveInstanceState(android.app.Activity, android.os.Bundle); - method public void onActivityStarted(android.app.Activity); - method public void onActivityStopped(android.app.Activity); + method public void displayMessage(com.google.firebase.inappmessaging.model.InAppMessage!, com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks!); + method public static com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay getInstance(); + method public void onActivityCreated(android.app.Activity!, android.os.Bundle!); + method public void onActivityDestroyed(android.app.Activity!); + method public void onActivityPaused(android.app.Activity!); + method public void onActivityResumed(android.app.Activity!); + method public void onActivitySaveInstanceState(android.app.Activity!, android.os.Bundle!); + method public void onActivityStarted(android.app.Activity!); + method public void onActivityStopped(android.app.Activity!); } public final class InAppMessagingDisplayKt { - method @NonNull public static com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay getInAppMessagingDisplay(@NonNull com.google.firebase.Firebase); + method public static com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay getInAppMessagingDisplay(com.google.firebase.Firebase); } } @@ -22,7 +22,7 @@ package com.google.firebase.inappmessaging.display { package com.google.firebase.inappmessaging.display.ktx { public final class InAppMessagingDisplayKt { - method @Deprecated @NonNull public static com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay getInAppMessagingDisplay(@NonNull com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay getInAppMessagingDisplay(com.google.firebase.ktx.Firebase); } } diff --git a/firebase-inappmessaging-display/ktx/api.txt b/firebase-inappmessaging-display/ktx/api.txt index 2357649b88d..da4f6cc18fe 100644 --- a/firebase-inappmessaging-display/ktx/api.txt +++ b/firebase-inappmessaging-display/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.inappmessaging.display.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-inappmessaging/api.txt b/firebase-inappmessaging/api.txt index add30c0c53e..e3c0d64be59 100644 --- a/firebase-inappmessaging/api.txt +++ b/firebase-inappmessaging/api.txt @@ -1,51 +1,51 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.inappmessaging { public class FirebaseInAppMessaging { - method public void addClickListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingClickListener); - method public void addClickListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingClickListener, @NonNull java.util.concurrent.Executor); - method public void addDismissListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDismissListener); - method public void addDismissListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDismissListener, @NonNull java.util.concurrent.Executor); - method public void addDisplayErrorListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayErrorListener); - method public void addDisplayErrorListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayErrorListener, @NonNull java.util.concurrent.Executor); - method public void addImpressionListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingImpressionListener); - method public void addImpressionListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingImpressionListener, @NonNull java.util.concurrent.Executor); + method public void addClickListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingClickListener); + method public void addClickListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingClickListener, java.util.concurrent.Executor); + method public void addDismissListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingDismissListener); + method public void addDismissListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingDismissListener, java.util.concurrent.Executor); + method public void addDisplayErrorListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayErrorListener); + method public void addDisplayErrorListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayErrorListener, java.util.concurrent.Executor); + method public void addImpressionListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingImpressionListener); + method public void addImpressionListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingImpressionListener, java.util.concurrent.Executor); method public boolean areMessagesSuppressed(); - method @NonNull public static com.google.firebase.inappmessaging.FirebaseInAppMessaging getInstance(); + method public static com.google.firebase.inappmessaging.FirebaseInAppMessaging getInstance(); method public boolean isAutomaticDataCollectionEnabled(); - method public void removeClickListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingClickListener); - method public void removeDismissListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDismissListener); - method public void removeDisplayErrorListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayErrorListener); - method public void removeImpressionListener(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingImpressionListener); - method public void setAutomaticDataCollectionEnabled(@Nullable Boolean); + method public void removeClickListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingClickListener); + method public void removeDismissListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingDismissListener); + method public void removeDisplayErrorListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayErrorListener); + method public void removeImpressionListener(com.google.firebase.inappmessaging.FirebaseInAppMessagingImpressionListener); method public void setAutomaticDataCollectionEnabled(boolean); - method public void setMessageDisplayComponent(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplay); - method public void setMessagesSuppressed(@NonNull Boolean); - method public void triggerEvent(@NonNull String); + method public void setAutomaticDataCollectionEnabled(Boolean?); + method public void setMessageDisplayComponent(com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplay); + method public void setMessagesSuppressed(Boolean); + method public void triggerEvent(String); } public interface FirebaseInAppMessagingClickListener { - method public void messageClicked(@NonNull com.google.firebase.inappmessaging.model.InAppMessage, @NonNull com.google.firebase.inappmessaging.model.Action); + method public void messageClicked(com.google.firebase.inappmessaging.model.InAppMessage, com.google.firebase.inappmessaging.model.Action); } public class FirebaseInAppMessagingContextualTrigger { - ctor public FirebaseInAppMessagingContextualTrigger(@NonNull String); - method @NonNull public String getTriggerName(); + ctor public FirebaseInAppMessagingContextualTrigger(String); + method public String getTriggerName(); } public interface FirebaseInAppMessagingDismissListener { - method public void messageDismissed(@NonNull com.google.firebase.inappmessaging.model.InAppMessage); + method public void messageDismissed(com.google.firebase.inappmessaging.model.InAppMessage); } @Keep public interface FirebaseInAppMessagingDisplay { - method @Keep public void displayMessage(@NonNull com.google.firebase.inappmessaging.model.InAppMessage, @NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks); + method @Keep public void displayMessage(com.google.firebase.inappmessaging.model.InAppMessage, com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks); } public interface FirebaseInAppMessagingDisplayCallbacks { - method @NonNull public com.google.android.gms.tasks.Task displayErrorEncountered(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks.InAppMessagingErrorReason); - method @NonNull public com.google.android.gms.tasks.Task impressionDetected(); - method @NonNull public com.google.android.gms.tasks.Task messageClicked(@NonNull com.google.firebase.inappmessaging.model.Action); - method @NonNull public com.google.android.gms.tasks.Task messageDismissed(@NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks.InAppMessagingDismissType); + method public com.google.android.gms.tasks.Task displayErrorEncountered(com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks.InAppMessagingErrorReason); + method public com.google.android.gms.tasks.Task impressionDetected(); + method public com.google.android.gms.tasks.Task messageClicked(com.google.firebase.inappmessaging.model.Action); + method public com.google.android.gms.tasks.Task messageDismissed(com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks.InAppMessagingDismissType); } public enum FirebaseInAppMessagingDisplayCallbacks.InAppMessagingDismissType { @@ -63,15 +63,15 @@ package com.google.firebase.inappmessaging { } public interface FirebaseInAppMessagingDisplayErrorListener { - method public void displayErrorEncountered(@NonNull com.google.firebase.inappmessaging.model.InAppMessage, @NonNull com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks.InAppMessagingErrorReason); + method public void displayErrorEncountered(com.google.firebase.inappmessaging.model.InAppMessage, com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayCallbacks.InAppMessagingErrorReason); } public interface FirebaseInAppMessagingImpressionListener { - method public void impressionDetected(@NonNull com.google.firebase.inappmessaging.model.InAppMessage); + method public void impressionDetected(com.google.firebase.inappmessaging.model.InAppMessage); } public final class InAppMessagingKt { - method @NonNull public static com.google.firebase.inappmessaging.FirebaseInAppMessaging getInAppMessaging(@NonNull com.google.firebase.Firebase); + method public static com.google.firebase.inappmessaging.FirebaseInAppMessaging getInAppMessaging(com.google.firebase.Firebase); } } @@ -79,7 +79,7 @@ package com.google.firebase.inappmessaging { package com.google.firebase.inappmessaging.ktx { public final class InAppMessagingKt { - method @Deprecated @NonNull public static com.google.firebase.inappmessaging.FirebaseInAppMessaging getInAppMessaging(@NonNull com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.inappmessaging.FirebaseInAppMessaging getInAppMessaging(com.google.firebase.ktx.Firebase); } } @@ -87,64 +87,64 @@ package com.google.firebase.inappmessaging.ktx { package com.google.firebase.inappmessaging.model { public class Action { - method @Nullable public String getActionUrl(); - method @Nullable public com.google.firebase.inappmessaging.model.Button getButton(); + method public String? getActionUrl(); + method public com.google.firebase.inappmessaging.model.Button? getButton(); } public class BannerMessage extends com.google.firebase.inappmessaging.model.InAppMessage { - method @Nullable public com.google.firebase.inappmessaging.model.Action getAction(); - method @NonNull public String getBackgroundHexColor(); - method @Nullable public com.google.firebase.inappmessaging.model.Text getBody(); - method @Nullable public com.google.firebase.inappmessaging.model.ImageData getImageData(); - method @NonNull public com.google.firebase.inappmessaging.model.Text getTitle(); + method public com.google.firebase.inappmessaging.model.Action? getAction(); + method public String getBackgroundHexColor(); + method public com.google.firebase.inappmessaging.model.Text? getBody(); + method public com.google.firebase.inappmessaging.model.ImageData? getImageData(); + method public com.google.firebase.inappmessaging.model.Text getTitle(); } public class Button { - method @NonNull public String getButtonHexColor(); - method @NonNull public com.google.firebase.inappmessaging.model.Text getText(); + method public String getButtonHexColor(); + method public com.google.firebase.inappmessaging.model.Text getText(); } public class CampaignMetadata { - method @NonNull public String getCampaignId(); - method @NonNull public String getCampaignName(); + method public String getCampaignId(); + method public String getCampaignName(); method public boolean getIsTestMessage(); } public class CardMessage extends com.google.firebase.inappmessaging.model.InAppMessage { - method @Deprecated @Nullable public com.google.firebase.inappmessaging.model.Action getAction(); - method @NonNull public String getBackgroundHexColor(); - method @Nullable public com.google.firebase.inappmessaging.model.Text getBody(); - method @Nullable public com.google.firebase.inappmessaging.model.ImageData getLandscapeImageData(); - method @Nullable public com.google.firebase.inappmessaging.model.ImageData getPortraitImageData(); - method @NonNull public com.google.firebase.inappmessaging.model.Action getPrimaryAction(); - method @Nullable public com.google.firebase.inappmessaging.model.Action getSecondaryAction(); - method @NonNull public com.google.firebase.inappmessaging.model.Text getTitle(); + method @Deprecated public com.google.firebase.inappmessaging.model.Action? getAction(); + method public String getBackgroundHexColor(); + method public com.google.firebase.inappmessaging.model.Text? getBody(); + method public com.google.firebase.inappmessaging.model.ImageData? getLandscapeImageData(); + method public com.google.firebase.inappmessaging.model.ImageData? getPortraitImageData(); + method public com.google.firebase.inappmessaging.model.Action getPrimaryAction(); + method public com.google.firebase.inappmessaging.model.Action? getSecondaryAction(); + method public com.google.firebase.inappmessaging.model.Text getTitle(); } public class ImageData { - method @Nullable public android.graphics.Bitmap getBitmapData(); - method @NonNull public String getImageUrl(); + method public android.graphics.Bitmap? getBitmapData(); + method public String getImageUrl(); } public class ImageOnlyMessage extends com.google.firebase.inappmessaging.model.InAppMessage { - method @Nullable public com.google.firebase.inappmessaging.model.Action getAction(); - method @NonNull public com.google.firebase.inappmessaging.model.ImageData getImageData(); + method public com.google.firebase.inappmessaging.model.Action? getAction(); + method public com.google.firebase.inappmessaging.model.ImageData getImageData(); } public abstract class InAppMessage { - method @Deprecated @Nullable public abstract com.google.firebase.inappmessaging.model.Action getAction(); - method @Deprecated @Nullable public com.google.firebase.inappmessaging.model.Button getActionButton(); - method @Deprecated @Nullable public String getBackgroundHexColor(); - method @Deprecated @Nullable public com.google.firebase.inappmessaging.model.Text getBody(); - method @Deprecated @Nullable public String getCampaignId(); - method @Nullable public com.google.firebase.inappmessaging.model.CampaignMetadata getCampaignMetadata(); - method @Deprecated @Nullable public String getCampaignName(); - method @Nullable public java.util.Map getData(); - method @Deprecated @Nullable public com.google.firebase.inappmessaging.model.ImageData getImageData(); - method @Deprecated @Nullable public String getImageUrl(); - method @Deprecated @Nullable public Boolean getIsTestMessage(); - method @Nullable public com.google.firebase.inappmessaging.model.MessageType getMessageType(); - method @Deprecated @Nullable public com.google.firebase.inappmessaging.model.Text getTitle(); + method @Deprecated public abstract com.google.firebase.inappmessaging.model.Action? getAction(); + method @Deprecated public com.google.firebase.inappmessaging.model.Button? getActionButton(); + method @Deprecated public String? getBackgroundHexColor(); + method @Deprecated public com.google.firebase.inappmessaging.model.Text? getBody(); + method @Deprecated public String? getCampaignId(); + method public com.google.firebase.inappmessaging.model.CampaignMetadata? getCampaignMetadata(); + method @Deprecated public String? getCampaignName(); + method public java.util.Map? getData(); + method @Deprecated public com.google.firebase.inappmessaging.model.ImageData? getImageData(); + method @Deprecated public String? getImageUrl(); + method @Deprecated public Boolean? getIsTestMessage(); + method public com.google.firebase.inappmessaging.model.MessageType? getMessageType(); + method @Deprecated public com.google.firebase.inappmessaging.model.Text? getTitle(); } @Keep public enum MessageType { @@ -156,16 +156,16 @@ package com.google.firebase.inappmessaging.model { } public class ModalMessage extends com.google.firebase.inappmessaging.model.InAppMessage { - method @Nullable public com.google.firebase.inappmessaging.model.Action getAction(); - method @NonNull public String getBackgroundHexColor(); - method @Nullable public com.google.firebase.inappmessaging.model.Text getBody(); - method @Nullable public com.google.firebase.inappmessaging.model.ImageData getImageData(); - method @NonNull public com.google.firebase.inappmessaging.model.Text getTitle(); + method public com.google.firebase.inappmessaging.model.Action? getAction(); + method public String getBackgroundHexColor(); + method public com.google.firebase.inappmessaging.model.Text? getBody(); + method public com.google.firebase.inappmessaging.model.ImageData? getImageData(); + method public com.google.firebase.inappmessaging.model.Text getTitle(); } public class Text { - method @NonNull public String getHexColor(); - method @Nullable public String getText(); + method public String getHexColor(); + method public String? getText(); } } diff --git a/firebase-inappmessaging/ktx/api.txt b/firebase-inappmessaging/ktx/api.txt index 57a96ed4b0a..da4f6cc18fe 100644 --- a/firebase-inappmessaging/ktx/api.txt +++ b/firebase-inappmessaging/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.inappmessaging.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-installations-interop/api.txt b/firebase-installations-interop/api.txt index 82087808935..9a7b4526e90 100644 --- a/firebase-installations-interop/api.txt +++ b/firebase-installations-interop/api.txt @@ -1,10 +1,10 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.installations { @com.google.auto.value.AutoValue public abstract class InstallationTokenResult { ctor public InstallationTokenResult(); - method @NonNull public abstract String getToken(); - method @NonNull public abstract long getTokenExpirationTimestamp(); + method public abstract String getToken(); + method public abstract long getTokenExpirationTimestamp(); } } diff --git a/firebase-installations/api.txt b/firebase-installations/api.txt index 9e99e69d965..71de29c094f 100644 --- a/firebase-installations/api.txt +++ b/firebase-installations/api.txt @@ -1,18 +1,18 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.installations { public class FirebaseInstallations implements com.google.firebase.installations.FirebaseInstallationsApi { - method @NonNull public com.google.android.gms.tasks.Task delete(); - method @NonNull public com.google.android.gms.tasks.Task getId(); - method @NonNull public static com.google.firebase.installations.FirebaseInstallations getInstance(); - method @NonNull public static com.google.firebase.installations.FirebaseInstallations getInstance(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public com.google.android.gms.tasks.Task getToken(boolean); - method @NonNull public com.google.firebase.installations.internal.FidListenerHandle registerFidListener(@NonNull com.google.firebase.installations.internal.FidListener); + method public com.google.android.gms.tasks.Task delete(); + method public com.google.android.gms.tasks.Task getId(); + method public static com.google.firebase.installations.FirebaseInstallations getInstance(); + method public static com.google.firebase.installations.FirebaseInstallations getInstance(com.google.firebase.FirebaseApp); + method public com.google.android.gms.tasks.Task getToken(boolean); + method public com.google.firebase.installations.internal.FidListenerHandle registerFidListener(com.google.firebase.installations.internal.FidListener); } public final class InstallationsKt { - method @NonNull public static com.google.firebase.installations.FirebaseInstallations getInstallations(@NonNull com.google.firebase.Firebase); - method @NonNull public static com.google.firebase.installations.FirebaseInstallations installations(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); + method public static com.google.firebase.installations.FirebaseInstallations getInstallations(com.google.firebase.Firebase); + method public static com.google.firebase.installations.FirebaseInstallations installations(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); } } @@ -20,8 +20,8 @@ package com.google.firebase.installations { package com.google.firebase.installations.ktx { public final class InstallationsKt { - method @Deprecated @NonNull public static com.google.firebase.installations.FirebaseInstallations getInstallations(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @NonNull public static com.google.firebase.installations.FirebaseInstallations installations(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); + method @Deprecated public static com.google.firebase.installations.FirebaseInstallations getInstallations(com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.installations.FirebaseInstallations installations(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); } } diff --git a/firebase-installations/ktx/api.txt b/firebase-installations/ktx/api.txt index 5e4b3374897..da4f6cc18fe 100644 --- a/firebase-installations/ktx/api.txt +++ b/firebase-installations/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.installations.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-messaging-directboot/api.txt b/firebase-messaging-directboot/api.txt index d802177e249..da4f6cc18fe 100644 --- a/firebase-messaging-directboot/api.txt +++ b/firebase-messaging-directboot/api.txt @@ -1 +1 @@ -// Signature format: 2.0 +// Signature format: 3.0 diff --git a/firebase-messaging/api.txt b/firebase-messaging/api.txt index bf42f6119ab..1cb7cd1e4c9 100644 --- a/firebase-messaging/api.txt +++ b/firebase-messaging/api.txt @@ -1,99 +1,99 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.messaging { public class FirebaseMessaging { - method @NonNull public com.google.android.gms.tasks.Task deleteToken(); - method @NonNull public boolean deliveryMetricsExportToBigQueryEnabled(); - method @NonNull public static com.google.firebase.messaging.FirebaseMessaging getInstance(); - method @NonNull public com.google.android.gms.tasks.Task getToken(); + method public com.google.android.gms.tasks.Task deleteToken(); + method public boolean deliveryMetricsExportToBigQueryEnabled(); + method public static com.google.firebase.messaging.FirebaseMessaging getInstance(); + method public com.google.android.gms.tasks.Task getToken(); method public boolean isAutoInitEnabled(); method public boolean isNotificationDelegationEnabled(); - method @Deprecated public void send(@NonNull com.google.firebase.messaging.RemoteMessage); + method @Deprecated public void send(com.google.firebase.messaging.RemoteMessage); method public void setAutoInitEnabled(boolean); method public void setDeliveryMetricsExportToBigQuery(boolean); - method @NonNull public com.google.android.gms.tasks.Task setNotificationDelegationEnabled(boolean); - method @NonNull public com.google.android.gms.tasks.Task subscribeToTopic(@NonNull String); - method @NonNull public com.google.android.gms.tasks.Task unsubscribeFromTopic(@NonNull String); + method public com.google.android.gms.tasks.Task setNotificationDelegationEnabled(boolean); + method public com.google.android.gms.tasks.Task subscribeToTopic(String); + method public com.google.android.gms.tasks.Task unsubscribeFromTopic(String); field @Deprecated public static final String INSTANCE_ID_SCOPE = "FCM"; } public class FirebaseMessagingService extends android.app.Service { ctor public FirebaseMessagingService(); - method public final android.os.IBinder onBind(android.content.Intent); + method public final android.os.IBinder! onBind(android.content.Intent!); method @WorkerThread public void onDeletedMessages(); - method @WorkerThread public void onMessageReceived(@NonNull com.google.firebase.messaging.RemoteMessage); - method @WorkerThread public void onMessageSent(@NonNull String); - method @WorkerThread public void onNewToken(@NonNull String); - method @WorkerThread public void onSendError(@NonNull String, @NonNull Exception); - method public final int onStartCommand(android.content.Intent, int, int); + method @WorkerThread public void onMessageReceived(com.google.firebase.messaging.RemoteMessage); + method @Deprecated @WorkerThread public void onMessageSent(String); + method @WorkerThread public void onNewToken(String); + method @Deprecated @WorkerThread public void onSendError(String, Exception); + method public final int onStartCommand(android.content.Intent!, int, int); } public final class MessagingKt { - method @NonNull public static com.google.firebase.messaging.FirebaseMessaging getMessaging(@NonNull com.google.firebase.Firebase); - method @NonNull public static inline com.google.firebase.messaging.RemoteMessage remoteMessage(@NonNull String to, @NonNull kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.messaging.FirebaseMessaging getMessaging(com.google.firebase.Firebase); + method public static inline com.google.firebase.messaging.RemoteMessage remoteMessage(String to, kotlin.jvm.functions.Function1 init); } @com.google.android.gms.common.internal.safeparcel.SafeParcelable.Class(creator="RemoteMessageCreator") @com.google.android.gms.common.internal.safeparcel.SafeParcelable.Reserved({1}) public final class RemoteMessage extends com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable { - method @Nullable public String getCollapseKey(); - method @NonNull public java.util.Map getData(); - method @Nullable public String getFrom(); - method @Nullable public String getMessageId(); - method @Nullable public String getMessageType(); - method @Nullable public com.google.firebase.messaging.RemoteMessage.Notification getNotification(); + method public String? getCollapseKey(); + method public java.util.Map getData(); + method public String? getFrom(); + method public String? getMessageId(); + method public String? getMessageType(); + method public com.google.firebase.messaging.RemoteMessage.Notification? getNotification(); method @com.google.firebase.messaging.RemoteMessage.MessagePriority public int getOriginalPriority(); method @com.google.firebase.messaging.RemoteMessage.MessagePriority public int getPriority(); - method @Nullable public String getSenderId(); + method public String? getSenderId(); method public long getSentTime(); - method @Nullable public String getTo(); + method @Deprecated public String? getTo(); method public int getTtl(); - method public void writeToParcel(@NonNull android.os.Parcel, int); + method public void writeToParcel(android.os.Parcel, int); field public static final int PRIORITY_HIGH = 1; // 0x1 field public static final int PRIORITY_NORMAL = 2; // 0x2 field public static final int PRIORITY_UNKNOWN = 0; // 0x0 } public static class RemoteMessage.Builder { - ctor public RemoteMessage.Builder(@NonNull String); - method @NonNull public com.google.firebase.messaging.RemoteMessage.Builder addData(@NonNull String, @Nullable String); - method @NonNull public com.google.firebase.messaging.RemoteMessage build(); - method @NonNull public com.google.firebase.messaging.RemoteMessage.Builder clearData(); - method @NonNull public com.google.firebase.messaging.RemoteMessage.Builder setCollapseKey(@Nullable String); - method @NonNull public com.google.firebase.messaging.RemoteMessage.Builder setData(@NonNull java.util.Map); - method @NonNull public com.google.firebase.messaging.RemoteMessage.Builder setMessageId(@NonNull String); - method @NonNull public com.google.firebase.messaging.RemoteMessage.Builder setMessageType(@Nullable String); - method @NonNull public com.google.firebase.messaging.RemoteMessage.Builder setTtl(@IntRange(from=0, to=86400) int); + ctor public RemoteMessage.Builder(String); + method public com.google.firebase.messaging.RemoteMessage.Builder addData(String, String?); + method public com.google.firebase.messaging.RemoteMessage build(); + method public com.google.firebase.messaging.RemoteMessage.Builder clearData(); + method public com.google.firebase.messaging.RemoteMessage.Builder setCollapseKey(String?); + method public com.google.firebase.messaging.RemoteMessage.Builder setData(java.util.Map); + method public com.google.firebase.messaging.RemoteMessage.Builder setMessageId(String); + method public com.google.firebase.messaging.RemoteMessage.Builder setMessageType(String?); + method public com.google.firebase.messaging.RemoteMessage.Builder setTtl(@IntRange(from=0, to=86400) int); } @IntDef({com.google.firebase.messaging.RemoteMessage.PRIORITY_UNKNOWN, com.google.firebase.messaging.RemoteMessage.PRIORITY_HIGH, com.google.firebase.messaging.RemoteMessage.PRIORITY_NORMAL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RemoteMessage.MessagePriority { } public static class RemoteMessage.Notification { - method @Nullable public String getBody(); - method @Nullable public String[] getBodyLocalizationArgs(); - method @Nullable public String getBodyLocalizationKey(); - method @Nullable public String getChannelId(); - method @Nullable public String getClickAction(); - method @Nullable public String getColor(); + method public String? getBody(); + method public String![]? getBodyLocalizationArgs(); + method public String? getBodyLocalizationKey(); + method public String? getChannelId(); + method public String? getClickAction(); + method public String? getColor(); method public boolean getDefaultLightSettings(); method public boolean getDefaultSound(); method public boolean getDefaultVibrateSettings(); - method @Nullable public Long getEventTime(); - method @Nullable public String getIcon(); - method @Nullable public android.net.Uri getImageUrl(); - method @Nullable public int[] getLightSettings(); - method @Nullable public android.net.Uri getLink(); + method public Long? getEventTime(); + method public String? getIcon(); + method public android.net.Uri? getImageUrl(); + method public int[]? getLightSettings(); + method public android.net.Uri? getLink(); method public boolean getLocalOnly(); - method @Nullable public Integer getNotificationCount(); - method @Nullable public Integer getNotificationPriority(); - method @Nullable public String getSound(); + method public Integer? getNotificationCount(); + method public Integer? getNotificationPriority(); + method public String? getSound(); method public boolean getSticky(); - method @Nullable public String getTag(); - method @Nullable public String getTicker(); - method @Nullable public String getTitle(); - method @Nullable public String[] getTitleLocalizationArgs(); - method @Nullable public String getTitleLocalizationKey(); - method @Nullable public long[] getVibrateTimings(); - method @Nullable public Integer getVisibility(); + method public String? getTag(); + method public String? getTicker(); + method public String? getTitle(); + method public String![]? getTitleLocalizationArgs(); + method public String? getTitleLocalizationKey(); + method public long[]? getVibrateTimings(); + method public Integer? getVisibility(); } public final class SendException extends java.lang.Exception { @@ -110,8 +110,8 @@ package com.google.firebase.messaging { package com.google.firebase.messaging.ktx { public final class MessagingKt { - method @Deprecated @NonNull public static com.google.firebase.messaging.FirebaseMessaging getMessaging(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @NonNull public static inline com.google.firebase.messaging.RemoteMessage remoteMessage(@NonNull String to, @NonNull kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.firebase.messaging.FirebaseMessaging getMessaging(com.google.firebase.ktx.Firebase); + method @Deprecated public static inline com.google.firebase.messaging.RemoteMessage remoteMessage(String to, kotlin.jvm.functions.Function1 init); } } diff --git a/firebase-messaging/ktx/api.txt b/firebase-messaging/ktx/api.txt index c0bdbc228e9..da4f6cc18fe 100644 --- a/firebase-messaging/ktx/api.txt +++ b/firebase-messaging/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.messaging.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-ml-modeldownloader/api.txt b/firebase-ml-modeldownloader/api.txt index 5d4d7395a5f..647ca614142 100644 --- a/firebase-ml-modeldownloader/api.txt +++ b/firebase-ml-modeldownloader/api.txt @@ -1,11 +1,11 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.ml.modeldownloader { public class CustomModel { method public long getDownloadId(); - method @Nullable public java.io.File getFile(); - method @NonNull public String getModelHash(); - method @NonNull public String getName(); + method public java.io.File? getFile(); + method public String getModelHash(); + method public String getName(); method public long getSize(); } @@ -17,10 +17,10 @@ package com.google.firebase.ml.modeldownloader { public static class CustomModelDownloadConditions.Builder { ctor public CustomModelDownloadConditions.Builder(); - method @NonNull public com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions build(); - method @NonNull @RequiresApi(android.os.Build.VERSION_CODES.N) public com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions.Builder requireCharging(); - method @NonNull @RequiresApi(android.os.Build.VERSION_CODES.N) public com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions.Builder requireDeviceIdle(); - method @NonNull public com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions.Builder requireWifi(); + method public com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions build(); + method @RequiresApi(android.os.Build.VERSION_CODES.N) public com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions.Builder requireCharging(); + method @RequiresApi(android.os.Build.VERSION_CODES.N) public com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions.Builder requireDeviceIdle(); + method public com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions.Builder requireWifi(); } public enum DownloadType { @@ -56,24 +56,24 @@ package com.google.firebase.ml.modeldownloader { } public class FirebaseModelDownloader { - method @NonNull public com.google.android.gms.tasks.Task deleteDownloadedModel(@NonNull String); - method @NonNull public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getInstance(); - method @NonNull public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getInstance(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public com.google.android.gms.tasks.Task getModel(@NonNull String, @NonNull com.google.firebase.ml.modeldownloader.DownloadType, @Nullable com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions); - method @NonNull public com.google.android.gms.tasks.Task getModelDownloadId(@NonNull String, @Nullable com.google.android.gms.tasks.Task); - method @NonNull public com.google.android.gms.tasks.Task> listDownloadedModels(); - method public void setModelDownloaderCollectionEnabled(@Nullable Boolean); + method public com.google.android.gms.tasks.Task deleteDownloadedModel(String); + method public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getInstance(); + method public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getInstance(com.google.firebase.FirebaseApp); + method public com.google.android.gms.tasks.Task getModel(String, com.google.firebase.ml.modeldownloader.DownloadType, com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions?); + method public com.google.android.gms.tasks.Task getModelDownloadId(String, com.google.android.gms.tasks.Task?); + method public com.google.android.gms.tasks.Task!> listDownloadedModels(); + method public void setModelDownloaderCollectionEnabled(Boolean?); } public final class ModelDownloaderKt { - method @Nullable public static operator java.io.File component1(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method public static operator long component2(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method public static operator long component3(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method @NonNull public static operator String component4(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method @NonNull public static operator String component5(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method @NonNull public static com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions customModelDownloadConditions(@NonNull kotlin.jvm.functions.Function1 init); - method @NonNull public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getModelDownloader(@NonNull com.google.firebase.Firebase); - method @NonNull public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader modelDownloader(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); + method public static operator java.io.File? component1(com.google.firebase.ml.modeldownloader.CustomModel); + method public static operator long component2(com.google.firebase.ml.modeldownloader.CustomModel); + method public static operator long component3(com.google.firebase.ml.modeldownloader.CustomModel); + method public static operator String component4(com.google.firebase.ml.modeldownloader.CustomModel); + method public static operator String component5(com.google.firebase.ml.modeldownloader.CustomModel); + method public static com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions customModelDownloadConditions(kotlin.jvm.functions.Function1 init); + method public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getModelDownloader(com.google.firebase.Firebase); + method public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader modelDownloader(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); } } @@ -81,14 +81,14 @@ package com.google.firebase.ml.modeldownloader { package com.google.firebase.ml.modeldownloader.ktx { public final class ModelDownloaderKt { - method @Nullable public static operator java.io.File component1(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method public static operator long component2(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method public static operator long component3(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method @NonNull public static operator String component4(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method @NonNull public static operator String component5(@NonNull com.google.firebase.ml.modeldownloader.CustomModel); - method @Deprecated @NonNull public static com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions customModelDownloadConditions(@NonNull kotlin.jvm.functions.Function1 init); - method @Deprecated @NonNull public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getModelDownloader(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @NonNull public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader modelDownloader(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); + method public static operator java.io.File? component1(com.google.firebase.ml.modeldownloader.CustomModel); + method public static operator long component2(com.google.firebase.ml.modeldownloader.CustomModel); + method public static operator long component3(com.google.firebase.ml.modeldownloader.CustomModel); + method public static operator String component4(com.google.firebase.ml.modeldownloader.CustomModel); + method public static operator String component5(com.google.firebase.ml.modeldownloader.CustomModel); + method @Deprecated public static com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions customModelDownloadConditions(kotlin.jvm.functions.Function1 init); + method @Deprecated public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getModelDownloader(com.google.firebase.ktx.Firebase); + method @Deprecated public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader modelDownloader(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); } } diff --git a/firebase-ml-modeldownloader/ktx/api.txt b/firebase-ml-modeldownloader/ktx/api.txt index 97f07ed6300..da4f6cc18fe 100644 --- a/firebase-ml-modeldownloader/ktx/api.txt +++ b/firebase-ml-modeldownloader/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.ml.modeldownloader.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-perf/CHANGELOG.md b/firebase-perf/CHANGELOG.md index 3486a9000f6..2112244a524 100644 --- a/firebase-perf/CHANGELOG.md +++ b/firebase-perf/CHANGELOG.md @@ -1,6 +1,16 @@ # Unreleased +# 21.0.4 +* [fixed] Fixed a performance issue with shared preferences + calling `.apply()` every time a value is read from remote config (#6407) + + +## Kotlin +The Kotlin extensions library transitively includes the updated +`firebase-performance` library. The Kotlin extensions library has no additional +updates. + # 21.0.3 * [changed] Bump internal dependencies. diff --git a/firebase-perf/api.txt b/firebase-perf/api.txt index 27e8478d5af..2352c0de69f 100644 --- a/firebase-perf/api.txt +++ b/firebase-perf/api.txt @@ -1,14 +1,14 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.perf { @javax.inject.Singleton public class FirebasePerformance { - method @NonNull public static com.google.firebase.perf.FirebasePerformance getInstance(); + method public static com.google.firebase.perf.FirebasePerformance getInstance(); method public boolean isPerformanceCollectionEnabled(); - method @NonNull public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(@NonNull String, @NonNull @com.google.firebase.perf.FirebasePerformance.HttpMethod String); - method @NonNull public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(@NonNull java.net.URL, @NonNull @com.google.firebase.perf.FirebasePerformance.HttpMethod String); - method @NonNull public com.google.firebase.perf.metrics.Trace newTrace(@NonNull String); + method public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(String, @com.google.firebase.perf.FirebasePerformance.HttpMethod String); + method public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(java.net.URL, @com.google.firebase.perf.FirebasePerformance.HttpMethod String); + method public com.google.firebase.perf.metrics.Trace newTrace(String); method public void setPerformanceCollectionEnabled(boolean); - method @NonNull public static com.google.firebase.perf.metrics.Trace startTrace(@NonNull String); + method public static com.google.firebase.perf.metrics.Trace startTrace(String); field public static final int MAX_ATTRIBUTE_KEY_LENGTH = 40; // 0x28 field public static final int MAX_ATTRIBUTE_VALUE_LENGTH = 100; // 0x64 field public static final int MAX_TRACE_CUSTOM_ATTRIBUTES = 5; // 0x5 @@ -28,10 +28,10 @@ package com.google.firebase.perf { } public final class PerformanceKt { - method @NonNull public static com.google.firebase.perf.FirebasePerformance getPerformance(@NonNull com.google.firebase.Firebase); - method public static inline void trace(@NonNull com.google.firebase.perf.metrics.HttpMetric, @NonNull kotlin.jvm.functions.Function1 block); - method public static inline T trace(@NonNull com.google.firebase.perf.metrics.Trace, @NonNull kotlin.jvm.functions.Function1 block); - method public static inline T trace(@NonNull String name, @NonNull kotlin.jvm.functions.Function1 block); + method public static com.google.firebase.perf.FirebasePerformance getPerformance(com.google.firebase.Firebase); + method public static inline void trace(com.google.firebase.perf.metrics.HttpMetric, kotlin.jvm.functions.Function1 block); + method public static inline T trace(com.google.firebase.perf.metrics.Trace, kotlin.jvm.functions.Function1 block); + method public static inline T trace(String name, kotlin.jvm.functions.Function1 block); } } @@ -39,10 +39,10 @@ package com.google.firebase.perf { package com.google.firebase.perf.ktx { public final class PerformanceKt { - method @Deprecated @NonNull public static com.google.firebase.perf.FirebasePerformance getPerformance(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated public static inline void trace(@NonNull com.google.firebase.perf.metrics.HttpMetric, @NonNull kotlin.jvm.functions.Function1 block); - method @Deprecated public static inline T trace(@NonNull com.google.firebase.perf.metrics.Trace, @NonNull kotlin.jvm.functions.Function1 block); - method @Deprecated public static inline T trace(@NonNull String name, @NonNull kotlin.jvm.functions.Function1 block); + method @Deprecated public static com.google.firebase.perf.FirebasePerformance getPerformance(com.google.firebase.ktx.Firebase); + method @Deprecated public static inline void trace(com.google.firebase.perf.metrics.HttpMetric, kotlin.jvm.functions.Function1 block); + method @Deprecated public static inline T trace(com.google.firebase.perf.metrics.Trace, kotlin.jvm.functions.Function1 block); + method @Deprecated public static inline T trace(String name, kotlin.jvm.functions.Function1 block); } } @@ -55,13 +55,13 @@ package com.google.firebase.perf.metrics { } public class HttpMetric { - method @Nullable public String getAttribute(@NonNull String); - method @NonNull public java.util.Map getAttributes(); - method public void putAttribute(@NonNull String, @NonNull String); - method public void removeAttribute(@NonNull String); + method public String? getAttribute(String); + method public java.util.Map getAttributes(); + method public void putAttribute(String, String); + method public void removeAttribute(String); method public void setHttpResponseCode(int); method public void setRequestPayloadSize(long); - method public void setResponseContentType(@Nullable String); + method public void setResponseContentType(String?); method public void setResponsePayloadSize(long); method public void start(); method public void stop(); @@ -73,17 +73,17 @@ package com.google.firebase.perf.metrics { public class Trace implements android.os.Parcelable { method @Keep public int describeContents(); - method @Keep @Nullable public String getAttribute(@NonNull String); - method @Keep @NonNull public java.util.Map getAttributes(); - method @Keep public long getLongMetric(@NonNull String); - method @Keep public void incrementMetric(@NonNull String, long); - method @Keep public void putAttribute(@NonNull String, @NonNull String); - method @Keep public void putMetric(@NonNull String, long); - method @Keep public void removeAttribute(@NonNull String); + method @Keep public String? getAttribute(String); + method @Keep public java.util.Map getAttributes(); + method @Keep public long getLongMetric(String); + method @Keep public void incrementMetric(String, long); + method @Keep public void putAttribute(String, String); + method @Keep public void putMetric(String, long); + method @Keep public void removeAttribute(String); method @Keep public void start(); method @Keep public void stop(); - method @Keep public void writeToParcel(@NonNull android.os.Parcel, int); - field @Keep public static final android.os.Parcelable.Creator CREATOR; + method @Keep public void writeToParcel(android.os.Parcel, int); + field @Keep public static final android.os.Parcelable.Creator! CREATOR; field public static final int MAX_ATTRIBUTE_KEY_LENGTH = 40; // 0x28 field public static final int MAX_ATTRIBUTE_VALUE_LENGTH = 100; // 0x64 field public static final int MAX_TRACE_CUSTOM_ATTRIBUTES = 5; // 0x5 diff --git a/firebase-perf/dev-app/README.md b/firebase-perf/dev-app/README.md index c79c43fcbcb..489f574d29f 100644 --- a/firebase-perf/dev-app/README.md +++ b/firebase-perf/dev-app/README.md @@ -83,7 +83,7 @@ firebase-android-sdk$ ./gradlew :firebase-perf:dev-app:devicecheck There are differences in terms of Firebase projects when running this command in different scenarios. 1. **CI Run**: These tests are run under Firebase Test Lab of the unified Firebase project -(according to [this](https://github.com/firebase/firebase-android-sdk/blob/main/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java)) +(according to [this](https://github.com/firebase/firebase-android-sdk/blob/main/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java)) but the performance events are sent to a different project with which apps are configured with (see `copyRootGoogleServices` task). diff --git a/firebase-perf/e2e-app/README.md b/firebase-perf/e2e-app/README.md index 6554f58e360..dc95fd52b70 100644 --- a/firebase-perf/e2e-app/README.md +++ b/firebase-perf/e2e-app/README.md @@ -75,7 +75,7 @@ firebase-android-sdk$ ./gradlew :firebase-perf:e2e-app:devicecheck There are differences in terms of Firebase projects when running this command in different scenarios. 1. **CI Run**: These tests are run under Firebase Test Lab of the unified Firebase project -(according to [this](https://github.com/firebase/firebase-android-sdk/blob/main/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java)) +(according to [this](https://github.com/firebase/firebase-android-sdk/blob/main/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java)) but the performance events are sent to a different project with which apps are configured with (see `copyRootGoogleServices` task) and the Prow Configuration in tg/831643). diff --git a/firebase-perf/gradle.properties b/firebase-perf/gradle.properties index 2f30a84d58e..4b2de75bc47 100644 --- a/firebase-perf/gradle.properties +++ b/firebase-perf/gradle.properties @@ -15,7 +15,7 @@ # # -version=21.0.4 -latestReleasedVersion=21.0.3 +version=21.0.5 +latestReleasedVersion=21.0.4 android.enableUnitTestBinaryResources=true diff --git a/firebase-perf/ktx/api.txt b/firebase-perf/ktx/api.txt index 5c7be346e10..da4f6cc18fe 100644 --- a/firebase-perf/ktx/api.txt +++ b/firebase-perf/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.perf.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java b/firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java index 762dff42a7e..1ee9d395e03 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java @@ -221,6 +221,7 @@ private boolean getIsSdkEnabled() { // 2. If the value exists in device cache, return this value. // 3. Otherwise, return default value. SdkEnabled config = SdkEnabled.getInstance(); + Optional deviceCacheValue = getDeviceCacheBoolean(config); // 1. Reads value from Firebase Remote Config, saves this value in cache layer if fetch status // is not failure. @@ -230,13 +231,19 @@ private boolean getIsSdkEnabled() { if (remoteConfigManager.isLastFetchFailed()) { return false; } - // b. Cache and return this value. - deviceCacheManager.setValue(config.getDeviceCacheFlag(), rcValue.get()); - return rcValue.get(); + + Boolean newValue = rcValue.get(); + // b. Only cache and return this value if it is different from the current value. + if (deviceCacheValue == null + || !deviceCacheValue.isAvailable() + || deviceCacheValue.get() != newValue) { + deviceCacheManager.setValue(config.getDeviceCacheFlag(), newValue); + } + + return newValue; } // 2. If the value exists in device cache, return this value. - Optional deviceCacheValue = getDeviceCacheBoolean(config); if (deviceCacheValue.isAvailable()) { return deviceCacheValue.get(); } @@ -257,17 +264,23 @@ private boolean getIsSdkVersionDisabled() { // 2. If the value exists in device cache, return this value. // 3. Otherwise, return default value. SdkDisabledVersions config = SdkDisabledVersions.getInstance(); + Optional deviceCacheValue = getDeviceCacheString(config); // 1. Reads value from Firebase Remote Config, cache and return this value. Optional rcValue = getRemoteConfigString(config); if (rcValue.isAvailable()) { // Do not check FRC last fetch status because it is the most recent value device can get. - deviceCacheManager.setValue(config.getDeviceCacheFlag(), rcValue.get()); - return isFireperfSdkVersionInList(rcValue.get()); + String newValue = rcValue.get(); + // Only cache and return this value if it is different from the current value. + if (deviceCacheValue == null + || !deviceCacheValue.isAvailable() + || !deviceCacheValue.get().equals(newValue)) { + deviceCacheManager.setValue(config.getDeviceCacheFlag(), newValue); + } + return isFireperfSdkVersionInList(newValue); } // 2. If the value exists in device cache, return this value. - Optional deviceCacheValue = getDeviceCacheString(config); if (deviceCacheValue.isAvailable()) { return isFireperfSdkVersionInList(deviceCacheValue.get()); } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/config/ConfigResolverTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/config/ConfigResolverTest.java index 7ccca34a8ff..24dc6597e4a 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/config/ConfigResolverTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/config/ConfigResolverTest.java @@ -280,6 +280,23 @@ public void getIsServiceCollectionEnabled_sdkDisabledVersionFlagNoFrc_returnDefa verify(mockDeviceCacheManager, never()).setValue(any(), anyString()); } + @Test + public void getIsServiceCollectionEnabled_deviceCacheHasSameValueAsFrc_returnCacheValue() { + when(mockRemoteConfigManager.getBoolean(FIREBASE_PERFORMANCE_SDK_ENABLED_FRC_KEY)) + .thenReturn(Optional.of(true)); + when(mockDeviceCacheManager.getBoolean(FIREBASE_PERFORMANCE_SDK_ENABLED_CACHE_KEY)) + .thenReturn(Optional.of(true)); + + when(mockDeviceCacheManager.getString(FIREBASE_PERFORMANCE_DISABLED_VERSIONS_CACHE_KEY)) + .thenReturn(Optional.of("")); + when(mockRemoteConfigManager.getString(FIREBASE_PERFORMANCE_DISABLED_VERSIONS_FRC_KEY)) + .thenReturn(Optional.of("")); + + assertThat(testConfigResolver.getIsServiceCollectionEnabled()).isTrue(); + verify(mockDeviceCacheManager, never()).setValue(any(), anyBoolean()); + verify(mockDeviceCacheManager, never()).setValue(any(), anyString()); + } + @Test public void getIsPerformanceCollectionConfigValueAvailable_noDeviceCacheNoRemoteConfig_returnsFalse() { diff --git a/firebase-sessions/CHANGELOG.md b/firebase-sessions/CHANGELOG.md index 7147a6bf504..48987a62df5 100644 --- a/firebase-sessions/CHANGELOG.md +++ b/firebase-sessions/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +* [fixed] Make AQS resilient to background init in multi-process apps. # 2.0.7 * [fixed] Removed extraneous logs that risk leaking internal identifiers. diff --git a/firebase-sessions/api.txt b/firebase-sessions/api.txt index c328c81853c..5824e4febdf 100644 --- a/firebase-sessions/api.txt +++ b/firebase-sessions/api.txt @@ -1,34 +1,32 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.sessions.api { public final class FirebaseSessionsDependencies { - method public static void addDependency(@NonNull com.google.firebase.sessions.api.SessionSubscriber.Name subscriberName); - method public static void register(@NonNull com.google.firebase.sessions.api.SessionSubscriber subscriber); - field @NonNull public static final com.google.firebase.sessions.api.FirebaseSessionsDependencies INSTANCE; + method public static void addDependency(com.google.firebase.sessions.api.SessionSubscriber.Name subscriberName); + method public static void register(com.google.firebase.sessions.api.SessionSubscriber subscriber); + field public static final com.google.firebase.sessions.api.FirebaseSessionsDependencies INSTANCE; } public interface SessionSubscriber { - method @NonNull public com.google.firebase.sessions.api.SessionSubscriber.Name getSessionSubscriberName(); + method public com.google.firebase.sessions.api.SessionSubscriber.Name getSessionSubscriberName(); method public boolean isDataCollectionEnabled(); - method public void onSessionChanged(@NonNull com.google.firebase.sessions.api.SessionSubscriber.SessionDetails sessionDetails); + method public void onSessionChanged(com.google.firebase.sessions.api.SessionSubscriber.SessionDetails sessionDetails); property public abstract boolean isDataCollectionEnabled; - property @NonNull public abstract com.google.firebase.sessions.api.SessionSubscriber.Name sessionSubscriberName; + property public abstract com.google.firebase.sessions.api.SessionSubscriber.Name sessionSubscriberName; } public enum SessionSubscriber.Name { - method @NonNull public static com.google.firebase.sessions.api.SessionSubscriber.Name valueOf(@NonNull String name) throws java.lang.IllegalArgumentException; - method @NonNull public static com.google.firebase.sessions.api.SessionSubscriber.Name[] values(); enum_constant public static final com.google.firebase.sessions.api.SessionSubscriber.Name CRASHLYTICS; enum_constant @Discouraged(message="This is for testing purposes only.") public static final com.google.firebase.sessions.api.SessionSubscriber.Name MATT_SAYS_HI; enum_constant public static final com.google.firebase.sessions.api.SessionSubscriber.Name PERFORMANCE; } public static final class SessionSubscriber.SessionDetails { - ctor public SessionSubscriber.SessionDetails(@NonNull String sessionId); - method @NonNull public String component1(); - method @NonNull public com.google.firebase.sessions.api.SessionSubscriber.SessionDetails copy(@NonNull String sessionId); - method @NonNull public String getSessionId(); - property @NonNull public final String sessionId; + ctor public SessionSubscriber.SessionDetails(String sessionId); + method public String component1(); + method public com.google.firebase.sessions.api.SessionSubscriber.SessionDetails copy(String sessionId); + method public String getSessionId(); + property public final String sessionId; } } diff --git a/firebase-sessions/gradle.properties b/firebase-sessions/gradle.properties index 84c76caede3..c9bd869d4cd 100644 --- a/firebase-sessions/gradle.properties +++ b/firebase-sessions/gradle.properties @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=2.0.8 -latestReleasedVersion=2.0.7 +version=2.0.9 +latestReleasedVersion=2.0.8 diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleService.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleService.kt index a900fcd95c1..bde6d138fbe 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleService.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleService.kt @@ -127,10 +127,17 @@ internal class SessionLifecycleService : Service() { /** Generates a new session id and sends it everywhere it's needed */ private fun newSession() { - SessionGenerator.instance.generateNewSession() - Log.d(TAG, "Generated new session.") - broadcastSession() - SessionDatastore.instance.updateSessionId(SessionGenerator.instance.currentSession.sessionId) + try { + // TODO(mrober): Consider migrating to Dagger, or update [FirebaseSessionsRegistrar]. + SessionGenerator.instance.generateNewSession() + Log.d(TAG, "Generated new session.") + broadcastSession() + SessionDatastore.instance.updateSessionId( + SessionGenerator.instance.currentSession.sessionId + ) + } catch (ex: IllegalStateException) { + Log.w(TAG, "Failed to generate new session.", ex) + } } /** @@ -146,13 +153,17 @@ internal class SessionLifecycleService : Service() { } private fun maybeSendSessionToClient(client: Messenger) { - if (hasForegrounded) { - sendSessionToClient(client, SessionGenerator.instance.currentSession.sessionId) - } else { - // Send the value from the datastore before the first foregrounding it exists - val storedSession = SessionDatastore.instance.getCurrentSessionId() - Log.d(TAG, "App has not yet foregrounded. Using previously stored session.") - storedSession?.let { sendSessionToClient(client, it) } + try { + if (hasForegrounded) { + sendSessionToClient(client, SessionGenerator.instance.currentSession.sessionId) + } else { + // Send the value from the datastore before the first foregrounding it exists + val storedSession = SessionDatastore.instance.getCurrentSessionId() + Log.d(TAG, "App has not yet foregrounded. Using previously stored session.") + storedSession?.let { sendSessionToClient(client, it) } + } + } catch (ex: IllegalStateException) { + Log.w(TAG, "Failed to send session to client.", ex) } } diff --git a/firebase-storage/api.txt b/firebase-storage/api.txt index 440cecda0dc..38c099acdac 100644 --- a/firebase-storage/api.txt +++ b/firebase-storage/api.txt @@ -1,71 +1,71 @@ -// Signature format: 2.0 +// Signature format: 3.0 package com.google.firebase.storage { - public abstract class CancellableTask extends com.google.android.gms.tasks.Task { + public abstract class CancellableTask extends com.google.android.gms.tasks.Task { ctor public CancellableTask(); - method @NonNull public abstract com.google.firebase.storage.CancellableTask addOnProgressListener(@NonNull com.google.firebase.storage.OnProgressListener); - method @NonNull public abstract com.google.firebase.storage.CancellableTask addOnProgressListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.storage.OnProgressListener); - method @NonNull public abstract com.google.firebase.storage.CancellableTask addOnProgressListener(@NonNull android.app.Activity, @NonNull com.google.firebase.storage.OnProgressListener); + method public abstract com.google.firebase.storage.CancellableTask addOnProgressListener(android.app.Activity, com.google.firebase.storage.OnProgressListener); + method public abstract com.google.firebase.storage.CancellableTask addOnProgressListener(com.google.firebase.storage.OnProgressListener); + method public abstract com.google.firebase.storage.CancellableTask addOnProgressListener(java.util.concurrent.Executor, com.google.firebase.storage.OnProgressListener); method public abstract boolean cancel(); method public abstract boolean isInProgress(); } - public abstract class ControllableTask extends com.google.firebase.storage.CancellableTask { + public abstract class ControllableTask extends com.google.firebase.storage.CancellableTask { ctor public ControllableTask(); - method @NonNull public abstract com.google.firebase.storage.ControllableTask addOnPausedListener(@NonNull com.google.firebase.storage.OnPausedListener); - method @NonNull public abstract com.google.firebase.storage.ControllableTask addOnPausedListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.storage.OnPausedListener); - method @NonNull public abstract com.google.firebase.storage.ControllableTask addOnPausedListener(@NonNull android.app.Activity, @NonNull com.google.firebase.storage.OnPausedListener); + method public abstract com.google.firebase.storage.ControllableTask addOnPausedListener(android.app.Activity, com.google.firebase.storage.OnPausedListener); + method public abstract com.google.firebase.storage.ControllableTask addOnPausedListener(com.google.firebase.storage.OnPausedListener); + method public abstract com.google.firebase.storage.ControllableTask addOnPausedListener(java.util.concurrent.Executor, com.google.firebase.storage.OnPausedListener); method public abstract boolean isPaused(); method public abstract boolean pause(); method public abstract boolean resume(); } - public class FileDownloadTask extends com.google.firebase.storage.StorageTask { + public class FileDownloadTask extends com.google.firebase.storage.StorageTask { } - public class FileDownloadTask.TaskSnapshot extends com.google.firebase.storage.StorageTask.SnapshotBase { + public class FileDownloadTask.TaskSnapshot extends com.google.firebase.storage.StorageTask.SnapshotBase { method public long getBytesTransferred(); method public long getTotalByteCount(); } public class FirebaseStorage { - method @NonNull public com.google.firebase.FirebaseApp getApp(); - method @NonNull public static com.google.firebase.storage.FirebaseStorage getInstance(); - method @NonNull public static com.google.firebase.storage.FirebaseStorage getInstance(@NonNull String); - method @NonNull public static com.google.firebase.storage.FirebaseStorage getInstance(@NonNull com.google.firebase.FirebaseApp); - method @NonNull public static com.google.firebase.storage.FirebaseStorage getInstance(@NonNull com.google.firebase.FirebaseApp, @NonNull String); + method public com.google.firebase.FirebaseApp getApp(); + method public static com.google.firebase.storage.FirebaseStorage getInstance(); + method public static com.google.firebase.storage.FirebaseStorage getInstance(com.google.firebase.FirebaseApp); + method public static com.google.firebase.storage.FirebaseStorage getInstance(com.google.firebase.FirebaseApp, String); + method public static com.google.firebase.storage.FirebaseStorage getInstance(String); method public long getMaxChunkUploadRetry(); method public long getMaxDownloadRetryTimeMillis(); method public long getMaxOperationRetryTimeMillis(); method public long getMaxUploadRetryTimeMillis(); - method @NonNull public com.google.firebase.storage.StorageReference getReference(); - method @NonNull public com.google.firebase.storage.StorageReference getReference(@NonNull String); - method @NonNull public com.google.firebase.storage.StorageReference getReferenceFromUrl(@NonNull String); + method public com.google.firebase.storage.StorageReference getReference(); + method public com.google.firebase.storage.StorageReference getReference(String); + method public com.google.firebase.storage.StorageReference getReferenceFromUrl(String); method public void setMaxChunkUploadRetry(long); method public void setMaxDownloadRetryTimeMillis(long); method public void setMaxOperationRetryTimeMillis(long); method public void setMaxUploadRetryTimeMillis(long); - method public void useEmulator(@NonNull String, int); + method public void useEmulator(String, int); } public final class ListResult { - method @NonNull public java.util.List getItems(); - method @Nullable public String getPageToken(); - method @NonNull public java.util.List getPrefixes(); + method public java.util.List getItems(); + method public String? getPageToken(); + method public java.util.List getPrefixes(); } public interface OnPausedListener { - method public void onPaused(@NonNull ProgressT); + method public void onPaused(ProgressT); } public interface OnProgressListener { - method public void onProgress(@NonNull ProgressT); + method public void onProgress(ProgressT); } public class StorageException extends com.google.firebase.FirebaseException { - method @NonNull public static com.google.firebase.storage.StorageException fromErrorStatus(@NonNull com.google.android.gms.common.api.Status); - method @NonNull public static com.google.firebase.storage.StorageException fromException(@NonNull Throwable); - method @Nullable public static com.google.firebase.storage.StorageException fromExceptionAndHttpCode(@Nullable Throwable, int); + method public static com.google.firebase.storage.StorageException fromErrorStatus(com.google.android.gms.common.api.Status); + method public static com.google.firebase.storage.StorageException fromException(Throwable); + method public static com.google.firebase.storage.StorageException? fromExceptionAndHttpCode(Throwable?, int); method @com.google.firebase.storage.StorageException.ErrorCode public int getErrorCode(); method public int getHttpResultCode(); method public boolean getIsRecoverableException(); @@ -85,125 +85,125 @@ package com.google.firebase.storage { } public final class StorageKt { - method public static operator long component1(@NonNull com.google.firebase.storage.UploadTask.TaskSnapshot); - method public static operator long component1(@NonNull com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); - method public static operator long component1(@NonNull com.google.firebase.storage.FileDownloadTask.TaskSnapshot); - method @NonNull public static operator java.util.List component1(@NonNull com.google.firebase.storage.ListResult); - method public static operator long component2(@NonNull com.google.firebase.storage.UploadTask.TaskSnapshot); - method public static operator long component2(@NonNull com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); - method public static operator long component2(@NonNull com.google.firebase.storage.FileDownloadTask.TaskSnapshot); - method @NonNull public static operator java.util.List component2(@NonNull com.google.firebase.storage.ListResult); - method @Nullable public static operator com.google.firebase.storage.StorageMetadata component3(@NonNull com.google.firebase.storage.UploadTask.TaskSnapshot); - method @NonNull public static operator java.io.InputStream component3(@NonNull com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); - method @Nullable public static operator String component3(@NonNull com.google.firebase.storage.ListResult); - method @Nullable public static operator android.net.Uri component4(@NonNull com.google.firebase.storage.UploadTask.TaskSnapshot); - method @NonNull public static com.google.firebase.storage.FirebaseStorage getStorage(@NonNull com.google.firebase.Firebase); - method @NonNull public static kotlinx.coroutines.flow.Flow> getTaskState(@NonNull com.google.firebase.storage.StorageTask); - method @NonNull public static com.google.firebase.storage.FirebaseStorage storage(@NonNull com.google.firebase.Firebase, @NonNull String url); - method @NonNull public static com.google.firebase.storage.FirebaseStorage storage(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @NonNull public static com.google.firebase.storage.FirebaseStorage storage(@NonNull com.google.firebase.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String url); - method @NonNull public static com.google.firebase.storage.StorageMetadata storageMetadata(@NonNull kotlin.jvm.functions.Function1 init); + method public static operator long component1(com.google.firebase.storage.FileDownloadTask.TaskSnapshot); + method public static operator java.util.List component1(com.google.firebase.storage.ListResult); + method public static operator long component1(com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); + method public static operator long component1(com.google.firebase.storage.UploadTask.TaskSnapshot); + method public static operator long component2(com.google.firebase.storage.FileDownloadTask.TaskSnapshot); + method public static operator java.util.List component2(com.google.firebase.storage.ListResult); + method public static operator long component2(com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); + method public static operator long component2(com.google.firebase.storage.UploadTask.TaskSnapshot); + method public static operator String? component3(com.google.firebase.storage.ListResult); + method public static operator java.io.InputStream component3(com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); + method public static operator com.google.firebase.storage.StorageMetadata? component3(com.google.firebase.storage.UploadTask.TaskSnapshot); + method public static operator android.net.Uri? component4(com.google.firebase.storage.UploadTask.TaskSnapshot); + method public static com.google.firebase.storage.FirebaseStorage getStorage(com.google.firebase.Firebase); + method public static .SnapshotBase> kotlinx.coroutines.flow.Flow> getTaskState(com.google.firebase.storage.StorageTask); + method public static com.google.firebase.storage.FirebaseStorage storage(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app); + method public static com.google.firebase.storage.FirebaseStorage storage(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app, String url); + method public static com.google.firebase.storage.FirebaseStorage storage(com.google.firebase.Firebase, String url); + method public static com.google.firebase.storage.StorageMetadata storageMetadata(kotlin.jvm.functions.Function1 init); } public class StorageMetadata { ctor public StorageMetadata(); - method @Nullable public String getBucket(); - method @Nullable public String getCacheControl(); - method @Nullable public String getContentDisposition(); - method @Nullable public String getContentEncoding(); - method @Nullable public String getContentLanguage(); - method @Nullable public String getContentType(); + method public String? getBucket(); + method public String? getCacheControl(); + method public String? getContentDisposition(); + method public String? getContentEncoding(); + method public String? getContentLanguage(); + method public String? getContentType(); method public long getCreationTimeMillis(); - method @Nullable public String getCustomMetadata(@NonNull String); - method @NonNull public java.util.Set getCustomMetadataKeys(); - method @Nullable public String getGeneration(); - method @Nullable public String getMd5Hash(); - method @Nullable public String getMetadataGeneration(); - method @Nullable public String getName(); - method @NonNull public String getPath(); - method @Nullable public com.google.firebase.storage.StorageReference getReference(); + method public String? getCustomMetadata(String); + method public java.util.Set getCustomMetadataKeys(); + method public String? getGeneration(); + method public String? getMd5Hash(); + method public String? getMetadataGeneration(); + method public String? getName(); + method public String getPath(); + method public com.google.firebase.storage.StorageReference? getReference(); method public long getSizeBytes(); method public long getUpdatedTimeMillis(); } public static class StorageMetadata.Builder { ctor public StorageMetadata.Builder(); - ctor public StorageMetadata.Builder(@NonNull com.google.firebase.storage.StorageMetadata); - method @NonNull public com.google.firebase.storage.StorageMetadata build(); - method @Nullable public String getCacheControl(); - method @Nullable public String getContentDisposition(); - method @Nullable public String getContentEncoding(); - method @Nullable public String getContentLanguage(); - method @Nullable public String getContentType(); - method @NonNull public com.google.firebase.storage.StorageMetadata.Builder setCacheControl(@Nullable String); - method @NonNull public com.google.firebase.storage.StorageMetadata.Builder setContentDisposition(@Nullable String); - method @NonNull public com.google.firebase.storage.StorageMetadata.Builder setContentEncoding(@Nullable String); - method @NonNull public com.google.firebase.storage.StorageMetadata.Builder setContentLanguage(@Nullable String); - method @NonNull public com.google.firebase.storage.StorageMetadata.Builder setContentType(@Nullable String); - method @NonNull public com.google.firebase.storage.StorageMetadata.Builder setCustomMetadata(@NonNull String, @Nullable String); + ctor public StorageMetadata.Builder(com.google.firebase.storage.StorageMetadata); + method public com.google.firebase.storage.StorageMetadata build(); + method public String? getCacheControl(); + method public String? getContentDisposition(); + method public String? getContentEncoding(); + method public String? getContentLanguage(); + method public String? getContentType(); + method public com.google.firebase.storage.StorageMetadata.Builder setCacheControl(String?); + method public com.google.firebase.storage.StorageMetadata.Builder setContentDisposition(String?); + method public com.google.firebase.storage.StorageMetadata.Builder setContentEncoding(String?); + method public com.google.firebase.storage.StorageMetadata.Builder setContentLanguage(String?); + method public com.google.firebase.storage.StorageMetadata.Builder setContentType(String?); + method public com.google.firebase.storage.StorageMetadata.Builder setCustomMetadata(String, String?); } - public class StorageReference implements java.lang.Comparable { - method @NonNull public com.google.firebase.storage.StorageReference child(@NonNull String); - method public int compareTo(@NonNull com.google.firebase.storage.StorageReference); - method @NonNull public com.google.android.gms.tasks.Task delete(); - method @NonNull public java.util.List getActiveDownloadTasks(); - method @NonNull public java.util.List getActiveUploadTasks(); - method @NonNull public String getBucket(); - method @NonNull public com.google.android.gms.tasks.Task getBytes(long); - method @NonNull public com.google.android.gms.tasks.Task getDownloadUrl(); - method @NonNull public com.google.firebase.storage.FileDownloadTask getFile(@NonNull android.net.Uri); - method @NonNull public com.google.firebase.storage.FileDownloadTask getFile(@NonNull java.io.File); - method @NonNull public com.google.android.gms.tasks.Task getMetadata(); - method @NonNull public String getName(); - method @Nullable public com.google.firebase.storage.StorageReference getParent(); - method @NonNull public String getPath(); - method @NonNull public com.google.firebase.storage.StorageReference getRoot(); - method @NonNull public com.google.firebase.storage.FirebaseStorage getStorage(); - method @NonNull public com.google.firebase.storage.StreamDownloadTask getStream(); - method @NonNull public com.google.firebase.storage.StreamDownloadTask getStream(@NonNull com.google.firebase.storage.StreamDownloadTask.StreamProcessor); - method @NonNull public com.google.android.gms.tasks.Task list(int); - method @NonNull public com.google.android.gms.tasks.Task list(int, @NonNull String); - method @NonNull public com.google.android.gms.tasks.Task listAll(); - method @NonNull public com.google.firebase.storage.UploadTask putBytes(@NonNull byte[]); - method @NonNull public com.google.firebase.storage.UploadTask putBytes(@NonNull byte[], @NonNull com.google.firebase.storage.StorageMetadata); - method @NonNull public com.google.firebase.storage.UploadTask putFile(@NonNull android.net.Uri); - method @NonNull public com.google.firebase.storage.UploadTask putFile(@NonNull android.net.Uri, @NonNull com.google.firebase.storage.StorageMetadata); - method @NonNull public com.google.firebase.storage.UploadTask putFile(@NonNull android.net.Uri, @Nullable com.google.firebase.storage.StorageMetadata, @Nullable android.net.Uri); - method @NonNull public com.google.firebase.storage.UploadTask putStream(@NonNull java.io.InputStream); - method @NonNull public com.google.firebase.storage.UploadTask putStream(@NonNull java.io.InputStream, @NonNull com.google.firebase.storage.StorageMetadata); - method @NonNull public com.google.android.gms.tasks.Task updateMetadata(@NonNull com.google.firebase.storage.StorageMetadata); + public class StorageReference implements java.lang.Comparable { + method public com.google.firebase.storage.StorageReference child(String); + method public int compareTo(com.google.firebase.storage.StorageReference); + method public com.google.android.gms.tasks.Task delete(); + method public java.util.List getActiveDownloadTasks(); + method public java.util.List getActiveUploadTasks(); + method public String getBucket(); + method public com.google.android.gms.tasks.Task getBytes(long); + method public com.google.android.gms.tasks.Task getDownloadUrl(); + method public com.google.firebase.storage.FileDownloadTask getFile(android.net.Uri); + method public com.google.firebase.storage.FileDownloadTask getFile(java.io.File); + method public com.google.android.gms.tasks.Task getMetadata(); + method public String getName(); + method public com.google.firebase.storage.StorageReference? getParent(); + method public String getPath(); + method public com.google.firebase.storage.StorageReference getRoot(); + method public com.google.firebase.storage.FirebaseStorage getStorage(); + method public com.google.firebase.storage.StreamDownloadTask getStream(); + method public com.google.firebase.storage.StreamDownloadTask getStream(com.google.firebase.storage.StreamDownloadTask.StreamProcessor); + method public com.google.android.gms.tasks.Task list(int); + method public com.google.android.gms.tasks.Task list(int, String); + method public com.google.android.gms.tasks.Task listAll(); + method public com.google.firebase.storage.UploadTask putBytes(byte[]); + method public com.google.firebase.storage.UploadTask putBytes(byte[], com.google.firebase.storage.StorageMetadata); + method public com.google.firebase.storage.UploadTask putFile(android.net.Uri); + method public com.google.firebase.storage.UploadTask putFile(android.net.Uri, com.google.firebase.storage.StorageMetadata); + method public com.google.firebase.storage.UploadTask putFile(android.net.Uri, com.google.firebase.storage.StorageMetadata?, android.net.Uri?); + method public com.google.firebase.storage.UploadTask putStream(java.io.InputStream); + method public com.google.firebase.storage.UploadTask putStream(java.io.InputStream, com.google.firebase.storage.StorageMetadata); + method public com.google.android.gms.tasks.Task updateMetadata(com.google.firebase.storage.StorageMetadata); } - public abstract class StorageTask extends com.google.firebase.storage.ControllableTask { + public abstract class StorageTask extends com.google.firebase.storage.ControllableTask { ctor protected StorageTask(); - method @NonNull public com.google.firebase.storage.StorageTask addOnCanceledListener(@NonNull com.google.android.gms.tasks.OnCanceledListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnCanceledListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.OnCanceledListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnCanceledListener(@NonNull android.app.Activity, @NonNull com.google.android.gms.tasks.OnCanceledListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnCompleteListener(@NonNull com.google.android.gms.tasks.OnCompleteListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.OnCompleteListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnCompleteListener(@NonNull android.app.Activity, @NonNull com.google.android.gms.tasks.OnCompleteListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnFailureListener(@NonNull com.google.android.gms.tasks.OnFailureListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnFailureListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.OnFailureListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnFailureListener(@NonNull android.app.Activity, @NonNull com.google.android.gms.tasks.OnFailureListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnPausedListener(@NonNull com.google.firebase.storage.OnPausedListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnPausedListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.storage.OnPausedListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnPausedListener(@NonNull android.app.Activity, @NonNull com.google.firebase.storage.OnPausedListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnProgressListener(@NonNull com.google.firebase.storage.OnProgressListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnProgressListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.storage.OnProgressListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnProgressListener(@NonNull android.app.Activity, @NonNull com.google.firebase.storage.OnProgressListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnSuccessListener(@NonNull com.google.android.gms.tasks.OnSuccessListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnSuccessListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.OnSuccessListener); - method @NonNull public com.google.firebase.storage.StorageTask addOnSuccessListener(@NonNull android.app.Activity, @NonNull com.google.android.gms.tasks.OnSuccessListener); + method public com.google.firebase.storage.StorageTask addOnCanceledListener(android.app.Activity, com.google.android.gms.tasks.OnCanceledListener); + method public com.google.firebase.storage.StorageTask addOnCanceledListener(com.google.android.gms.tasks.OnCanceledListener); + method public com.google.firebase.storage.StorageTask addOnCanceledListener(java.util.concurrent.Executor, com.google.android.gms.tasks.OnCanceledListener); + method public com.google.firebase.storage.StorageTask addOnCompleteListener(android.app.Activity, com.google.android.gms.tasks.OnCompleteListener); + method public com.google.firebase.storage.StorageTask addOnCompleteListener(com.google.android.gms.tasks.OnCompleteListener); + method public com.google.firebase.storage.StorageTask addOnCompleteListener(java.util.concurrent.Executor, com.google.android.gms.tasks.OnCompleteListener); + method public com.google.firebase.storage.StorageTask addOnFailureListener(android.app.Activity, com.google.android.gms.tasks.OnFailureListener); + method public com.google.firebase.storage.StorageTask addOnFailureListener(com.google.android.gms.tasks.OnFailureListener); + method public com.google.firebase.storage.StorageTask addOnFailureListener(java.util.concurrent.Executor, com.google.android.gms.tasks.OnFailureListener); + method public com.google.firebase.storage.StorageTask addOnPausedListener(android.app.Activity, com.google.firebase.storage.OnPausedListener); + method public com.google.firebase.storage.StorageTask addOnPausedListener(com.google.firebase.storage.OnPausedListener); + method public com.google.firebase.storage.StorageTask addOnPausedListener(java.util.concurrent.Executor, com.google.firebase.storage.OnPausedListener); + method public com.google.firebase.storage.StorageTask addOnProgressListener(android.app.Activity, com.google.firebase.storage.OnProgressListener); + method public com.google.firebase.storage.StorageTask addOnProgressListener(com.google.firebase.storage.OnProgressListener); + method public com.google.firebase.storage.StorageTask addOnProgressListener(java.util.concurrent.Executor, com.google.firebase.storage.OnProgressListener); + method public com.google.firebase.storage.StorageTask addOnSuccessListener(android.app.Activity, com.google.android.gms.tasks.OnSuccessListener); + method public com.google.firebase.storage.StorageTask addOnSuccessListener(com.google.android.gms.tasks.OnSuccessListener); + method public com.google.firebase.storage.StorageTask addOnSuccessListener(java.util.concurrent.Executor, com.google.android.gms.tasks.OnSuccessListener); method public boolean cancel(); - method @NonNull public com.google.android.gms.tasks.Task continueWith(@NonNull com.google.android.gms.tasks.Continuation); - method @NonNull public com.google.android.gms.tasks.Task continueWith(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.Continuation); - method @NonNull public com.google.android.gms.tasks.Task continueWithTask(@NonNull com.google.android.gms.tasks.Continuation>); - method @NonNull public com.google.android.gms.tasks.Task continueWithTask(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.Continuation>); - method @Nullable public Exception getException(); - method @NonNull public ResultT getResult(); - method @NonNull public ResultT getResult(@NonNull Class) throws X; - method @NonNull public ResultT getSnapshot(); + method public com.google.android.gms.tasks.Task continueWith(com.google.android.gms.tasks.Continuation); + method public com.google.android.gms.tasks.Task continueWith(java.util.concurrent.Executor, com.google.android.gms.tasks.Continuation); + method public com.google.android.gms.tasks.Task continueWithTask(com.google.android.gms.tasks.Continuation!>); + method public com.google.android.gms.tasks.Task continueWithTask(java.util.concurrent.Executor, com.google.android.gms.tasks.Continuation!>); + method public Exception? getException(); + method public ResultT getResult(); + method public ResultT getResult(Class) throws X; + method public ResultT getSnapshot(); method public boolean isCanceled(); method public boolean isComplete(); method public boolean isInProgress(); @@ -215,40 +215,40 @@ package com.google.firebase.storage { method protected void onProgress(); method protected void onQueued(); method protected void onSuccess(); - method @NonNull public com.google.android.gms.tasks.Task onSuccessTask(@NonNull com.google.android.gms.tasks.SuccessContinuation); - method @NonNull public com.google.android.gms.tasks.Task onSuccessTask(@NonNull java.util.concurrent.Executor, @NonNull com.google.android.gms.tasks.SuccessContinuation); + method public com.google.android.gms.tasks.Task onSuccessTask(com.google.android.gms.tasks.SuccessContinuation); + method public com.google.android.gms.tasks.Task onSuccessTask(java.util.concurrent.Executor, com.google.android.gms.tasks.SuccessContinuation); method public boolean pause(); - method @NonNull public com.google.firebase.storage.StorageTask removeOnCanceledListener(@NonNull com.google.android.gms.tasks.OnCanceledListener); - method @NonNull public com.google.firebase.storage.StorageTask removeOnCompleteListener(@NonNull com.google.android.gms.tasks.OnCompleteListener); - method @NonNull public com.google.firebase.storage.StorageTask removeOnFailureListener(@NonNull com.google.android.gms.tasks.OnFailureListener); - method @NonNull public com.google.firebase.storage.StorageTask removeOnPausedListener(@NonNull com.google.firebase.storage.OnPausedListener); - method @NonNull public com.google.firebase.storage.StorageTask removeOnProgressListener(@NonNull com.google.firebase.storage.OnProgressListener); - method @NonNull public com.google.firebase.storage.StorageTask removeOnSuccessListener(@NonNull com.google.android.gms.tasks.OnSuccessListener); + method public com.google.firebase.storage.StorageTask removeOnCanceledListener(com.google.android.gms.tasks.OnCanceledListener); + method public com.google.firebase.storage.StorageTask removeOnCompleteListener(com.google.android.gms.tasks.OnCompleteListener); + method public com.google.firebase.storage.StorageTask removeOnFailureListener(com.google.android.gms.tasks.OnFailureListener); + method public com.google.firebase.storage.StorageTask removeOnPausedListener(com.google.firebase.storage.OnPausedListener); + method public com.google.firebase.storage.StorageTask removeOnProgressListener(com.google.firebase.storage.OnProgressListener); + method public com.google.firebase.storage.StorageTask removeOnSuccessListener(com.google.android.gms.tasks.OnSuccessListener); method public boolean resume(); - field protected final Object syncObject; + field protected final Object! syncObject; } protected static interface StorageTask.ProvideError { - method public Exception getError(); + method public Exception! getError(); } public class StorageTask.SnapshotBase implements com.google.firebase.storage.StorageTask.ProvideError { - ctor public StorageTask.SnapshotBase(@Nullable Exception); - method @Nullable public Exception getError(); - method @NonNull public com.google.firebase.storage.StorageReference getStorage(); - method @NonNull public com.google.firebase.storage.StorageTask getTask(); + ctor public StorageTask.SnapshotBase(Exception?); + method public Exception? getError(); + method public com.google.firebase.storage.StorageReference getStorage(); + method public com.google.firebase.storage.StorageTask getTask(); } - public class StreamDownloadTask extends com.google.firebase.storage.StorageTask { + public class StreamDownloadTask extends com.google.firebase.storage.StorageTask { } public static interface StreamDownloadTask.StreamProcessor { - method public void doInBackground(@NonNull com.google.firebase.storage.StreamDownloadTask.TaskSnapshot, @NonNull java.io.InputStream) throws java.io.IOException; + method public void doInBackground(com.google.firebase.storage.StreamDownloadTask.TaskSnapshot, java.io.InputStream) throws java.io.IOException; } - public class StreamDownloadTask.TaskSnapshot extends com.google.firebase.storage.StorageTask.SnapshotBase { + public class StreamDownloadTask.TaskSnapshot extends com.google.firebase.storage.StorageTask.SnapshotBase { method public long getBytesTransferred(); - method @NonNull public java.io.InputStream getStream(); + method public java.io.InputStream getStream(); method public long getTotalByteCount(); } @@ -256,27 +256,27 @@ package com.google.firebase.storage { } public static final class TaskState.InProgress extends com.google.firebase.storage.TaskState { - ctor public TaskState.InProgress(@Nullable T snapshot); + ctor public TaskState.InProgress(T snapshot); method public T getSnapshot(); property public final T snapshot; } public static final class TaskState.Paused extends com.google.firebase.storage.TaskState { - ctor public TaskState.Paused(@Nullable T snapshot); + ctor public TaskState.Paused(T snapshot); method public T getSnapshot(); property public final T snapshot; } - public class UploadTask extends com.google.firebase.storage.StorageTask { + public class UploadTask extends com.google.firebase.storage.StorageTask { method protected void resetState(); method protected void schedule(); } - public class UploadTask.TaskSnapshot extends com.google.firebase.storage.StorageTask.SnapshotBase { + public class UploadTask.TaskSnapshot extends com.google.firebase.storage.StorageTask.SnapshotBase { method public long getBytesTransferred(); - method @Nullable public com.google.firebase.storage.StorageMetadata getMetadata(); + method public com.google.firebase.storage.StorageMetadata? getMetadata(); method public long getTotalByteCount(); - method @Nullable public android.net.Uri getUploadSessionUri(); + method public android.net.Uri? getUploadSessionUri(); } } @@ -284,39 +284,39 @@ package com.google.firebase.storage { package com.google.firebase.storage.ktx { public final class StorageKt { - method @Deprecated public static operator long component1(@NonNull com.google.firebase.storage.UploadTask.TaskSnapshot); - method @Deprecated public static operator long component1(@NonNull com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); - method @Deprecated public static operator long component1(@NonNull com.google.firebase.storage.FileDownloadTask.TaskSnapshot); - method @Deprecated @NonNull public static operator java.util.List component1(@NonNull com.google.firebase.storage.ListResult); - method @Deprecated public static operator long component2(@NonNull com.google.firebase.storage.UploadTask.TaskSnapshot); - method @Deprecated public static operator long component2(@NonNull com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); - method @Deprecated public static operator long component2(@NonNull com.google.firebase.storage.FileDownloadTask.TaskSnapshot); - method @Deprecated @NonNull public static operator java.util.List component2(@NonNull com.google.firebase.storage.ListResult); - method @Deprecated @Nullable public static operator com.google.firebase.storage.StorageMetadata component3(@NonNull com.google.firebase.storage.UploadTask.TaskSnapshot); - method @Deprecated @NonNull public static operator java.io.InputStream component3(@NonNull com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); - method @Deprecated @Nullable public static operator String component3(@NonNull com.google.firebase.storage.ListResult); - method @Deprecated @Nullable public static operator android.net.Uri component4(@NonNull com.google.firebase.storage.UploadTask.TaskSnapshot); - method @Deprecated @NonNull public static com.google.firebase.storage.FirebaseStorage getStorage(@NonNull com.google.firebase.ktx.Firebase); - method @Deprecated @NonNull public static kotlinx.coroutines.flow.Flow> getTaskState(@NonNull com.google.firebase.storage.StorageTask); - method @Deprecated @NonNull public static com.google.firebase.storage.FirebaseStorage storage(@NonNull com.google.firebase.ktx.Firebase, @NonNull String url); - method @Deprecated @NonNull public static com.google.firebase.storage.FirebaseStorage storage(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); - method @Deprecated @NonNull public static com.google.firebase.storage.FirebaseStorage storage(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String url); - method @Deprecated @NonNull public static com.google.firebase.storage.StorageMetadata storageMetadata(@NonNull kotlin.jvm.functions.Function1 init); + method @Deprecated public static operator long component1(com.google.firebase.storage.FileDownloadTask.TaskSnapshot); + method @Deprecated public static operator java.util.List component1(com.google.firebase.storage.ListResult); + method @Deprecated public static operator long component1(com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); + method @Deprecated public static operator long component1(com.google.firebase.storage.UploadTask.TaskSnapshot); + method @Deprecated public static operator long component2(com.google.firebase.storage.FileDownloadTask.TaskSnapshot); + method @Deprecated public static operator java.util.List component2(com.google.firebase.storage.ListResult); + method @Deprecated public static operator long component2(com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); + method @Deprecated public static operator long component2(com.google.firebase.storage.UploadTask.TaskSnapshot); + method @Deprecated public static operator String? component3(com.google.firebase.storage.ListResult); + method @Deprecated public static operator java.io.InputStream component3(com.google.firebase.storage.StreamDownloadTask.TaskSnapshot); + method @Deprecated public static operator com.google.firebase.storage.StorageMetadata? component3(com.google.firebase.storage.UploadTask.TaskSnapshot); + method @Deprecated public static operator android.net.Uri? component4(com.google.firebase.storage.UploadTask.TaskSnapshot); + method @Deprecated public static com.google.firebase.storage.FirebaseStorage getStorage(com.google.firebase.ktx.Firebase); + method @Deprecated public static .SnapshotBase> kotlinx.coroutines.flow.Flow> getTaskState(com.google.firebase.storage.StorageTask); + method @Deprecated public static com.google.firebase.storage.FirebaseStorage storage(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app); + method @Deprecated public static com.google.firebase.storage.FirebaseStorage storage(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app, String url); + method @Deprecated public static com.google.firebase.storage.FirebaseStorage storage(com.google.firebase.ktx.Firebase, String url); + method @Deprecated public static com.google.firebase.storage.StorageMetadata storageMetadata(kotlin.jvm.functions.Function1 init); } @Deprecated public abstract class TaskState { } @Deprecated public static final class TaskState.InProgress extends com.google.firebase.storage.ktx.TaskState { - ctor @Deprecated public TaskState.InProgress(@Nullable T snapshot); + ctor @Deprecated public TaskState.InProgress(T snapshot); method @Deprecated public T getSnapshot(); - property public final T snapshot; + property @Deprecated public final T snapshot; } @Deprecated public static final class TaskState.Paused extends com.google.firebase.storage.ktx.TaskState { - ctor @Deprecated public TaskState.Paused(@Nullable T snapshot); + ctor @Deprecated public TaskState.Paused(T snapshot); method @Deprecated public T getSnapshot(); - property public final T snapshot; + property @Deprecated public final T snapshot; } } diff --git a/firebase-storage/ktx/api.txt b/firebase-storage/ktx/api.txt index 3d03b63fadb..da4f6cc18fe 100644 --- a/firebase-storage/ktx/api.txt +++ b/firebase-storage/ktx/api.txt @@ -1,8 +1 @@ -// Signature format: 2.0 -package com.google.firebase.storage.ktx { - - public final class LoggingKt { - } - -} - +// Signature format: 3.0 diff --git a/firebase-vertexai/CHANGELOG.md b/firebase-vertexai/CHANGELOG.md index 7554f0cd821..557a2f10589 100644 --- a/firebase-vertexai/CHANGELOG.md +++ b/firebase-vertexai/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +* [fixed] Added support for new values sent by the server for `FinishReason` and `BlockReason`. +* [changed] Added support for modality-based token count. (#6658) +# 16.1.0 +* [changed] Internal improvements to correctly handle empty model responses. # 16.0.2 * [fixed] Improved error message when using an invalid location. (#6428) @@ -61,4 +65,3 @@ * [feature] Added support for `responseMimeType` in `GenerationConfig`. * [changed] Renamed `GoogleGenerativeAIException` to `FirebaseVertexAIException`. * [changed] Updated the KDocs for various classes and functions. - diff --git a/firebase-vertexai/README.md b/firebase-vertexai/README.md index df5f5f15bf0..dc436cd6eb0 100644 --- a/firebase-vertexai/README.md +++ b/firebase-vertexai/README.md @@ -1,9 +1,5 @@ # Firebase Vertex AI SDK -**Preview**: Vertex AI for Firebase is in Public Preview, which means that the product is -not subject to any SLA or deprecation policy and could change in backwards-incompatible -ways. - For developer documentation, please visit https://firebase.google.com/docs/vertex-ai. This README is for contributors building and running tests for the SDK. @@ -30,7 +26,7 @@ Integration tests, requiring a running and connected device (emulator or real): ## Code Formatting Format Kotlin code in this SDK in Android Studio using -the [ktfmt plugin](https://plugins.jetbrains.com/plugin/14912-ktfmt) with code style to -**Google (internal)**, or by running: +the [spotless plugin]([https://plugins.jetbrains.com/plugin/14912-ktfmt](https://github.com/diffplug/spotless) +by running: -`./gradlew :firebase-vertexai:ktfmtFormat` +`./gradlew firebase-vertexai:spotlessApply` diff --git a/firebase-vertexai/api.txt b/firebase-vertexai/api.txt new file mode 100644 index 00000000000..02faa3674a4 --- /dev/null +++ b/firebase-vertexai/api.txt @@ -0,0 +1,599 @@ +// Signature format: 3.0 +package com.google.firebase.vertexai { + + public final class Chat { + ctor public Chat(com.google.firebase.vertexai.GenerativeModel model, java.util.List history = java.util.ArrayList()); + method public java.util.List getHistory(); + method public suspend Object? sendMessage(android.graphics.Bitmap prompt, kotlin.coroutines.Continuation); + method public suspend Object? sendMessage(com.google.firebase.vertexai.type.Content prompt, kotlin.coroutines.Continuation); + method public suspend Object? sendMessage(String prompt, kotlin.coroutines.Continuation); + method public kotlinx.coroutines.flow.Flow sendMessageStream(android.graphics.Bitmap prompt); + method public kotlinx.coroutines.flow.Flow sendMessageStream(com.google.firebase.vertexai.type.Content prompt); + method public kotlinx.coroutines.flow.Flow sendMessageStream(String prompt); + property public final java.util.List history; + } + + public final class FirebaseVertexAI { + method public com.google.firebase.vertexai.GenerativeModel generativeModel(String modelName); + method public com.google.firebase.vertexai.GenerativeModel generativeModel(String modelName, com.google.firebase.vertexai.type.GenerationConfig? generationConfig = null); + method public com.google.firebase.vertexai.GenerativeModel generativeModel(String modelName, com.google.firebase.vertexai.type.GenerationConfig? generationConfig = null, java.util.List? safetySettings = null); + method public com.google.firebase.vertexai.GenerativeModel generativeModel(String modelName, com.google.firebase.vertexai.type.GenerationConfig? generationConfig = null, java.util.List? safetySettings = null, java.util.List? tools = null); + method public com.google.firebase.vertexai.GenerativeModel generativeModel(String modelName, com.google.firebase.vertexai.type.GenerationConfig? generationConfig = null, java.util.List? safetySettings = null, java.util.List? tools = null, com.google.firebase.vertexai.type.ToolConfig? toolConfig = null); + method public com.google.firebase.vertexai.GenerativeModel generativeModel(String modelName, com.google.firebase.vertexai.type.GenerationConfig? generationConfig = null, java.util.List? safetySettings = null, java.util.List? tools = null, com.google.firebase.vertexai.type.ToolConfig? toolConfig = null, com.google.firebase.vertexai.type.Content? systemInstruction = null); + method public com.google.firebase.vertexai.GenerativeModel generativeModel(String modelName, com.google.firebase.vertexai.type.GenerationConfig? generationConfig = null, java.util.List? safetySettings = null, java.util.List? tools = null, com.google.firebase.vertexai.type.ToolConfig? toolConfig = null, com.google.firebase.vertexai.type.Content? systemInstruction = null, com.google.firebase.vertexai.type.RequestOptions requestOptions = com.google.firebase.vertexai.type.RequestOptions()); + method public static com.google.firebase.vertexai.FirebaseVertexAI getInstance(); + method public static com.google.firebase.vertexai.FirebaseVertexAI getInstance(com.google.firebase.FirebaseApp app); + method public static com.google.firebase.vertexai.FirebaseVertexAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, String location); + method public static com.google.firebase.vertexai.FirebaseVertexAI getInstance(String location); + property public static final com.google.firebase.vertexai.FirebaseVertexAI instance; + field public static final com.google.firebase.vertexai.FirebaseVertexAI.Companion Companion; + } + + public static final class FirebaseVertexAI.Companion { + method public com.google.firebase.vertexai.FirebaseVertexAI getInstance(); + method public com.google.firebase.vertexai.FirebaseVertexAI getInstance(com.google.firebase.FirebaseApp app); + method public com.google.firebase.vertexai.FirebaseVertexAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, String location); + method public com.google.firebase.vertexai.FirebaseVertexAI getInstance(String location); + property public final com.google.firebase.vertexai.FirebaseVertexAI instance; + } + + public final class FirebaseVertexAIKt { + method public static com.google.firebase.vertexai.FirebaseVertexAI getVertexAI(com.google.firebase.Firebase); + method public static com.google.firebase.vertexai.FirebaseVertexAI vertexAI(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app = Firebase.app, String location = "us-central1"); + } + + public final class GenerativeModel { + method public suspend Object? countTokens(android.graphics.Bitmap prompt, kotlin.coroutines.Continuation); + method public suspend Object? countTokens(com.google.firebase.vertexai.type.Content[] prompt, kotlin.coroutines.Continuation); + method public suspend Object? countTokens(String prompt, kotlin.coroutines.Continuation); + method public suspend Object? generateContent(android.graphics.Bitmap prompt, kotlin.coroutines.Continuation); + method public suspend Object? generateContent(com.google.firebase.vertexai.type.Content[] prompt, kotlin.coroutines.Continuation); + method public suspend Object? generateContent(String prompt, kotlin.coroutines.Continuation); + method public kotlinx.coroutines.flow.Flow generateContentStream(android.graphics.Bitmap prompt); + method public kotlinx.coroutines.flow.Flow generateContentStream(com.google.firebase.vertexai.type.Content... prompt); + method public kotlinx.coroutines.flow.Flow generateContentStream(String prompt); + method public com.google.firebase.vertexai.Chat startChat(java.util.List history = emptyList()); + } + +} + +package com.google.firebase.vertexai.java { + + public abstract class ChatFutures { + method public static final com.google.firebase.vertexai.java.ChatFutures from(com.google.firebase.vertexai.Chat chat); + method public abstract com.google.firebase.vertexai.Chat getChat(); + method public abstract com.google.common.util.concurrent.ListenableFuture sendMessage(com.google.firebase.vertexai.type.Content prompt); + method public abstract org.reactivestreams.Publisher sendMessageStream(com.google.firebase.vertexai.type.Content prompt); + field public static final com.google.firebase.vertexai.java.ChatFutures.Companion Companion; + } + + public static final class ChatFutures.Companion { + method public com.google.firebase.vertexai.java.ChatFutures from(com.google.firebase.vertexai.Chat chat); + } + + public abstract class GenerativeModelFutures { + method public abstract com.google.common.util.concurrent.ListenableFuture countTokens(com.google.firebase.vertexai.type.Content... prompt); + method public static final com.google.firebase.vertexai.java.GenerativeModelFutures from(com.google.firebase.vertexai.GenerativeModel model); + method public abstract com.google.common.util.concurrent.ListenableFuture generateContent(com.google.firebase.vertexai.type.Content... prompt); + method public abstract org.reactivestreams.Publisher generateContentStream(com.google.firebase.vertexai.type.Content... prompt); + method public abstract com.google.firebase.vertexai.GenerativeModel getGenerativeModel(); + method public abstract com.google.firebase.vertexai.java.ChatFutures startChat(); + method public abstract com.google.firebase.vertexai.java.ChatFutures startChat(java.util.List history); + field public static final com.google.firebase.vertexai.java.GenerativeModelFutures.Companion Companion; + } + + public static final class GenerativeModelFutures.Companion { + method public com.google.firebase.vertexai.java.GenerativeModelFutures from(com.google.firebase.vertexai.GenerativeModel model); + } + +} + +package com.google.firebase.vertexai.type { + + public final class BlockReason { + method public String getName(); + method public int getOrdinal(); + property public final String name; + property public final int ordinal; + field public static final com.google.firebase.vertexai.type.BlockReason BLOCKLIST; + field public static final com.google.firebase.vertexai.type.BlockReason.Companion Companion; + field public static final com.google.firebase.vertexai.type.BlockReason OTHER; + field public static final com.google.firebase.vertexai.type.BlockReason PROHIBITED_CONTENT; + field public static final com.google.firebase.vertexai.type.BlockReason SAFETY; + field public static final com.google.firebase.vertexai.type.BlockReason UNKNOWN; + } + + public static final class BlockReason.Companion { + } + + public final class Candidate { + method public com.google.firebase.vertexai.type.CitationMetadata? getCitationMetadata(); + method public com.google.firebase.vertexai.type.Content getContent(); + method public com.google.firebase.vertexai.type.FinishReason? getFinishReason(); + method public java.util.List getSafetyRatings(); + property public final com.google.firebase.vertexai.type.CitationMetadata? citationMetadata; + property public final com.google.firebase.vertexai.type.Content content; + property public final com.google.firebase.vertexai.type.FinishReason? finishReason; + property public final java.util.List safetyRatings; + } + + public final class Citation { + method public int getEndIndex(); + method public String? getLicense(); + method public java.util.Calendar? getPublicationDate(); + method public int getStartIndex(); + method public String? getTitle(); + method public String? getUri(); + property public final int endIndex; + property public final String? license; + property public final java.util.Calendar? publicationDate; + property public final int startIndex; + property public final String? title; + property public final String? uri; + } + + public final class CitationMetadata { + method public java.util.List getCitations(); + property public final java.util.List citations; + } + + public final class Content { + ctor public Content(String? role = "user", java.util.List parts); + ctor public Content(java.util.List parts); + method public com.google.firebase.vertexai.type.Content copy(String? role = role, java.util.List parts = parts); + method public java.util.List getParts(); + method public String? getRole(); + property public final java.util.List parts; + property public final String? role; + } + + public static final class Content.Builder { + ctor public Content.Builder(); + method public com.google.firebase.vertexai.type.Content.Builder addFileData(String uri, String mimeType); + method public com.google.firebase.vertexai.type.Content.Builder addImage(android.graphics.Bitmap image); + method public com.google.firebase.vertexai.type.Content.Builder addInlineData(byte[] bytes, String mimeType); + method public com.google.firebase.vertexai.type.Content.Builder addPart(T data); + method public com.google.firebase.vertexai.type.Content.Builder addText(String text); + method public com.google.firebase.vertexai.type.Content build(); + method public java.util.List getParts(); + method public String? getRole(); + method public void setParts(java.util.List); + method public void setRole(String?); + property public final java.util.List parts; + property public final String? role; + } + + public final class ContentKt { + method public static com.google.firebase.vertexai.type.Content content(String? role = "user", kotlin.jvm.functions.Function1 init); + } + + public final class ContentModality { + method public int getOrdinal(); + property public final int ordinal; + field public static final com.google.firebase.vertexai.type.ContentModality AUDIO; + field public static final com.google.firebase.vertexai.type.ContentModality.Companion Companion; + field public static final com.google.firebase.vertexai.type.ContentModality DOCUMENT; + field public static final com.google.firebase.vertexai.type.ContentModality IMAGE; + field public static final com.google.firebase.vertexai.type.ContentModality TEXT; + field public static final com.google.firebase.vertexai.type.ContentModality UNSPECIFIED; + field public static final com.google.firebase.vertexai.type.ContentModality VIDEO; + } + + public static final class ContentModality.Companion { + } + + public final class CountTokensResponse { + ctor public CountTokensResponse(int totalTokens, Integer? totalBillableCharacters = null, java.util.List promptTokensDetails = emptyList()); + method public operator int component1(); + method public operator Integer? component2(); + method public operator java.util.List? component3(); + method public java.util.List getPromptTokensDetails(); + method public Integer? getTotalBillableCharacters(); + method public int getTotalTokens(); + property public final java.util.List promptTokensDetails; + property public final Integer? totalBillableCharacters; + property public final int totalTokens; + } + + public final class FileDataPart implements com.google.firebase.vertexai.type.Part { + ctor public FileDataPart(String uri, String mimeType); + method public String getMimeType(); + method public String getUri(); + property public final String mimeType; + property public final String uri; + } + + public final class FinishReason { + method public String getName(); + method public int getOrdinal(); + property public final String name; + property public final int ordinal; + field public static final com.google.firebase.vertexai.type.FinishReason BLOCKLIST; + field public static final com.google.firebase.vertexai.type.FinishReason.Companion Companion; + field public static final com.google.firebase.vertexai.type.FinishReason MALFORMED_FUNCTION_CALL; + field public static final com.google.firebase.vertexai.type.FinishReason MAX_TOKENS; + field public static final com.google.firebase.vertexai.type.FinishReason OTHER; + field public static final com.google.firebase.vertexai.type.FinishReason PROHIBITED_CONTENT; + field public static final com.google.firebase.vertexai.type.FinishReason RECITATION; + field public static final com.google.firebase.vertexai.type.FinishReason SAFETY; + field public static final com.google.firebase.vertexai.type.FinishReason SPII; + field public static final com.google.firebase.vertexai.type.FinishReason STOP; + field public static final com.google.firebase.vertexai.type.FinishReason UNKNOWN; + } + + public static final class FinishReason.Companion { + } + + public abstract class FirebaseVertexAIException extends java.lang.RuntimeException { + } + + public final class FunctionCallPart implements com.google.firebase.vertexai.type.Part { + ctor public FunctionCallPart(String name, java.util.Map args); + method public java.util.Map getArgs(); + method public String getName(); + property public final java.util.Map args; + property public final String name; + } + + public final class FunctionCallingConfig { + method public static com.google.firebase.vertexai.type.FunctionCallingConfig any(); + method public static com.google.firebase.vertexai.type.FunctionCallingConfig any(java.util.List? allowedFunctionNames = null); + method public static com.google.firebase.vertexai.type.FunctionCallingConfig auto(); + method public static com.google.firebase.vertexai.type.FunctionCallingConfig none(); + field public static final com.google.firebase.vertexai.type.FunctionCallingConfig.Companion Companion; + } + + public static final class FunctionCallingConfig.Companion { + method public com.google.firebase.vertexai.type.FunctionCallingConfig any(); + method public com.google.firebase.vertexai.type.FunctionCallingConfig any(java.util.List? allowedFunctionNames = null); + method public com.google.firebase.vertexai.type.FunctionCallingConfig auto(); + method public com.google.firebase.vertexai.type.FunctionCallingConfig none(); + } + + public final class FunctionDeclaration { + ctor public FunctionDeclaration(String name, String description, java.util.Map parameters, java.util.List optionalParameters = emptyList()); + } + + public final class FunctionResponsePart implements com.google.firebase.vertexai.type.Part { + ctor public FunctionResponsePart(String name, kotlinx.serialization.json.JsonObject response); + method public String getName(); + method public kotlinx.serialization.json.JsonObject getResponse(); + property public final String name; + property public final kotlinx.serialization.json.JsonObject response; + } + + public final class GenerateContentResponse { + ctor public GenerateContentResponse(java.util.List candidates, com.google.firebase.vertexai.type.PromptFeedback? promptFeedback, com.google.firebase.vertexai.type.UsageMetadata? usageMetadata); + method public java.util.List getCandidates(); + method public java.util.List getFunctionCalls(); + method public com.google.firebase.vertexai.type.PromptFeedback? getPromptFeedback(); + method public String? getText(); + method public com.google.firebase.vertexai.type.UsageMetadata? getUsageMetadata(); + property public final java.util.List candidates; + property public final java.util.List functionCalls; + property public final com.google.firebase.vertexai.type.PromptFeedback? promptFeedback; + property public final String? text; + property public final com.google.firebase.vertexai.type.UsageMetadata? usageMetadata; + } + + public final class GenerationConfig { + field public static final com.google.firebase.vertexai.type.GenerationConfig.Companion Companion; + } + + public static final class GenerationConfig.Builder { + ctor public GenerationConfig.Builder(); + method public com.google.firebase.vertexai.type.GenerationConfig build(); + field public Integer? candidateCount; + field public Float? frequencyPenalty; + field public Integer? maxOutputTokens; + field public Float? presencePenalty; + field public String? responseMimeType; + field public com.google.firebase.vertexai.type.Schema? responseSchema; + field public java.util.List? stopSequences; + field public Float? temperature; + field public Integer? topK; + field public Float? topP; + } + + public static final class GenerationConfig.Companion { + method public com.google.firebase.vertexai.type.GenerationConfig.Builder builder(); + } + + public final class GenerationConfigKt { + method public static com.google.firebase.vertexai.type.GenerationConfig generationConfig(kotlin.jvm.functions.Function1 init); + } + + public final class HarmBlockMethod { + method public int getOrdinal(); + property public final int ordinal; + field public static final com.google.firebase.vertexai.type.HarmBlockMethod.Companion Companion; + field public static final com.google.firebase.vertexai.type.HarmBlockMethod PROBABILITY; + field public static final com.google.firebase.vertexai.type.HarmBlockMethod SEVERITY; + } + + public static final class HarmBlockMethod.Companion { + } + + public final class HarmBlockThreshold { + method public int getOrdinal(); + property public final int ordinal; + field public static final com.google.firebase.vertexai.type.HarmBlockThreshold.Companion Companion; + field public static final com.google.firebase.vertexai.type.HarmBlockThreshold LOW_AND_ABOVE; + field public static final com.google.firebase.vertexai.type.HarmBlockThreshold MEDIUM_AND_ABOVE; + field public static final com.google.firebase.vertexai.type.HarmBlockThreshold NONE; + field public static final com.google.firebase.vertexai.type.HarmBlockThreshold ONLY_HIGH; + } + + public static final class HarmBlockThreshold.Companion { + } + + public final class HarmCategory { + method public int getOrdinal(); + property public final int ordinal; + field public static final com.google.firebase.vertexai.type.HarmCategory CIVIC_INTEGRITY; + field public static final com.google.firebase.vertexai.type.HarmCategory.Companion Companion; + field public static final com.google.firebase.vertexai.type.HarmCategory DANGEROUS_CONTENT; + field public static final com.google.firebase.vertexai.type.HarmCategory HARASSMENT; + field public static final com.google.firebase.vertexai.type.HarmCategory HATE_SPEECH; + field public static final com.google.firebase.vertexai.type.HarmCategory SEXUALLY_EXPLICIT; + field public static final com.google.firebase.vertexai.type.HarmCategory UNKNOWN; + } + + public static final class HarmCategory.Companion { + } + + public final class HarmProbability { + method public int getOrdinal(); + property public final int ordinal; + field public static final com.google.firebase.vertexai.type.HarmProbability.Companion Companion; + field public static final com.google.firebase.vertexai.type.HarmProbability HIGH; + field public static final com.google.firebase.vertexai.type.HarmProbability LOW; + field public static final com.google.firebase.vertexai.type.HarmProbability MEDIUM; + field public static final com.google.firebase.vertexai.type.HarmProbability NEGLIGIBLE; + field public static final com.google.firebase.vertexai.type.HarmProbability UNKNOWN; + } + + public static final class HarmProbability.Companion { + } + + public final class HarmSeverity { + method public int getOrdinal(); + property public final int ordinal; + field public static final com.google.firebase.vertexai.type.HarmSeverity.Companion Companion; + field public static final com.google.firebase.vertexai.type.HarmSeverity HIGH; + field public static final com.google.firebase.vertexai.type.HarmSeverity LOW; + field public static final com.google.firebase.vertexai.type.HarmSeverity MEDIUM; + field public static final com.google.firebase.vertexai.type.HarmSeverity NEGLIGIBLE; + field public static final com.google.firebase.vertexai.type.HarmSeverity UNKNOWN; + } + + public static final class HarmSeverity.Companion { + } + + public final class ImagePart implements com.google.firebase.vertexai.type.Part { + ctor public ImagePart(android.graphics.Bitmap image); + method public android.graphics.Bitmap getImage(); + property public final android.graphics.Bitmap image; + } + + public final class InlineDataPart implements com.google.firebase.vertexai.type.Part { + ctor public InlineDataPart(byte[] inlineData, String mimeType); + method public byte[] getInlineData(); + method public String getMimeType(); + property public final byte[] inlineData; + property public final String mimeType; + } + + public final class InvalidAPIKeyException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public final class InvalidLocationException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public final class InvalidStateException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public final class ModalityTokenCount { + method public operator com.google.firebase.vertexai.type.ContentModality component1(); + method public operator int component2(); + method public com.google.firebase.vertexai.type.ContentModality getModality(); + method public int getTokenCount(); + property public final com.google.firebase.vertexai.type.ContentModality modality; + property public final int tokenCount; + } + + public interface Part { + } + + public final class PartKt { + method public static com.google.firebase.vertexai.type.FileDataPart? asFileDataOrNull(com.google.firebase.vertexai.type.Part); + method public static android.graphics.Bitmap? asImageOrNull(com.google.firebase.vertexai.type.Part); + method public static com.google.firebase.vertexai.type.InlineDataPart? asInlineDataPartOrNull(com.google.firebase.vertexai.type.Part); + method public static String? asTextOrNull(com.google.firebase.vertexai.type.Part); + } + + public final class PromptBlockedException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + method public com.google.firebase.vertexai.type.GenerateContentResponse getResponse(); + property public final com.google.firebase.vertexai.type.GenerateContentResponse response; + } + + public final class PromptFeedback { + ctor public PromptFeedback(com.google.firebase.vertexai.type.BlockReason? blockReason, java.util.List safetyRatings, String? blockReasonMessage); + method public com.google.firebase.vertexai.type.BlockReason? getBlockReason(); + method public String? getBlockReasonMessage(); + method public java.util.List getSafetyRatings(); + property public final com.google.firebase.vertexai.type.BlockReason? blockReason; + property public final String? blockReasonMessage; + property public final java.util.List safetyRatings; + } + + public final class RequestOptions { + ctor public RequestOptions(); + ctor public RequestOptions(long timeoutInMillis = 180.seconds.inWholeMilliseconds); + } + + public final class RequestTimeoutException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public final class ResponseStoppedException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + method public com.google.firebase.vertexai.type.GenerateContentResponse getResponse(); + property public final com.google.firebase.vertexai.type.GenerateContentResponse response; + } + + public final class SafetyRating { + method public Boolean? getBlocked(); + method public com.google.firebase.vertexai.type.HarmCategory getCategory(); + method public com.google.firebase.vertexai.type.HarmProbability getProbability(); + method public float getProbabilityScore(); + method public com.google.firebase.vertexai.type.HarmSeverity? getSeverity(); + method public Float? getSeverityScore(); + property public final Boolean? blocked; + property public final com.google.firebase.vertexai.type.HarmCategory category; + property public final com.google.firebase.vertexai.type.HarmProbability probability; + property public final float probabilityScore; + property public final com.google.firebase.vertexai.type.HarmSeverity? severity; + property public final Float? severityScore; + } + + public final class SafetySetting { + ctor public SafetySetting(com.google.firebase.vertexai.type.HarmCategory harmCategory, com.google.firebase.vertexai.type.HarmBlockThreshold threshold, com.google.firebase.vertexai.type.HarmBlockMethod? method = null); + } + + public final class Schema { + method public static com.google.firebase.vertexai.type.Schema array(com.google.firebase.vertexai.type.Schema items); + method public static com.google.firebase.vertexai.type.Schema array(com.google.firebase.vertexai.type.Schema items, String? description = null); + method public static com.google.firebase.vertexai.type.Schema array(com.google.firebase.vertexai.type.Schema items, String? description = null, boolean nullable = false); + method public static com.google.firebase.vertexai.type.Schema boolean(); + method public static com.google.firebase.vertexai.type.Schema boolean(String? description = null); + method public static com.google.firebase.vertexai.type.Schema boolean(String? description = null, boolean nullable = false); + method public static com.google.firebase.vertexai.type.Schema enumeration(java.util.List values); + method public static com.google.firebase.vertexai.type.Schema enumeration(java.util.List values, String? description = null); + method public static com.google.firebase.vertexai.type.Schema enumeration(java.util.List values, String? description = null, boolean nullable = false); + method public String? getDescription(); + method public java.util.List? getEnum(); + method public String? getFormat(); + method public com.google.firebase.vertexai.type.Schema? getItems(); + method public Boolean? getNullable(); + method public java.util.Map? getProperties(); + method public java.util.List? getRequired(); + method public String getType(); + method public static com.google.firebase.vertexai.type.Schema numDouble(); + method public static com.google.firebase.vertexai.type.Schema numDouble(String? description = null); + method public static com.google.firebase.vertexai.type.Schema numDouble(String? description = null, boolean nullable = false); + method public static com.google.firebase.vertexai.type.Schema numFloat(); + method public static com.google.firebase.vertexai.type.Schema numFloat(String? description = null); + method public static com.google.firebase.vertexai.type.Schema numFloat(String? description = null, boolean nullable = false); + method public static com.google.firebase.vertexai.type.Schema numInt(); + method public static com.google.firebase.vertexai.type.Schema numInt(String? description = null); + method public static com.google.firebase.vertexai.type.Schema numInt(String? description = null, boolean nullable = false); + method public static com.google.firebase.vertexai.type.Schema numLong(); + method public static com.google.firebase.vertexai.type.Schema numLong(String? description = null); + method public static com.google.firebase.vertexai.type.Schema numLong(String? description = null, boolean nullable = false); + method public static com.google.firebase.vertexai.type.Schema obj(java.util.Map properties); + method public static com.google.firebase.vertexai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList()); + method public static com.google.firebase.vertexai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null); + method public static com.google.firebase.vertexai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null, boolean nullable = false); + method public static com.google.firebase.vertexai.type.Schema str(); + method public static com.google.firebase.vertexai.type.Schema str(String? description = null); + method public static com.google.firebase.vertexai.type.Schema str(String? description = null, boolean nullable = false); + method public static com.google.firebase.vertexai.type.Schema str(String? description = null, boolean nullable = false, com.google.firebase.vertexai.type.StringFormat? format = null); + property public final String? description; + property public final java.util.List? enum; + property public final String? format; + property public final com.google.firebase.vertexai.type.Schema? items; + property public final Boolean? nullable; + property public final java.util.Map? properties; + property public final java.util.List? required; + property public final String type; + field public static final com.google.firebase.vertexai.type.Schema.Companion Companion; + } + + public static final class Schema.Companion { + method public com.google.firebase.vertexai.type.Schema array(com.google.firebase.vertexai.type.Schema items); + method public com.google.firebase.vertexai.type.Schema array(com.google.firebase.vertexai.type.Schema items, String? description = null); + method public com.google.firebase.vertexai.type.Schema array(com.google.firebase.vertexai.type.Schema items, String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema boolean(); + method public com.google.firebase.vertexai.type.Schema boolean(String? description = null); + method public com.google.firebase.vertexai.type.Schema boolean(String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema enumeration(java.util.List values); + method public com.google.firebase.vertexai.type.Schema enumeration(java.util.List values, String? description = null); + method public com.google.firebase.vertexai.type.Schema enumeration(java.util.List values, String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema numDouble(); + method public com.google.firebase.vertexai.type.Schema numDouble(String? description = null); + method public com.google.firebase.vertexai.type.Schema numDouble(String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema numFloat(); + method public com.google.firebase.vertexai.type.Schema numFloat(String? description = null); + method public com.google.firebase.vertexai.type.Schema numFloat(String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema numInt(); + method public com.google.firebase.vertexai.type.Schema numInt(String? description = null); + method public com.google.firebase.vertexai.type.Schema numInt(String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema numLong(); + method public com.google.firebase.vertexai.type.Schema numLong(String? description = null); + method public com.google.firebase.vertexai.type.Schema numLong(String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema obj(java.util.Map properties); + method public com.google.firebase.vertexai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList()); + method public com.google.firebase.vertexai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null); + method public com.google.firebase.vertexai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema str(); + method public com.google.firebase.vertexai.type.Schema str(String? description = null); + method public com.google.firebase.vertexai.type.Schema str(String? description = null, boolean nullable = false); + method public com.google.firebase.vertexai.type.Schema str(String? description = null, boolean nullable = false, com.google.firebase.vertexai.type.StringFormat? format = null); + } + + public final class SerializationException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public final class ServerException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public final class ServiceDisabledException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public abstract class StringFormat { + } + + public static final class StringFormat.Custom extends com.google.firebase.vertexai.type.StringFormat { + ctor public StringFormat.Custom(String value); + } + + public final class TextPart implements com.google.firebase.vertexai.type.Part { + ctor public TextPart(String text); + method public String getText(); + property public final String text; + } + + public final class Tool { + method public static com.google.firebase.vertexai.type.Tool functionDeclarations(java.util.List functionDeclarations); + field public static final com.google.firebase.vertexai.type.Tool.Companion Companion; + } + + public static final class Tool.Companion { + method public com.google.firebase.vertexai.type.Tool functionDeclarations(java.util.List functionDeclarations); + } + + public final class ToolConfig { + ctor public ToolConfig(com.google.firebase.vertexai.type.FunctionCallingConfig? functionCallingConfig); + } + + public final class UnknownException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public final class UnsupportedUserLocationException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + public final class UsageMetadata { + ctor public UsageMetadata(int promptTokenCount, Integer? candidatesTokenCount, int totalTokenCount, java.util.List promptTokensDetails, java.util.List candidatesTokensDetails); + method public Integer? getCandidatesTokenCount(); + method public java.util.List getCandidatesTokensDetails(); + method public int getPromptTokenCount(); + method public java.util.List getPromptTokensDetails(); + method public int getTotalTokenCount(); + property public final Integer? candidatesTokenCount; + property public final java.util.List candidatesTokensDetails; + property public final int promptTokenCount; + property public final java.util.List promptTokensDetails; + property public final int totalTokenCount; + } + +} + diff --git a/firebase-vertexai/consumer-rules.pro b/firebase-vertexai/consumer-rules.pro index 7947f53cd58..f328794a748 100644 --- a/firebase-vertexai/consumer-rules.pro +++ b/firebase-vertexai/consumer-rules.pro @@ -20,4 +20,5 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile +-keep class com.google.firebase.vertexai.type.** { *; } -keep class com.google.firebase.vertexai.common.** { *; } diff --git a/firebase-vertexai/gradle.properties b/firebase-vertexai/gradle.properties index 66585dec24c..b686fdcb9db 100644 --- a/firebase-vertexai/gradle.properties +++ b/firebase-vertexai/gradle.properties @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=16.1.0 -latestReleasedVersion=16.0.2 +version=16.2.0 +latestReleasedVersion=16.1.0 diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/GenerativeModel.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/GenerativeModel.kt index 45c6aa9bce4..c2b9fcfd2f9 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/GenerativeModel.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/GenerativeModel.kt @@ -24,8 +24,6 @@ import com.google.firebase.vertexai.common.APIController import com.google.firebase.vertexai.common.CountTokensRequest import com.google.firebase.vertexai.common.GenerateContentRequest import com.google.firebase.vertexai.common.HeaderProvider -import com.google.firebase.vertexai.internal.util.toInternal -import com.google.firebase.vertexai.internal.util.toPublic import com.google.firebase.vertexai.type.Content import com.google.firebase.vertexai.type.CountTokensResponse import com.google.firebase.vertexai.type.FinishReason diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/APIController.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/APIController.kt index cbcc0c69e97..286b8829241 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/APIController.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/APIController.kt @@ -19,12 +19,14 @@ package com.google.firebase.vertexai.common import android.util.Log import com.google.firebase.Firebase import com.google.firebase.options -import com.google.firebase.vertexai.common.server.FinishReason -import com.google.firebase.vertexai.common.server.GRpcError -import com.google.firebase.vertexai.common.server.GRpcErrorDetails import com.google.firebase.vertexai.common.util.decodeToFlow import com.google.firebase.vertexai.common.util.fullModelName +import com.google.firebase.vertexai.type.CountTokensResponse +import com.google.firebase.vertexai.type.FinishReason +import com.google.firebase.vertexai.type.GRpcErrorResponse +import com.google.firebase.vertexai.type.GenerateContentResponse import com.google.firebase.vertexai.type.RequestOptions +import com.google.firebase.vertexai.type.Response import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.HttpClientEngine @@ -106,7 +108,7 @@ internal constructor( install(ContentNegotiation) { json(JSON) } } - suspend fun generateContent(request: GenerateContentRequest): GenerateContentResponse = + suspend fun generateContent(request: GenerateContentRequest): GenerateContentResponse.Internal = try { client .post("${requestOptions.endpoint}/${requestOptions.apiVersion}/$model:generateContent") { @@ -114,15 +116,17 @@ internal constructor( applyHeaderProvider() } .also { validateResponse(it) } - .body() + .body() .validate() } catch (e: Throwable) { throw FirebaseCommonAIException.from(e) } - fun generateContentStream(request: GenerateContentRequest): Flow = + fun generateContentStream( + request: GenerateContentRequest + ): Flow = client - .postStream( + .postStream( "${requestOptions.endpoint}/${requestOptions.apiVersion}/$model:streamGenerateContent?alt=sse" ) { applyCommonConfiguration(request) @@ -130,7 +134,7 @@ internal constructor( .map { it.validate() } .catch { throw FirebaseCommonAIException.from(it) } - suspend fun countTokens(request: CountTokensRequest): CountTokensResponse = + suspend fun countTokens(request: CountTokensRequest): CountTokensResponse.Internal = try { client .post("${requestOptions.endpoint}/${requestOptions.apiVersion}/$model:countTokens") { @@ -275,19 +279,21 @@ private suspend fun validateResponse(response: HttpResponse) { throw ServerException(message) } -private fun getServiceDisabledErrorDetailsOrNull(error: GRpcError): GRpcErrorDetails? { +private fun getServiceDisabledErrorDetailsOrNull( + error: GRpcErrorResponse.GRpcError +): GRpcErrorResponse.GRpcError.GRpcErrorDetails? { return error.details?.firstOrNull { it.reason == "SERVICE_DISABLED" && it.domain == "googleapis.com" } } -private fun GenerateContentResponse.validate() = apply { +private fun GenerateContentResponse.Internal.validate() = apply { if ((candidates?.isEmpty() != false) && promptFeedback == null) { throw SerializationException("Error deserializing response, found no valid fields") } promptFeedback?.blockReason?.let { throw PromptBlockedException(this) } candidates ?.mapNotNull { it.finishReason } - ?.firstOrNull { it != FinishReason.STOP } + ?.firstOrNull { it != FinishReason.Internal.STOP } ?.let { throw ResponseStoppedException(this) } } diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Exceptions.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Exceptions.kt index 41954b13497..7567c384618 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Exceptions.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Exceptions.kt @@ -16,6 +16,7 @@ package com.google.firebase.vertexai.common +import com.google.firebase.vertexai.type.GenerateContentResponse import io.ktor.serialization.JsonConvertException import kotlinx.coroutines.TimeoutCancellationException @@ -66,7 +67,7 @@ internal class InvalidAPIKeyException(message: String, cause: Throwable? = null) * @property response the full server response for the request. */ internal class PromptBlockedException( - val response: GenerateContentResponse, + val response: GenerateContentResponse.Internal, cause: Throwable? = null ) : FirebaseCommonAIException( @@ -98,7 +99,7 @@ internal class InvalidStateException(message: String, cause: Throwable? = null) * @property response the full server response for the request */ internal class ResponseStoppedException( - val response: GenerateContentResponse, + val response: GenerateContentResponse.Internal, cause: Throwable? = null ) : FirebaseCommonAIException( @@ -125,3 +126,18 @@ internal class ServiceDisabledException(message: String, cause: Throwable? = nul /** Catch all case for exceptions not explicitly expected. */ internal class UnknownException(message: String, cause: Throwable? = null) : FirebaseCommonAIException(message, cause) + +internal fun makeMissingCaseException( + source: String, + ordinal: Int +): com.google.firebase.vertexai.type.SerializationException { + return com.google.firebase.vertexai.type.SerializationException( + """ + |Missing case for a $source: $ordinal + |This error indicates that one of the `toInternal` conversions needs updating. + |If you're a developer seeing this exception, please file an issue on our GitHub repo: + |https://github.com/firebase/firebase-android-sdk + """ + .trimMargin() + ) +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Request.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Request.kt index 39adea5629c..040a38e0a0b 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Request.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Request.kt @@ -16,12 +16,12 @@ package com.google.firebase.vertexai.common -import com.google.firebase.vertexai.common.client.GenerationConfig -import com.google.firebase.vertexai.common.client.Tool -import com.google.firebase.vertexai.common.client.ToolConfig -import com.google.firebase.vertexai.common.shared.Content -import com.google.firebase.vertexai.common.shared.SafetySetting import com.google.firebase.vertexai.common.util.fullModelName +import com.google.firebase.vertexai.type.Content +import com.google.firebase.vertexai.type.GenerationConfig +import com.google.firebase.vertexai.type.SafetySetting +import com.google.firebase.vertexai.type.Tool +import com.google.firebase.vertexai.type.ToolConfig import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -30,21 +30,21 @@ internal sealed interface Request @Serializable internal data class GenerateContentRequest( val model: String? = null, - val contents: List, - @SerialName("safety_settings") val safetySettings: List? = null, - @SerialName("generation_config") val generationConfig: GenerationConfig? = null, - val tools: List? = null, - @SerialName("tool_config") var toolConfig: ToolConfig? = null, - @SerialName("system_instruction") val systemInstruction: Content? = null, + val contents: List, + @SerialName("safety_settings") val safetySettings: List? = null, + @SerialName("generation_config") val generationConfig: GenerationConfig.Internal? = null, + val tools: List? = null, + @SerialName("tool_config") var toolConfig: ToolConfig.Internal? = null, + @SerialName("system_instruction") val systemInstruction: Content.Internal? = null, ) : Request @Serializable internal data class CountTokensRequest( val generateContentRequest: GenerateContentRequest? = null, val model: String? = null, - val contents: List? = null, - val tools: List? = null, - @SerialName("system_instruction") val systemInstruction: Content? = null, + val contents: List? = null, + val tools: List? = null, + @SerialName("system_instruction") val systemInstruction: Content.Internal? = null, ) : Request { companion object { fun forGenAI(generateContentRequest: GenerateContentRequest) = diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Response.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Response.kt deleted file mode 100644 index d8182883442..00000000000 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/Response.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024 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.vertexai.common - -import com.google.firebase.vertexai.common.server.Candidate -import com.google.firebase.vertexai.common.server.GRpcError -import com.google.firebase.vertexai.common.server.PromptFeedback -import kotlinx.serialization.Serializable - -internal sealed interface Response - -@Serializable -internal data class GenerateContentResponse( - val candidates: List? = null, - val promptFeedback: PromptFeedback? = null, - val usageMetadata: UsageMetadata? = null, -) : Response - -@Serializable -internal data class CountTokensResponse( - val totalTokens: Int, - val totalBillableCharacters: Int? = null -) : Response - -@Serializable internal data class GRpcErrorResponse(val error: GRpcError) : Response - -@Serializable -internal data class UsageMetadata( - val promptTokenCount: Int? = null, - val candidatesTokenCount: Int? = null, - val totalTokenCount: Int? = null, -) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/client/Types.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/client/Types.kt deleted file mode 100644 index b950aa3c5f2..00000000000 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/client/Types.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2024 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.vertexai.common.client - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonObject - -@Serializable -internal data class GenerationConfig( - val temperature: Float?, - @SerialName("top_p") val topP: Float?, - @SerialName("top_k") val topK: Int?, - @SerialName("candidate_count") val candidateCount: Int?, - @SerialName("max_output_tokens") val maxOutputTokens: Int?, - @SerialName("stop_sequences") val stopSequences: List?, - @SerialName("response_mime_type") val responseMimeType: String? = null, - @SerialName("presence_penalty") val presencePenalty: Float? = null, - @SerialName("frequency_penalty") val frequencyPenalty: Float? = null, - @SerialName("response_schema") val responseSchema: Schema? = null, -) - -@Serializable -internal data class Tool( - val functionDeclarations: List? = null, - // This is a json object because it is not possible to make a data class with no parameters. - val codeExecution: JsonObject? = null, -) - -@Serializable -internal data class ToolConfig( - @SerialName("function_calling_config") val functionCallingConfig: FunctionCallingConfig? -) - -@Serializable -internal data class FunctionCallingConfig( - val mode: Mode, - @SerialName("allowed_function_names") val allowedFunctionNames: List? = null -) { - @Serializable - enum class Mode { - @SerialName("MODE_UNSPECIFIED") UNSPECIFIED, - AUTO, - ANY, - NONE - } -} - -@Serializable -internal data class FunctionDeclaration( - val name: String, - val description: String, - val parameters: Schema -) - -@Serializable -internal data class Schema( - val type: String, - val description: String? = null, - val format: String? = null, - val nullable: Boolean? = false, - val enum: List? = null, - val properties: Map? = null, - val required: List? = null, - val items: Schema? = null, -) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/server/Types.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/server/Types.kt deleted file mode 100644 index 3749d534e47..00000000000 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/server/Types.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2024 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.vertexai.common.server - -import com.google.firebase.vertexai.common.shared.Content -import com.google.firebase.vertexai.common.shared.HarmCategory -import com.google.firebase.vertexai.common.util.FirstOrdinalSerializer -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonNames - -internal object BlockReasonSerializer : - KSerializer by FirstOrdinalSerializer(BlockReason::class) - -internal object HarmProbabilitySerializer : - KSerializer by FirstOrdinalSerializer(HarmProbability::class) - -internal object HarmSeveritySerializer : - KSerializer by FirstOrdinalSerializer(HarmSeverity::class) - -internal object FinishReasonSerializer : - KSerializer by FirstOrdinalSerializer(FinishReason::class) - -@Serializable -internal data class PromptFeedback( - val blockReason: BlockReason? = null, - val safetyRatings: List? = null, - val blockReasonMessage: String? = null, -) - -@Serializable(BlockReasonSerializer::class) -internal enum class BlockReason { - UNKNOWN, - @SerialName("BLOCKED_REASON_UNSPECIFIED") UNSPECIFIED, - SAFETY, - OTHER -} - -@Serializable -internal data class Candidate( - val content: Content? = null, - val finishReason: FinishReason? = null, - val safetyRatings: List? = null, - val citationMetadata: CitationMetadata? = null, - val groundingMetadata: GroundingMetadata? = null, -) - -@Serializable -internal data class CitationMetadata -@OptIn(ExperimentalSerializationApi::class) -internal constructor(@JsonNames("citations") val citationSources: List) - -@Serializable -internal data class CitationSources( - val title: String? = null, - val startIndex: Int = 0, - val endIndex: Int, - val uri: String? = null, - val license: String? = null, - val publicationDate: Date? = null, -) - -@Serializable -internal data class Date( - /** Year of the date. Must be between 1 and 9999, or 0 for no year. */ - val year: Int? = null, - /** 1-based index for month. Must be from 1 to 12, or 0 to specify a year without a month. */ - val month: Int? = null, - /** - * Day of a month. Must be from 1 to 31 and valid for the year and month, or 0 to specify a year - * by itself or a year and month where the day isn't significant. - */ - val day: Int? = null, -) - -@Serializable -internal data class SafetyRating( - val category: HarmCategory, - val probability: HarmProbability, - val blocked: Boolean? = null, // TODO(): any reason not to default to false? - val probabilityScore: Float? = null, - val severity: HarmSeverity? = null, - val severityScore: Float? = null, -) - -@Serializable -internal data class GroundingMetadata( - @SerialName("web_search_queries") val webSearchQueries: List?, - @SerialName("search_entry_point") val searchEntryPoint: SearchEntryPoint?, - @SerialName("retrieval_queries") val retrievalQueries: List?, - @SerialName("grounding_attribution") val groundingAttribution: List?, -) - -@Serializable -internal data class SearchEntryPoint( - @SerialName("rendered_content") val renderedContent: String?, - @SerialName("sdk_blob") val sdkBlob: String?, -) - -@Serializable -internal data class GroundingAttribution( - val segment: Segment, - @SerialName("confidence_score") val confidenceScore: Float?, -) - -@Serializable -internal data class Segment( - @SerialName("start_index") val startIndex: Int, - @SerialName("end_index") val endIndex: Int, -) - -@Serializable(HarmProbabilitySerializer::class) -internal enum class HarmProbability { - UNKNOWN, - @SerialName("HARM_PROBABILITY_UNSPECIFIED") UNSPECIFIED, - NEGLIGIBLE, - LOW, - MEDIUM, - HIGH -} - -@Serializable(HarmSeveritySerializer::class) -internal enum class HarmSeverity { - UNKNOWN, - @SerialName("HARM_SEVERITY_UNSPECIFIED") UNSPECIFIED, - @SerialName("HARM_SEVERITY_NEGLIGIBLE") NEGLIGIBLE, - @SerialName("HARM_SEVERITY_LOW") LOW, - @SerialName("HARM_SEVERITY_MEDIUM") MEDIUM, - @SerialName("HARM_SEVERITY_HIGH") HIGH -} - -@Serializable(FinishReasonSerializer::class) -internal enum class FinishReason { - UNKNOWN, - @SerialName("FINISH_REASON_UNSPECIFIED") UNSPECIFIED, - STOP, - MAX_TOKENS, - SAFETY, - RECITATION, - OTHER -} - -@Serializable -internal data class GRpcError( - val code: Int, - val message: String, - val details: List? = null -) - -@Serializable -internal data class GRpcErrorDetails( - val reason: String? = null, - val domain: String? = null, - val metadata: Map? = null -) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/shared/Types.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/shared/Types.kt deleted file mode 100644 index b32772e995c..00000000000 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/shared/Types.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2024 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.vertexai.common.shared - -import com.google.firebase.vertexai.common.util.FirstOrdinalSerializer -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.EncodeDefault -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.JsonContentPolymorphicSerializer -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject - -internal object HarmCategorySerializer : - KSerializer by FirstOrdinalSerializer(HarmCategory::class) - -@Serializable(HarmCategorySerializer::class) -internal enum class HarmCategory { - UNKNOWN, - @SerialName("HARM_CATEGORY_HARASSMENT") HARASSMENT, - @SerialName("HARM_CATEGORY_HATE_SPEECH") HATE_SPEECH, - @SerialName("HARM_CATEGORY_SEXUALLY_EXPLICIT") SEXUALLY_EXPLICIT, - @SerialName("HARM_CATEGORY_DANGEROUS_CONTENT") DANGEROUS_CONTENT, - @SerialName("HARM_CATEGORY_CIVIC_INTEGRITY") CIVIC_INTEGRITY, -} - -internal typealias Base64 = String - -@ExperimentalSerializationApi -@Serializable -internal data class Content(@EncodeDefault val role: String? = "user", val parts: List) - -@Serializable(PartSerializer::class) internal sealed interface Part - -@Serializable internal data class TextPart(val text: String) : Part - -@Serializable -internal data class InlineDataPart(@SerialName("inline_data") val inlineData: InlineData) : Part - -@Serializable internal data class FunctionCallPart(val functionCall: FunctionCall) : Part - -@Serializable -internal data class FunctionResponsePart(val functionResponse: FunctionResponse) : Part - -@Serializable internal data class FunctionResponse(val name: String, val response: JsonObject) - -@Serializable -internal data class FunctionCall(val name: String, val args: Map? = null) - -@Serializable -internal data class FileDataPart(@SerialName("file_data") val fileData: FileData) : Part - -@Serializable -internal data class FileData( - @SerialName("mime_type") val mimeType: String, - @SerialName("file_uri") val fileUri: String, -) - -@Serializable -internal data class InlineData(@SerialName("mime_type") val mimeType: String, val data: Base64) - -@Serializable -internal data class SafetySetting( - val category: HarmCategory, - val threshold: HarmBlockThreshold, - val method: HarmBlockMethod? = null, -) - -@Serializable -internal enum class HarmBlockThreshold { - @SerialName("HARM_BLOCK_THRESHOLD_UNSPECIFIED") UNSPECIFIED, - BLOCK_LOW_AND_ABOVE, - BLOCK_MEDIUM_AND_ABOVE, - BLOCK_ONLY_HIGH, - BLOCK_NONE, -} - -@Serializable -internal enum class HarmBlockMethod { - @SerialName("HARM_BLOCK_METHOD_UNSPECIFIED") UNSPECIFIED, - SEVERITY, - PROBABILITY, -} - -internal object PartSerializer : JsonContentPolymorphicSerializer(Part::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { - val jsonObject = element.jsonObject - return when { - "text" in jsonObject -> TextPart.serializer() - "functionCall" in jsonObject -> FunctionCallPart.serializer() - "functionResponse" in jsonObject -> FunctionResponsePart.serializer() - "inlineData" in jsonObject -> InlineDataPart.serializer() - "fileData" in jsonObject -> FileDataPart.serializer() - else -> throw SerializationException("Unknown Part type") - } - } -} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/conversions.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/conversions.kt deleted file mode 100644 index f8388054260..00000000000 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/conversions.kt +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Copyright 2023 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.vertexai.internal.util - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.util.Base64 -import com.google.firebase.vertexai.common.client.Schema -import com.google.firebase.vertexai.common.shared.FileData -import com.google.firebase.vertexai.common.shared.FunctionCall -import com.google.firebase.vertexai.common.shared.FunctionCallPart -import com.google.firebase.vertexai.common.shared.FunctionResponse -import com.google.firebase.vertexai.common.shared.FunctionResponsePart -import com.google.firebase.vertexai.common.shared.InlineData -import com.google.firebase.vertexai.type.BlockReason -import com.google.firebase.vertexai.type.Candidate -import com.google.firebase.vertexai.type.Citation -import com.google.firebase.vertexai.type.CitationMetadata -import com.google.firebase.vertexai.type.Content -import com.google.firebase.vertexai.type.CountTokensResponse -import com.google.firebase.vertexai.type.FileDataPart -import com.google.firebase.vertexai.type.FinishReason -import com.google.firebase.vertexai.type.FunctionCallingConfig -import com.google.firebase.vertexai.type.FunctionDeclaration -import com.google.firebase.vertexai.type.GenerateContentResponse -import com.google.firebase.vertexai.type.GenerationConfig -import com.google.firebase.vertexai.type.HarmBlockMethod -import com.google.firebase.vertexai.type.HarmBlockThreshold -import com.google.firebase.vertexai.type.HarmCategory -import com.google.firebase.vertexai.type.HarmProbability -import com.google.firebase.vertexai.type.HarmSeverity -import com.google.firebase.vertexai.type.ImagePart -import com.google.firebase.vertexai.type.InlineDataPart -import com.google.firebase.vertexai.type.Part -import com.google.firebase.vertexai.type.PromptFeedback -import com.google.firebase.vertexai.type.SafetyRating -import com.google.firebase.vertexai.type.SafetySetting -import com.google.firebase.vertexai.type.SerializationException -import com.google.firebase.vertexai.type.TextPart -import com.google.firebase.vertexai.type.Tool -import com.google.firebase.vertexai.type.ToolConfig -import com.google.firebase.vertexai.type.UsageMetadata -import com.google.firebase.vertexai.type.content -import java.io.ByteArrayOutputStream -import java.util.Calendar -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonObject -import org.json.JSONObject - -private const val BASE_64_FLAGS = Base64.NO_WRAP - -internal fun Content.toInternal() = - com.google.firebase.vertexai.common.shared.Content( - this.role ?: "user", - this.parts.map { it.toInternal() } - ) - -internal fun Part.toInternal(): com.google.firebase.vertexai.common.shared.Part { - return when (this) { - is TextPart -> com.google.firebase.vertexai.common.shared.TextPart(text) - is ImagePart -> - com.google.firebase.vertexai.common.shared.InlineDataPart( - InlineData("image/jpeg", encodeBitmapToBase64Png(image)) - ) - is InlineDataPart -> - com.google.firebase.vertexai.common.shared.InlineDataPart( - InlineData(mimeType, Base64.encodeToString(inlineData, BASE_64_FLAGS)) - ) - is com.google.firebase.vertexai.type.FunctionCallPart -> - FunctionCallPart(FunctionCall(name, args)) - is com.google.firebase.vertexai.type.FunctionResponsePart -> - FunctionResponsePart(FunctionResponse(name, response)) - is FileDataPart -> - com.google.firebase.vertexai.common.shared.FileDataPart( - FileData(mimeType = mimeType, fileUri = uri) - ) - else -> - throw SerializationException( - "The given subclass of Part (${javaClass.simpleName}) is not supported in the serialization yet." - ) - } -} - -internal fun SafetySetting.toInternal() = - com.google.firebase.vertexai.common.shared.SafetySetting( - harmCategory.toInternal(), - threshold.toInternal(), - method?.toInternal() - ) - -internal fun makeMissingCaseException(source: String, ordinal: Int): SerializationException { - return SerializationException( - """ - |Missing case for a $source: $ordinal - |This error indicates that one of the `toInternal` conversions needs updating. - |If you're a developer seeing this exception, please file an issue on our GitHub repo: - |https://github.com/firebase/firebase-android-sdk - """ - .trimMargin() - ) -} - -internal fun GenerationConfig.toInternal() = - com.google.firebase.vertexai.common.client.GenerationConfig( - temperature = temperature, - topP = topP, - topK = topK, - candidateCount = candidateCount, - maxOutputTokens = maxOutputTokens, - stopSequences = stopSequences, - frequencyPenalty = frequencyPenalty, - presencePenalty = presencePenalty, - responseMimeType = responseMimeType, - responseSchema = responseSchema?.toInternal() - ) - -internal fun HarmCategory.toInternal() = - when (this) { - HarmCategory.HARASSMENT -> com.google.firebase.vertexai.common.shared.HarmCategory.HARASSMENT - HarmCategory.HATE_SPEECH -> com.google.firebase.vertexai.common.shared.HarmCategory.HATE_SPEECH - HarmCategory.SEXUALLY_EXPLICIT -> - com.google.firebase.vertexai.common.shared.HarmCategory.SEXUALLY_EXPLICIT - HarmCategory.DANGEROUS_CONTENT -> - com.google.firebase.vertexai.common.shared.HarmCategory.DANGEROUS_CONTENT - HarmCategory.CIVIC_INTEGRITY -> - com.google.firebase.vertexai.common.shared.HarmCategory.CIVIC_INTEGRITY - HarmCategory.UNKNOWN -> com.google.firebase.vertexai.common.shared.HarmCategory.UNKNOWN - else -> throw makeMissingCaseException("HarmCategory", ordinal) - } - -internal fun HarmBlockMethod.toInternal() = - when (this) { - HarmBlockMethod.SEVERITY -> com.google.firebase.vertexai.common.shared.HarmBlockMethod.SEVERITY - HarmBlockMethod.PROBABILITY -> - com.google.firebase.vertexai.common.shared.HarmBlockMethod.PROBABILITY - else -> throw makeMissingCaseException("HarmBlockMethod", ordinal) - } - -internal fun ToolConfig.toInternal() = - com.google.firebase.vertexai.common.client.ToolConfig( - functionCallingConfig?.let { - com.google.firebase.vertexai.common.client.FunctionCallingConfig( - when (it.mode) { - FunctionCallingConfig.Mode.ANY -> - com.google.firebase.vertexai.common.client.FunctionCallingConfig.Mode.ANY - FunctionCallingConfig.Mode.AUTO -> - com.google.firebase.vertexai.common.client.FunctionCallingConfig.Mode.AUTO - FunctionCallingConfig.Mode.NONE -> - com.google.firebase.vertexai.common.client.FunctionCallingConfig.Mode.NONE - }, - it.allowedFunctionNames - ) - } - ) - -internal fun HarmBlockThreshold.toInternal() = - when (this) { - HarmBlockThreshold.NONE -> - com.google.firebase.vertexai.common.shared.HarmBlockThreshold.BLOCK_NONE - HarmBlockThreshold.ONLY_HIGH -> - com.google.firebase.vertexai.common.shared.HarmBlockThreshold.BLOCK_ONLY_HIGH - HarmBlockThreshold.MEDIUM_AND_ABOVE -> - com.google.firebase.vertexai.common.shared.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE - HarmBlockThreshold.LOW_AND_ABOVE -> - com.google.firebase.vertexai.common.shared.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE - else -> throw makeMissingCaseException("HarmBlockThreshold", ordinal) - } - -internal fun Tool.toInternal() = - com.google.firebase.vertexai.common.client.Tool( - functionDeclarations?.map { it.toInternal() } ?: emptyList() - ) - -internal fun FunctionDeclaration.toInternal() = - com.google.firebase.vertexai.common.client.FunctionDeclaration(name, "", schema.toInternal()) - -internal fun com.google.firebase.vertexai.type.Schema.toInternal(): Schema = - Schema( - type, - description, - format, - nullable, - enum, - properties?.mapValues { it.value.toInternal() }, - required, - items?.toInternal(), - ) - -internal fun JSONObject.toInternal() = Json.decodeFromString(toString()) - -internal fun com.google.firebase.vertexai.common.server.Candidate.toPublic(): Candidate { - val safetyRatings = safetyRatings?.map { it.toPublic() }.orEmpty() - val citations = citationMetadata?.toPublic() - val finishReason = finishReason.toPublic() - - return Candidate( - this.content?.toPublic() ?: content("model") {}, - safetyRatings, - citations, - finishReason - ) -} - -internal fun com.google.firebase.vertexai.common.UsageMetadata.toPublic(): UsageMetadata = - UsageMetadata(promptTokenCount ?: 0, candidatesTokenCount ?: 0, totalTokenCount ?: 0) - -internal fun com.google.firebase.vertexai.common.shared.Content.toPublic(): Content = - Content(role, parts.map { it.toPublic() }) - -internal fun com.google.firebase.vertexai.common.shared.Part.toPublic(): Part { - return when (this) { - is com.google.firebase.vertexai.common.shared.TextPart -> TextPart(text) - is com.google.firebase.vertexai.common.shared.InlineDataPart -> { - val data = Base64.decode(inlineData.data, BASE_64_FLAGS) - if (inlineData.mimeType.contains("image")) { - ImagePart(decodeBitmapFromImage(data)) - } else { - InlineDataPart(data, inlineData.mimeType) - } - } - is FunctionCallPart -> - com.google.firebase.vertexai.type.FunctionCallPart( - functionCall.name, - functionCall.args.orEmpty().mapValues { it.value ?: JsonNull } - ) - is FunctionResponsePart -> - com.google.firebase.vertexai.type.FunctionResponsePart( - functionResponse.name, - functionResponse.response, - ) - is com.google.firebase.vertexai.common.shared.FileDataPart -> - FileDataPart(fileData.mimeType, fileData.fileUri) - else -> - throw SerializationException( - "Unsupported part type \"${javaClass.simpleName}\" provided. This model may not be supported by this SDK." - ) - } -} - -internal fun com.google.firebase.vertexai.common.server.CitationSources.toPublic(): Citation { - val publicationDateAsCalendar = - publicationDate?.let { - val calendar = Calendar.getInstance() - // Internal `Date.year` uses 0 to represent not specified. We use 1 as default. - val year = if (it.year == null || it.year < 1) 1 else it.year - // Internal `Date.month` uses 0 to represent not specified, or is 1-12 as months. The month as - // expected by [Calendar] is 0-based, so we subtract 1 or use 0 as default. - val month = if (it.month == null || it.month < 1) 0 else it.month - 1 - // Internal `Date.day` uses 0 to represent not specified. We use 1 as default. - val day = if (it.day == null || it.day < 1) 1 else it.day - calendar.set(year, month, day) - calendar - } - return Citation( - title = title, - startIndex = startIndex, - endIndex = endIndex, - uri = uri, - license = license, - publicationDate = publicationDateAsCalendar - ) -} - -internal fun com.google.firebase.vertexai.common.server.CitationMetadata.toPublic() = - CitationMetadata(citationSources.map { it.toPublic() }) - -internal fun com.google.firebase.vertexai.common.server.SafetyRating.toPublic() = - SafetyRating( - category = category.toPublic(), - probability = probability.toPublic(), - probabilityScore = probabilityScore ?: 0f, - blocked = blocked, - severity = severity?.toPublic(), - severityScore = severityScore - ) - -internal fun com.google.firebase.vertexai.common.server.PromptFeedback.toPublic(): PromptFeedback { - val safetyRatings = safetyRatings?.map { it.toPublic() }.orEmpty() - return com.google.firebase.vertexai.type.PromptFeedback( - blockReason?.toPublic(), - safetyRatings, - blockReasonMessage - ) -} - -internal fun com.google.firebase.vertexai.common.server.FinishReason?.toPublic() = - when (this) { - null -> null - com.google.firebase.vertexai.common.server.FinishReason.MAX_TOKENS -> FinishReason.MAX_TOKENS - com.google.firebase.vertexai.common.server.FinishReason.RECITATION -> FinishReason.RECITATION - com.google.firebase.vertexai.common.server.FinishReason.SAFETY -> FinishReason.SAFETY - com.google.firebase.vertexai.common.server.FinishReason.STOP -> FinishReason.STOP - com.google.firebase.vertexai.common.server.FinishReason.OTHER -> FinishReason.OTHER - else -> FinishReason.UNKNOWN - } - -internal fun com.google.firebase.vertexai.common.shared.HarmCategory.toPublic() = - when (this) { - com.google.firebase.vertexai.common.shared.HarmCategory.HARASSMENT -> HarmCategory.HARASSMENT - com.google.firebase.vertexai.common.shared.HarmCategory.HATE_SPEECH -> HarmCategory.HATE_SPEECH - com.google.firebase.vertexai.common.shared.HarmCategory.SEXUALLY_EXPLICIT -> - HarmCategory.SEXUALLY_EXPLICIT - com.google.firebase.vertexai.common.shared.HarmCategory.DANGEROUS_CONTENT -> - HarmCategory.DANGEROUS_CONTENT - com.google.firebase.vertexai.common.shared.HarmCategory.CIVIC_INTEGRITY -> - HarmCategory.CIVIC_INTEGRITY - else -> HarmCategory.UNKNOWN - } - -internal fun com.google.firebase.vertexai.common.server.HarmProbability.toPublic() = - when (this) { - com.google.firebase.vertexai.common.server.HarmProbability.HIGH -> HarmProbability.HIGH - com.google.firebase.vertexai.common.server.HarmProbability.MEDIUM -> HarmProbability.MEDIUM - com.google.firebase.vertexai.common.server.HarmProbability.LOW -> HarmProbability.LOW - com.google.firebase.vertexai.common.server.HarmProbability.NEGLIGIBLE -> - HarmProbability.NEGLIGIBLE - else -> HarmProbability.UNKNOWN - } - -internal fun com.google.firebase.vertexai.common.server.HarmSeverity.toPublic() = - when (this) { - com.google.firebase.vertexai.common.server.HarmSeverity.HIGH -> HarmSeverity.HIGH - com.google.firebase.vertexai.common.server.HarmSeverity.MEDIUM -> HarmSeverity.MEDIUM - com.google.firebase.vertexai.common.server.HarmSeverity.LOW -> HarmSeverity.LOW - com.google.firebase.vertexai.common.server.HarmSeverity.NEGLIGIBLE -> HarmSeverity.NEGLIGIBLE - else -> HarmSeverity.UNKNOWN - } - -internal fun com.google.firebase.vertexai.common.server.BlockReason.toPublic() = - when (this) { - com.google.firebase.vertexai.common.server.BlockReason.SAFETY -> BlockReason.SAFETY - com.google.firebase.vertexai.common.server.BlockReason.OTHER -> BlockReason.OTHER - else -> BlockReason.UNKNOWN - } - -internal fun com.google.firebase.vertexai.common.GenerateContentResponse.toPublic(): - GenerateContentResponse { - return GenerateContentResponse( - candidates?.map { it.toPublic() }.orEmpty(), - promptFeedback?.toPublic(), - usageMetadata?.toPublic() - ) -} - -internal fun com.google.firebase.vertexai.common.CountTokensResponse.toPublic() = - CountTokensResponse(totalTokens, totalBillableCharacters ?: 0) - -internal fun JsonObject.toPublic() = JSONObject(toString()) - -private fun encodeBitmapToBase64Png(input: Bitmap): String { - ByteArrayOutputStream().let { - input.compress(Bitmap.CompressFormat.JPEG, 80, it) - return Base64.encodeToString(it.toByteArray(), BASE_64_FLAGS) - } -} - -private fun decodeBitmapFromImage(input: ByteArray) = - BitmapFactory.decodeByteArray(input, 0, input.size) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Candidate.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Candidate.kt index 6d8d96eb047..5d236c8ecc9 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Candidate.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Candidate.kt @@ -16,7 +16,13 @@ package com.google.firebase.vertexai.type +import com.google.firebase.vertexai.common.util.FirstOrdinalSerializer import java.util.Calendar +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonNames /** * A `Candidate` represents a single response generated by the model for a given request. @@ -32,7 +38,58 @@ internal constructor( public val safetyRatings: List, public val citationMetadata: CitationMetadata?, public val finishReason: FinishReason? -) +) { + + @Serializable + internal data class Internal( + val content: Content.Internal? = null, + val finishReason: FinishReason.Internal? = null, + val safetyRatings: List? = null, + val citationMetadata: CitationMetadata.Internal? = null, + val groundingMetadata: GroundingMetadata? = null, + ) { + internal fun toPublic(): Candidate { + val safetyRatings = safetyRatings?.map { it.toPublic() }.orEmpty() + val citations = citationMetadata?.toPublic() + val finishReason = finishReason?.toPublic() + + return Candidate( + this.content?.toPublic() ?: content("model") {}, + safetyRatings, + citations, + finishReason + ) + } + + @Serializable + internal data class GroundingMetadata( + @SerialName("web_search_queries") val webSearchQueries: List?, + @SerialName("search_entry_point") val searchEntryPoint: SearchEntryPoint?, + @SerialName("retrieval_queries") val retrievalQueries: List?, + @SerialName("grounding_attribution") val groundingAttribution: List?, + ) { + + @Serializable + internal data class SearchEntryPoint( + @SerialName("rendered_content") val renderedContent: String?, + @SerialName("sdk_blob") val sdkBlob: String?, + ) + + @Serializable + internal data class GroundingAttribution( + val segment: Segment, + @SerialName("confidence_score") val confidenceScore: Float?, + ) { + + @Serializable + internal data class Segment( + @SerialName("start_index") val startIndex: Int, + @SerialName("end_index") val endIndex: Int, + ) + } + } + } +} /** * An assessment of the potential harm of some generated content. @@ -55,7 +112,31 @@ internal constructor( public val blocked: Boolean? = null, public val severity: HarmSeverity? = null, public val severityScore: Float? = null -) +) { + + @Serializable + internal data class Internal + @JvmOverloads + constructor( + val category: HarmCategory.Internal, + val probability: HarmProbability.Internal, + val blocked: Boolean? = null, // TODO(): any reason not to default to false? + val probabilityScore: Float? = null, + val severity: HarmSeverity.Internal? = null, + val severityScore: Float? = null, + ) { + + internal fun toPublic() = + SafetyRating( + category = category.toPublic(), + probability = probability.toPublic(), + probabilityScore = probabilityScore ?: 0f, + blocked = blocked, + severity = severity?.toPublic(), + severityScore = severityScore + ) + } +} /** * A collection of source attributions for a piece of content. @@ -63,7 +144,16 @@ internal constructor( * @property citations A list of individual cited sources and the parts of the content to which they * apply. */ -public class CitationMetadata internal constructor(public val citations: List) +public class CitationMetadata internal constructor(public val citations: List) { + + @Serializable + internal data class Internal + @OptIn(ExperimentalSerializationApi::class) + internal constructor(@JsonNames("citations") val citationSources: List) { + + internal fun toPublic() = CitationMetadata(citationSources.map { it.toPublic() }) + } +} /** * Represents a citation of content from an external source within the model's output. @@ -89,7 +179,57 @@ internal constructor( public val uri: String? = null, public val license: String? = null, public val publicationDate: Calendar? = null -) +) { + + @Serializable + internal data class Internal( + val title: String? = null, + val startIndex: Int = 0, + val endIndex: Int, + val uri: String? = null, + val license: String? = null, + val publicationDate: Date? = null, + ) { + + internal fun toPublic(): Citation { + val publicationDateAsCalendar = + publicationDate?.let { + val calendar = Calendar.getInstance() + // Internal `Date.year` uses 0 to represent not specified. We use 1 as default. + val year = if (it.year == null || it.year < 1) 1 else it.year + // Internal `Date.month` uses 0 to represent not specified, or is 1-12 as months. The + // month as + // expected by [Calendar] is 0-based, so we subtract 1 or use 0 as default. + val month = if (it.month == null || it.month < 1) 0 else it.month - 1 + // Internal `Date.day` uses 0 to represent not specified. We use 1 as default. + val day = if (it.day == null || it.day < 1) 1 else it.day + calendar.set(year, month, day) + calendar + } + return Citation( + title = title, + startIndex = startIndex, + endIndex = endIndex, + uri = uri, + license = license, + publicationDate = publicationDateAsCalendar + ) + } + + @Serializable + internal data class Date( + /** Year of the date. Must be between 1 and 9999, or 0 for no year. */ + val year: Int? = null, + /** 1-based index for month. Must be from 1 to 12, or 0 to specify a year without a month. */ + val month: Int? = null, + /** + * Day of a month. Must be from 1 to 31 and valid for the year and month, or 0 to specify a + * year by itself or a year and month where the day isn't significant. + */ + val day: Int? = null, + ) + } +} /** * Represents the reason why the model stopped generating content. @@ -98,6 +238,37 @@ internal constructor( * @property ordinal The ordinal value of the finish reason. */ public class FinishReason private constructor(public val name: String, public val ordinal: Int) { + + @Serializable(Internal.Serializer::class) + internal enum class Internal { + UNKNOWN, + @SerialName("FINISH_REASON_UNSPECIFIED") UNSPECIFIED, + STOP, + MAX_TOKENS, + SAFETY, + RECITATION, + OTHER, + BLOCKLIST, + PROHIBITED_CONTENT, + SPII, + MALFORMED_FUNCTION_CALL; + + internal object Serializer : KSerializer by FirstOrdinalSerializer(Internal::class) + + internal fun toPublic() = + when (this) { + MAX_TOKENS -> FinishReason.MAX_TOKENS + RECITATION -> FinishReason.RECITATION + SAFETY -> FinishReason.SAFETY + STOP -> FinishReason.STOP + OTHER -> FinishReason.OTHER + BLOCKLIST -> FinishReason.BLOCKLIST + PROHIBITED_CONTENT -> FinishReason.PROHIBITED_CONTENT + SPII -> FinishReason.SPII + MALFORMED_FUNCTION_CALL -> FinishReason.MALFORMED_FUNCTION_CALL + else -> FinishReason.UNKNOWN + } + } public companion object { /** A new and not yet supported value. */ @JvmField public val UNKNOWN: FinishReason = FinishReason("UNKNOWN", 0) @@ -118,5 +289,21 @@ public class FinishReason private constructor(public val name: String, public va /** Model stopped for another reason. */ @JvmField public val OTHER: FinishReason = FinishReason("OTHER", 5) + + /** Token generation stopped because the content contains forbidden terms. */ + @JvmField public val BLOCKLIST: FinishReason = FinishReason("BLOCKLIST", 6) + + /** Token generation stopped for potentially containing prohibited content. */ + @JvmField public val PROHIBITED_CONTENT: FinishReason = FinishReason("PROHIBITED_CONTENT", 7) + + /** + * Token generation stopped because the content potentially contains Sensitive Personally + * Identifiable Information (SPII). + */ + @JvmField public val SPII: FinishReason = FinishReason("SPII", 8) + + /** The function call generated by the model is invalid. */ + @JvmField + public val MALFORMED_FUNCTION_CALL: FinishReason = FinishReason("MALFORMED_FUNCTION_CALL", 9) } } diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Content.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Content.kt index ec3e9555741..241d0becfe6 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Content.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Content.kt @@ -17,6 +17,9 @@ package com.google.firebase.vertexai.type import android.graphics.Bitmap +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable /** * Represents content sent to and received from the model. @@ -76,6 +79,24 @@ constructor(public val role: String? = "user", public val parts: List) { /** Returns a new [Content] using the defined [role] and [parts]. */ public fun build(): Content = Content(role, parts) } + + internal fun toInternal() = Internal(this.role ?: "user", this.parts.map { it.toInternal() }) + + @ExperimentalSerializationApi + @Serializable + internal data class Internal( + @EncodeDefault val role: String? = "user", + val parts: List + ) { + internal fun toPublic(): Content { + val returnedParts = + parts.map { it.toPublic() }.filterNot { it is TextPart && it.text.isEmpty() } + // If all returned parts were text and empty, we coalesce them into a single one-character + // string + // part so the backend doesn't fail if we send this back as part of a multi-turn interaction. + return Content(role, returnedParts.ifEmpty { listOf(TextPart(" ")) }) + } + } } /** diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ContentModality.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ContentModality.kt new file mode 100644 index 00000000000..dd928f92273 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ContentModality.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2025 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.vertexai.type + +import com.google.firebase.vertexai.common.util.FirstOrdinalSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** Content part modality. */ +public class ContentModality private constructor(public val ordinal: Int) { + + @Serializable(Internal.Serializer::class) + internal enum class Internal { + @SerialName("MODALITY_UNSPECIFIED") UNSPECIFIED, + TEXT, + IMAGE, + VIDEO, + AUDIO, + DOCUMENT; + + internal object Serializer : KSerializer by FirstOrdinalSerializer(Internal::class) + + internal fun toPublic() = + when (this) { + TEXT -> ContentModality.TEXT + IMAGE -> ContentModality.IMAGE + VIDEO -> ContentModality.VIDEO + AUDIO -> ContentModality.AUDIO + DOCUMENT -> ContentModality.DOCUMENT + else -> ContentModality.UNSPECIFIED + } + } + + public companion object { + /** Unspecified modality. */ + @JvmField public val UNSPECIFIED: ContentModality = ContentModality(0) + + /** Plain text. */ + @JvmField public val TEXT: ContentModality = ContentModality(1) + + /** Image. */ + @JvmField public val IMAGE: ContentModality = ContentModality(2) + + /** Video. */ + @JvmField public val VIDEO: ContentModality = ContentModality(3) + + /** Audio. */ + @JvmField public val AUDIO: ContentModality = ContentModality(4) + + /** Document, e.g. PDF. */ + @JvmField public val DOCUMENT: ContentModality = ContentModality(5) + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/CountTokensResponse.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/CountTokensResponse.kt index 2835deba6f7..49f6b0433e0 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/CountTokensResponse.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/CountTokensResponse.kt @@ -16,6 +16,8 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.Serializable + /** * The model's response to a count tokens request. * @@ -28,12 +30,33 @@ package com.google.firebase.vertexai.type * to the model as a prompt. **Important:** this property does not include billable image, video or * other non-text input. See * [Vertex AI pricing](https://cloud.google.com/vertex-ai/generative-ai/pricing) for details. + * @property promptTokensDetails The breakdown, by modality, of how many tokens are consumed by the + * prompt. */ public class CountTokensResponse( public val totalTokens: Int, - public val totalBillableCharacters: Int? = null + public val totalBillableCharacters: Int? = null, + public val promptTokensDetails: List = emptyList(), ) { public operator fun component1(): Int = totalTokens public operator fun component2(): Int? = totalBillableCharacters + + public operator fun component3(): List? = promptTokensDetails + + @Serializable + internal data class Internal( + val totalTokens: Int, + val totalBillableCharacters: Int? = null, + val promptTokensDetails: List? = null + ) : Response { + + internal fun toPublic(): CountTokensResponse { + return CountTokensResponse( + totalTokens, + totalBillableCharacters ?: 0, + promptTokensDetails?.map { it.toPublic() } ?: emptyList() + ) + } + } } diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Exceptions.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Exceptions.kt index a3bd95e15ab..4f4ca954f36 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Exceptions.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Exceptions.kt @@ -18,7 +18,6 @@ package com.google.firebase.vertexai.type import com.google.firebase.vertexai.FirebaseVertexAI import com.google.firebase.vertexai.common.FirebaseCommonAIException -import com.google.firebase.vertexai.internal.util.toPublic import kotlinx.coroutines.TimeoutCancellationException /** Parent class for any errors that occur from the [FirebaseVertexAI] SDK. */ diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionCallingConfig.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionCallingConfig.kt index a2ea9b1d01e..ee557556bbc 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionCallingConfig.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionCallingConfig.kt @@ -16,6 +16,9 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** * The configuration that specifies the function calling behavior. * @@ -42,7 +45,21 @@ internal constructor( * The model will never predict a function call to answer a query. This can also be achieved by * not passing any tools to the model. */ - NONE + NONE, + } + + @Serializable + internal data class Internal( + val mode: Mode, + @SerialName("allowed_function_names") val allowedFunctionNames: List? = null + ) { + @Serializable + enum class Mode { + @SerialName("MODE_UNSPECIFIED") UNSPECIFIED, + AUTO, + ANY, + NONE, + } } public companion object { diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclaration.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclaration.kt index 672293bb559..8813de18b43 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclaration.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclaration.kt @@ -16,6 +16,8 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.Serializable + /** * Defines a function that the model can use as a tool. * @@ -58,4 +60,13 @@ public class FunctionDeclaration( ) { internal val schema: Schema = Schema.obj(properties = parameters, optionalProperties = optionalParameters, nullable = false) + + internal fun toInternal() = Internal(name, "", schema.toInternal()) + + @Serializable + internal data class Internal( + val name: String, + val description: String, + val parameters: Schema.Internal + ) } diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerateContentResponse.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerateContentResponse.kt index 85891457b78..00395252914 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerateContentResponse.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerateContentResponse.kt @@ -16,6 +16,8 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.Serializable + /** * A response from the model. * @@ -41,4 +43,19 @@ public class GenerateContentResponse( public val functionCalls: List by lazy { candidates.first().content.parts.filterIsInstance() } + + @Serializable + internal data class Internal( + val candidates: List? = null, + val promptFeedback: PromptFeedback.Internal? = null, + val usageMetadata: UsageMetadata.Internal? = null, + ) : Response { + internal fun toPublic(): GenerateContentResponse { + return GenerateContentResponse( + candidates?.map { it.toPublic() }.orEmpty(), + promptFeedback?.toPublic(), + usageMetadata?.toPublic() + ) + } + } } diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerationConfig.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerationConfig.kt index 8bf8d7a1ac7..4abec8a260d 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerationConfig.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerationConfig.kt @@ -16,6 +16,9 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** * Configuration parameters to use for content generation. * @@ -142,6 +145,34 @@ private constructor( ) } + internal fun toInternal() = + Internal( + temperature = temperature, + topP = topP, + topK = topK, + candidateCount = candidateCount, + maxOutputTokens = maxOutputTokens, + stopSequences = stopSequences, + frequencyPenalty = frequencyPenalty, + presencePenalty = presencePenalty, + responseMimeType = responseMimeType, + responseSchema = responseSchema?.toInternal() + ) + + @Serializable + internal data class Internal( + val temperature: Float?, + @SerialName("top_p") val topP: Float?, + @SerialName("top_k") val topK: Int?, + @SerialName("candidate_count") val candidateCount: Int?, + @SerialName("max_output_tokens") val maxOutputTokens: Int?, + @SerialName("stop_sequences") val stopSequences: List?, + @SerialName("response_mime_type") val responseMimeType: String? = null, + @SerialName("presence_penalty") val presencePenalty: Float? = null, + @SerialName("frequency_penalty") val frequencyPenalty: Float? = null, + @SerialName("response_schema") val responseSchema: Schema.Internal? = null, + ) + public companion object { /** diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmBlockMethod.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmBlockMethod.kt index e743964c64a..1bd16949b20 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmBlockMethod.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmBlockMethod.kt @@ -16,11 +16,28 @@ package com.google.firebase.vertexai.type +import com.google.firebase.vertexai.common.makeMissingCaseException +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** * Specifies how the block method computes the score that will be compared against the * [HarmBlockThreshold] in [SafetySetting]. */ public class HarmBlockMethod private constructor(public val ordinal: Int) { + internal fun toInternal() = + when (this) { + SEVERITY -> Internal.SEVERITY + PROBABILITY -> Internal.PROBABILITY + else -> throw makeMissingCaseException("HarmBlockMethod", ordinal) + } + + @Serializable + internal enum class Internal { + @SerialName("HARM_BLOCK_METHOD_UNSPECIFIED") UNSPECIFIED, + SEVERITY, + PROBABILITY, + } public companion object { /** * The harm block method uses both probability and severity scores. See [HarmSeverity] and diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmBlockThreshold.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmBlockThreshold.kt index 073416112ab..1b3233bda2a 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmBlockThreshold.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmBlockThreshold.kt @@ -16,8 +16,31 @@ package com.google.firebase.vertexai.type +import com.google.firebase.vertexai.common.makeMissingCaseException +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** Represents the threshold for a [HarmCategory] to be allowed by [SafetySetting]. */ public class HarmBlockThreshold private constructor(public val ordinal: Int) { + + internal fun toInternal() = + when (this) { + NONE -> Internal.BLOCK_NONE + ONLY_HIGH -> Internal.BLOCK_ONLY_HIGH + MEDIUM_AND_ABOVE -> Internal.BLOCK_MEDIUM_AND_ABOVE + LOW_AND_ABOVE -> Internal.BLOCK_LOW_AND_ABOVE + else -> throw makeMissingCaseException("HarmBlockThreshold", ordinal) + } + + @Serializable + internal enum class Internal { + @SerialName("HARM_BLOCK_THRESHOLD_UNSPECIFIED") UNSPECIFIED, + BLOCK_LOW_AND_ABOVE, + BLOCK_MEDIUM_AND_ABOVE, + BLOCK_ONLY_HIGH, + BLOCK_NONE, + } + public companion object { /** Content with negligible harm is allowed. */ @JvmField public val LOW_AND_ABOVE: HarmBlockThreshold = HarmBlockThreshold(0) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmCategory.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmCategory.kt index d19de2e1568..2429688b02b 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmCategory.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmCategory.kt @@ -16,8 +16,45 @@ package com.google.firebase.vertexai.type +import com.google.firebase.vertexai.common.makeMissingCaseException +import com.google.firebase.vertexai.common.util.FirstOrdinalSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** Category for a given harm rating. */ public class HarmCategory private constructor(public val ordinal: Int) { + internal fun toInternal() = + when (this) { + HARASSMENT -> Internal.HARASSMENT + HATE_SPEECH -> Internal.HATE_SPEECH + SEXUALLY_EXPLICIT -> Internal.SEXUALLY_EXPLICIT + DANGEROUS_CONTENT -> Internal.DANGEROUS_CONTENT + CIVIC_INTEGRITY -> Internal.CIVIC_INTEGRITY + UNKNOWN -> Internal.UNKNOWN + else -> throw makeMissingCaseException("HarmCategory", ordinal) + } + @Serializable(Internal.Serializer::class) + internal enum class Internal { + UNKNOWN, + @SerialName("HARM_CATEGORY_HARASSMENT") HARASSMENT, + @SerialName("HARM_CATEGORY_HATE_SPEECH") HATE_SPEECH, + @SerialName("HARM_CATEGORY_SEXUALLY_EXPLICIT") SEXUALLY_EXPLICIT, + @SerialName("HARM_CATEGORY_DANGEROUS_CONTENT") DANGEROUS_CONTENT, + @SerialName("HARM_CATEGORY_CIVIC_INTEGRITY") CIVIC_INTEGRITY; + + internal object Serializer : KSerializer by FirstOrdinalSerializer(Internal::class) + + internal fun toPublic() = + when (this) { + HARASSMENT -> HarmCategory.HARASSMENT + HATE_SPEECH -> HarmCategory.HATE_SPEECH + SEXUALLY_EXPLICIT -> HarmCategory.SEXUALLY_EXPLICIT + DANGEROUS_CONTENT -> HarmCategory.DANGEROUS_CONTENT + CIVIC_INTEGRITY -> HarmCategory.CIVIC_INTEGRITY + else -> HarmCategory.UNKNOWN + } + } public companion object { /** A new and not yet supported value. */ @JvmField public val UNKNOWN: HarmCategory = HarmCategory(0) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmProbability.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmProbability.kt index d4208f7bf85..3d13e177819 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmProbability.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmProbability.kt @@ -16,8 +16,33 @@ package com.google.firebase.vertexai.type +import com.google.firebase.vertexai.common.util.FirstOrdinalSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** Represents the probability that some [HarmCategory] is applicable in a [SafetyRating]. */ public class HarmProbability private constructor(public val ordinal: Int) { + @Serializable(Internal.Serializer::class) + internal enum class Internal { + UNKNOWN, + @SerialName("HARM_PROBABILITY_UNSPECIFIED") UNSPECIFIED, + NEGLIGIBLE, + LOW, + MEDIUM, + HIGH; + + internal object Serializer : KSerializer by FirstOrdinalSerializer(Internal::class) + + internal fun toPublic() = + when (this) { + HIGH -> HarmProbability.HIGH + MEDIUM -> HarmProbability.MEDIUM + LOW -> HarmProbability.LOW + NEGLIGIBLE -> HarmProbability.NEGLIGIBLE + else -> HarmProbability.UNKNOWN + } + } public companion object { /** A new and not yet supported value. */ @JvmField public val UNKNOWN: HarmProbability = HarmProbability(0) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmSeverity.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmSeverity.kt index 40fe73ca906..0d0a39f2ac9 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmSeverity.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/HarmSeverity.kt @@ -16,8 +16,33 @@ package com.google.firebase.vertexai.type +import com.google.firebase.vertexai.common.util.FirstOrdinalSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** Represents the severity of a [HarmCategory] being applicable in a [SafetyRating]. */ public class HarmSeverity private constructor(public val ordinal: Int) { + @Serializable(Internal.Serializer::class) + internal enum class Internal { + UNKNOWN, + @SerialName("HARM_SEVERITY_UNSPECIFIED") UNSPECIFIED, + @SerialName("HARM_SEVERITY_NEGLIGIBLE") NEGLIGIBLE, + @SerialName("HARM_SEVERITY_LOW") LOW, + @SerialName("HARM_SEVERITY_MEDIUM") MEDIUM, + @SerialName("HARM_SEVERITY_HIGH") HIGH; + + internal object Serializer : KSerializer by FirstOrdinalSerializer(Internal::class) + + internal fun toPublic() = + when (this) { + HIGH -> HarmSeverity.HIGH + MEDIUM -> HarmSeverity.MEDIUM + LOW -> HarmSeverity.LOW + NEGLIGIBLE -> HarmSeverity.NEGLIGIBLE + else -> HarmSeverity.UNKNOWN + } + } public companion object { /** A new and not yet supported value. */ @JvmField public val UNKNOWN: HarmSeverity = HarmSeverity(0) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ModalityTokenCount.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ModalityTokenCount.kt new file mode 100644 index 00000000000..16b7b1e4207 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ModalityTokenCount.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2025 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.vertexai.type + +import kotlinx.serialization.Serializable + +/** + * Represents token counting info for a single modality. + * + * @property modality The modality associated with this token count. + * @property tokenCount The number of tokens counted. + */ +public class ModalityTokenCount +private constructor(public val modality: ContentModality, public val tokenCount: Int) { + + public operator fun component1(): ContentModality = modality + + public operator fun component2(): Int = tokenCount + + @Serializable + internal data class Internal( + val modality: ContentModality.Internal, + val tokenCount: Int? = null + ) { + internal fun toPublic() = ModalityTokenCount(modality.toPublic(), tokenCount ?: 0) + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Part.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Part.kt index 41ddfcfbe41..a0a47cf79ee 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Part.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Part.kt @@ -17,19 +17,31 @@ package com.google.firebase.vertexai.type import android.graphics.Bitmap +import android.graphics.BitmapFactory +import java.io.ByteArrayOutputStream +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.JsonContentPolymorphicSerializer import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonObject import org.json.JSONObject /** Interface representing data sent to and received from requests. */ -public interface Part +public interface Part {} /** Represents text or string based data sent to and received from requests. */ -public class TextPart(public val text: String) : Part +public class TextPart(public val text: String) : Part { + + @Serializable internal data class Internal(val text: String) : InternalPart +} /** - * Represents image data sent to and received from requests. When this is sent to the server it is - * converted to jpeg encoding at 80% quality. + * Represents image data sent to and received from requests. The image is converted client-side to + * JPEG encoding at 80% quality before being sent to the server. * * @param image [Bitmap] to convert into a [Part] */ @@ -42,7 +54,16 @@ public class ImagePart(public val image: Bitmap) : Part * @param mimeType an IANA standard MIME type. For supported values, see the * [Vertex AI documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts#media_requirements) */ -public class InlineDataPart(public val inlineData: ByteArray, public val mimeType: String) : Part +public class InlineDataPart(public val inlineData: ByteArray, public val mimeType: String) : Part { + + @Serializable + internal data class Internal(@SerialName("inline_data") val inlineData: InlineData) : + InternalPart { + + @Serializable + internal data class InlineData(@SerialName("mime_type") val mimeType: String, val data: Base64) + } +} /** * Represents function call name and params received from requests. @@ -51,7 +72,15 @@ public class InlineDataPart(public val inlineData: ByteArray, public val mimeTyp * @param args the function parameters and values as a [Map] */ public class FunctionCallPart(public val name: String, public val args: Map) : - Part + Part { + + @Serializable + internal data class Internal(val functionCall: FunctionCall) : InternalPart { + + @Serializable + internal data class FunctionCall(val name: String, val args: Map? = null) + } +} /** * Represents function call output to be returned to the model when it requests a function call. @@ -59,7 +88,14 @@ public class FunctionCallPart(public val name: String, public val args: Map(InternalPart::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val jsonObject = element.jsonObject + return when { + "text" in jsonObject -> TextPart.Internal.serializer() + "functionCall" in jsonObject -> FunctionCallPart.Internal.serializer() + "functionResponse" in jsonObject -> FunctionResponsePart.Internal.serializer() + "inlineData" in jsonObject -> InlineDataPart.Internal.serializer() + "fileData" in jsonObject -> FileDataPart.Internal.serializer() + else -> throw SerializationException("Unknown Part type") + } + } +} + +internal fun Part.toInternal(): InternalPart { + return when (this) { + is TextPart -> TextPart.Internal(text) + is ImagePart -> + InlineDataPart.Internal( + InlineDataPart.Internal.InlineData("image/jpeg", encodeBitmapToBase64Png(image)) + ) + is InlineDataPart -> + InlineDataPart.Internal( + InlineDataPart.Internal.InlineData( + mimeType, + android.util.Base64.encodeToString(inlineData, BASE_64_FLAGS) + ) + ) + is FunctionCallPart -> + FunctionCallPart.Internal(FunctionCallPart.Internal.FunctionCall(name, args)) + is FunctionResponsePart -> + FunctionResponsePart.Internal(FunctionResponsePart.Internal.FunctionResponse(name, response)) + is FileDataPart -> + FileDataPart.Internal(FileDataPart.Internal.FileData(mimeType = mimeType, fileUri = uri)) + else -> + throw com.google.firebase.vertexai.type.SerializationException( + "The given subclass of Part (${javaClass.simpleName}) is not supported in the serialization yet." + ) + } +} + +private fun encodeBitmapToBase64Png(input: Bitmap): String { + ByteArrayOutputStream().let { + input.compress(Bitmap.CompressFormat.JPEG, 80, it) + return android.util.Base64.encodeToString(it.toByteArray(), BASE_64_FLAGS) + } +} + +internal fun InternalPart.toPublic(): Part { + return when (this) { + is TextPart.Internal -> TextPart(text) + is InlineDataPart.Internal -> { + val data = android.util.Base64.decode(inlineData.data, BASE_64_FLAGS) + if (inlineData.mimeType.contains("image")) { + ImagePart(decodeBitmapFromImage(data)) + } else { + InlineDataPart(data, inlineData.mimeType) + } + } + is FunctionCallPart.Internal -> + FunctionCallPart( + functionCall.name, + functionCall.args.orEmpty().mapValues { it.value ?: JsonNull } + ) + is FunctionResponsePart.Internal -> + FunctionResponsePart( + functionResponse.name, + functionResponse.response, + ) + is FileDataPart.Internal -> FileDataPart(fileData.mimeType, fileData.fileUri) + else -> + throw com.google.firebase.vertexai.type.SerializationException( + "Unsupported part type \"${javaClass.simpleName}\" provided. This model may not be supported by this SDK." + ) + } +} + +private fun decodeBitmapFromImage(input: ByteArray) = + BitmapFactory.decodeByteArray(input, 0, input.size) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/PromptFeedback.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/PromptFeedback.kt index b4d06d04b8a..f7e1ad0948a 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/PromptFeedback.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/PromptFeedback.kt @@ -16,6 +16,11 @@ package com.google.firebase.vertexai.type +import com.google.firebase.vertexai.common.util.FirstOrdinalSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** * Feedback on the prompt provided in the request. * @@ -27,10 +32,45 @@ public class PromptFeedback( public val blockReason: BlockReason?, public val safetyRatings: List, public val blockReasonMessage: String? -) +) { + + @Serializable + internal data class Internal( + val blockReason: BlockReason.Internal? = null, + val safetyRatings: List? = null, + val blockReasonMessage: String? = null, + ) { + + internal fun toPublic(): PromptFeedback { + val safetyRatings = safetyRatings?.map { it.toPublic() }.orEmpty() + return PromptFeedback(blockReason?.toPublic(), safetyRatings, blockReasonMessage) + } + } +} /** Describes why content was blocked. */ public class BlockReason private constructor(public val name: String, public val ordinal: Int) { + + @Serializable(Internal.Serializer::class) + internal enum class Internal { + UNKNOWN, + @SerialName("BLOCKED_REASON_UNSPECIFIED") UNSPECIFIED, + SAFETY, + OTHER, + BLOCKLIST, + PROHIBITED_CONTENT; + + internal object Serializer : KSerializer by FirstOrdinalSerializer(Internal::class) + + internal fun toPublic() = + when (this) { + SAFETY -> BlockReason.SAFETY + OTHER -> BlockReason.OTHER + BLOCKLIST -> BlockReason.BLOCKLIST + PROHIBITED_CONTENT -> BlockReason.PROHIBITED_CONTENT + else -> BlockReason.UNKNOWN + } + } public companion object { /** A new and not yet supported value. */ @JvmField public val UNKNOWN: BlockReason = BlockReason("UNKNOWN", 0) @@ -40,5 +80,11 @@ public class BlockReason private constructor(public val name: String, public val /** Content was blocked for another reason. */ @JvmField public val OTHER: BlockReason = BlockReason("OTHER", 2) + + /** Content was blocked for another reason. */ + @JvmField public val BLOCKLIST: BlockReason = BlockReason("BLOCKLIST", 3) + + /** Candidates blocked due to the terms which are included from the terminology blocklist. */ + @JvmField public val PROHIBITED_CONTENT: BlockReason = BlockReason("PROHIBITED_CONTENT", 4) } } diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/SafetySetting.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/SafetySetting.kt index 68d7f93aa99..8095c42c532 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/SafetySetting.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/SafetySetting.kt @@ -16,6 +16,8 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.Serializable + /** * A configuration for a [HarmBlockThreshold] of some [HarmCategory] allowed and blocked in * responses. @@ -29,4 +31,14 @@ public class SafetySetting( internal val harmCategory: HarmCategory, internal val threshold: HarmBlockThreshold, internal val method: HarmBlockMethod? = null, -) +) { + internal fun toInternal() = + Internal(harmCategory.toInternal(), threshold.toInternal(), method?.toInternal()) + + @Serializable + internal data class Internal( + val category: HarmCategory.Internal, + val threshold: HarmBlockThreshold.Internal, + val method: HarmBlockMethod.Internal? = null, + ) +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Schema.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Schema.kt index b6f69e51d49..869d83b0eb9 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Schema.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Schema.kt @@ -16,6 +16,8 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.Serializable + public abstract class StringFormat private constructor(internal val value: String) { public class Custom(value: String) : StringFormat(value) } @@ -238,4 +240,27 @@ internal constructor( type = "STRING", ) } + + internal fun toInternal(): Internal = + Internal( + type, + description, + format, + nullable, + enum, + properties?.mapValues { it.value.toInternal() }, + required, + items?.toInternal(), + ) + @Serializable + internal data class Internal( + val type: String, + val description: String? = null, + val format: String? = null, + val nullable: Boolean? = false, + val enum: List? = null, + val properties: Map? = null, + val required: List? = null, + val items: Internal? = null, + ) } diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Tool.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Tool.kt index 41cbf99f6c4..e62e02f55b1 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Tool.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Tool.kt @@ -16,6 +16,9 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + /** * Contains a set of function declarations that the model has access to. These can be used to gather * information, or complete tasks @@ -24,6 +27,13 @@ package com.google.firebase.vertexai.type */ public class Tool internal constructor(internal val functionDeclarations: List?) { + internal fun toInternal() = Internal(functionDeclarations?.map { it.toInternal() } ?: emptyList()) + @Serializable + internal data class Internal( + val functionDeclarations: List? = null, + // This is a json object because it is not possible to make a data class with no parameters. + val codeExecution: JsonObject? = null, + ) public companion object { /** diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ToolConfig.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ToolConfig.kt index ee26f6b0f57..99769ed46b6 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ToolConfig.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ToolConfig.kt @@ -16,10 +16,34 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** * Contains configuration for the function calling tools of the model. This can be used to change * when the model can predict function calls. * * @param functionCallingConfig The config for function calling */ -public class ToolConfig(internal val functionCallingConfig: FunctionCallingConfig?) +public class ToolConfig(internal val functionCallingConfig: FunctionCallingConfig?) { + + internal fun toInternal() = + Internal( + functionCallingConfig?.let { + FunctionCallingConfig.Internal( + when (it.mode) { + FunctionCallingConfig.Mode.ANY -> FunctionCallingConfig.Internal.Mode.ANY + FunctionCallingConfig.Mode.AUTO -> FunctionCallingConfig.Internal.Mode.AUTO + FunctionCallingConfig.Mode.NONE -> FunctionCallingConfig.Internal.Mode.NONE + }, + it.allowedFunctionNames + ) + } + ) + + @Serializable + internal data class Internal( + @SerialName("function_calling_config") + val functionCallingConfig: FunctionCallingConfig.Internal? + ) +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Type.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Type.kt index ff33240aa24..a35000c7da5 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Type.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Type.kt @@ -15,3 +15,33 @@ */ package com.google.firebase.vertexai.type + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import org.json.JSONObject + +internal sealed interface Response + +@Serializable +internal data class GRpcErrorResponse(val error: GRpcError) : Response { + + @Serializable + internal data class GRpcError( + val code: Int, + val message: String, + val details: List? = null + ) { + + @Serializable + internal data class GRpcErrorDetails( + val reason: String? = null, + val domain: String? = null, + val metadata: Map? = null + ) + } +} + +internal fun JSONObject.toInternal() = Json.decodeFromString(toString()) + +internal fun JsonObject.toPublic() = JSONObject(toString()) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/UsageMetadata.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/UsageMetadata.kt index 21da0255cb9..16200792f9c 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/UsageMetadata.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/UsageMetadata.kt @@ -16,15 +16,43 @@ package com.google.firebase.vertexai.type +import kotlinx.serialization.Serializable + /** * Usage metadata about response(s). * * @param promptTokenCount Number of tokens in the request. * @param candidatesTokenCount Number of tokens in the response(s). * @param totalTokenCount Total number of tokens. + * @param promptTokensDetails The breakdown, by modality, of how many tokens are consumed by the + * prompt. + * @param candidatesTokensDetails The breakdown, by modality, of how many tokens are consumed by the + * candidates. */ public class UsageMetadata( public val promptTokenCount: Int, public val candidatesTokenCount: Int?, - public val totalTokenCount: Int -) + public val totalTokenCount: Int, + public val promptTokensDetails: List, + public val candidatesTokensDetails: List, +) { + + @Serializable + internal data class Internal( + val promptTokenCount: Int? = null, + val candidatesTokenCount: Int? = null, + val totalTokenCount: Int? = null, + val promptTokensDetails: List? = null, + val candidatesTokensDetails: List? = null, + ) { + + internal fun toPublic(): UsageMetadata = + UsageMetadata( + promptTokenCount ?: 0, + candidatesTokenCount ?: 0, + totalTokenCount ?: 0, + promptTokensDetails = promptTokensDetails?.map { it.toPublic() } ?: emptyList(), + candidatesTokensDetails = candidatesTokensDetails?.map { it.toPublic() } ?: emptyList() + ) + } +} diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/GenerativeModelTesting.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/GenerativeModelTesting.kt index 8b668371a31..67d41c9b5d6 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/GenerativeModelTesting.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/GenerativeModelTesting.kt @@ -17,14 +17,14 @@ package com.google.firebase.vertexai import com.google.firebase.vertexai.common.APIController -import com.google.firebase.vertexai.common.GenerateContentResponse import com.google.firebase.vertexai.common.JSON -import com.google.firebase.vertexai.common.server.Candidate -import com.google.firebase.vertexai.common.shared.Content -import com.google.firebase.vertexai.common.shared.TextPart import com.google.firebase.vertexai.common.util.doBlocking +import com.google.firebase.vertexai.type.Candidate +import com.google.firebase.vertexai.type.Content +import com.google.firebase.vertexai.type.GenerateContentResponse import com.google.firebase.vertexai.type.RequestOptions import com.google.firebase.vertexai.type.ServerException +import com.google.firebase.vertexai.type.TextPart import com.google.firebase.vertexai.type.content import io.kotest.assertions.json.shouldContainJsonKey import io.kotest.assertions.json.shouldContainJsonKeyValue @@ -129,7 +129,9 @@ internal class GenerativeModelTesting { private fun generateContentResponseAsJsonString(text: String): String { return JSON.encodeToString( - GenerateContentResponse(listOf(Candidate(Content(parts = listOf(TextPart(text)))))) + GenerateContentResponse.Internal( + listOf(Candidate.Internal(Content.Internal(parts = listOf(TextPart.Internal(text))))) + ) ) } } diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/SchemaTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/SchemaTests.kt index 747f65ac168..4701d516ff5 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/SchemaTests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/SchemaTests.kt @@ -16,7 +16,6 @@ package com.google.firebase.vertexai -import com.google.firebase.vertexai.internal.util.toInternal import com.google.firebase.vertexai.type.Schema import com.google.firebase.vertexai.type.StringFormat import io.kotest.assertions.json.shouldEqualJson diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/UnarySnapshotTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/UnarySnapshotTests.kt index b19dcd7aa1e..11d5a0df052 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/UnarySnapshotTests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/UnarySnapshotTests.kt @@ -17,6 +17,7 @@ package com.google.firebase.vertexai import com.google.firebase.vertexai.type.BlockReason +import com.google.firebase.vertexai.type.ContentModality import com.google.firebase.vertexai.type.FinishReason import com.google.firebase.vertexai.type.FunctionCallPart import com.google.firebase.vertexai.type.HarmCategory @@ -33,6 +34,7 @@ import com.google.firebase.vertexai.type.UnsupportedUserLocationException import com.google.firebase.vertexai.util.goldenUnaryFile import com.google.firebase.vertexai.util.shouldNotBeNullOrEmpty import io.kotest.assertions.throwables.shouldThrow +import io.kotest.inspectors.forAtLeastOne import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.should @@ -42,6 +44,7 @@ import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldNotBeEmpty import io.kotest.matchers.types.shouldBeInstanceOf import io.ktor.http.HttpStatusCode +import java.util.Calendar import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.withTimeout import kotlinx.serialization.json.JsonPrimitive @@ -67,15 +70,27 @@ internal class UnarySnapshotTests { } @Test - fun `long reply`() = - goldenUnaryFile("unary-success-basic-reply-long.json") { + fun `response with detailed token-based usageMetadata`() = + goldenUnaryFile("unary-success-basic-response-long-usage-metadata.json") { withTimeout(testTimeout) { val response = model.generateContent("prompt") response.candidates.isEmpty() shouldBe false response.candidates.first().finishReason shouldBe FinishReason.STOP response.candidates.first().content.parts.isEmpty() shouldBe false - response.candidates.first().safetyRatings.isEmpty() shouldBe false + response.usageMetadata shouldNotBe null + response.usageMetadata?.apply { + totalTokenCount shouldBe 1913 + candidatesTokenCount shouldBe 76 + promptTokensDetails?.forAtLeastOne { + it.modality shouldBe ContentModality.IMAGE + it.tokenCount shouldBe 1806 + } + candidatesTokensDetails?.forAtLeastOne { + it.modality shouldBe ContentModality.TEXT + it.tokenCount shouldBe 76 + } + } } } @@ -213,6 +228,11 @@ internal class UnarySnapshotTests { withTimeout(testTimeout) { val exception = shouldThrow { model.generateContent("prompt") } exception.response.candidates.first().finishReason shouldBe FinishReason.SAFETY + exception.response.candidates.first().safetyRatings.forAtLeastOne { + it.category shouldBe HarmCategory.HARASSMENT + it.probability shouldBe HarmProbability.LOW + it.severity shouldBe HarmSeverity.LOW + } } } @@ -233,6 +253,10 @@ internal class UnarySnapshotTests { response.candidates.isEmpty() shouldBe false response.candidates.first().citationMetadata?.citations?.size shouldBe 3 + response.candidates.first().citationMetadata?.citations?.forAtLeastOne { + it.publicationDate?.get(Calendar.YEAR) shouldBe 2019 + it.publicationDate?.get(Calendar.DAY_OF_MONTH) shouldBe 10 + } } } @@ -265,6 +289,7 @@ internal class UnarySnapshotTests { response.candidates.first().finishReason shouldBe FinishReason.STOP response.usageMetadata shouldNotBe null response.usageMetadata?.totalTokenCount shouldBe 363 + response.usageMetadata?.promptTokensDetails?.isEmpty() shouldBe true } } @@ -454,6 +479,23 @@ internal class UnarySnapshotTests { response.totalTokens shouldBe 6 response.totalBillableCharacters shouldBe 16 + response.promptTokensDetails.isEmpty() shouldBe true + } + } + + @Test + fun `countTokens with modality fields returned`() = + goldenUnaryFile("unary-success-detailed-token-response.json") { + withTimeout(testTimeout) { + val response = model.countTokens("prompt") + + response.totalTokens shouldBe 1837 + response.totalBillableCharacters shouldBe 117 + response.promptTokensDetails shouldNotBe null + response.promptTokensDetails?.forAtLeastOne { + it.modality shouldBe ContentModality.IMAGE + it.tokenCount shouldBe 1806 + } } } diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/APIControllerTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/APIControllerTests.kt index 8937b13569b..463dbe773f7 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/APIControllerTests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/APIControllerTests.kt @@ -17,16 +17,17 @@ package com.google.firebase.vertexai.common import com.google.firebase.vertexai.BuildConfig -import com.google.firebase.vertexai.common.client.FunctionCallingConfig -import com.google.firebase.vertexai.common.client.Tool -import com.google.firebase.vertexai.common.client.ToolConfig -import com.google.firebase.vertexai.common.shared.Content -import com.google.firebase.vertexai.common.shared.TextPart import com.google.firebase.vertexai.common.util.commonTest import com.google.firebase.vertexai.common.util.createResponses import com.google.firebase.vertexai.common.util.doBlocking import com.google.firebase.vertexai.common.util.prepareStreamingResponse +import com.google.firebase.vertexai.type.Content +import com.google.firebase.vertexai.type.CountTokensResponse +import com.google.firebase.vertexai.type.FunctionCallingConfig import com.google.firebase.vertexai.type.RequestOptions +import com.google.firebase.vertexai.type.TextPart +import com.google.firebase.vertexai.type.Tool +import com.google.firebase.vertexai.type.ToolConfig import io.kotest.assertions.json.shouldContainJsonKey import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe @@ -140,7 +141,7 @@ internal class RequestFormatTests { @Test fun `client id header is set correctly in the request`() = doBlocking { - val response = JSON.encodeToString(CountTokensResponse(totalTokens = 10)) + val response = JSON.encodeToString(CountTokensResponse.Internal(totalTokens = 10)) val mockEngine = MockEngine { respond(response, HttpStatusCode.OK, headersOf(HttpHeaders.ContentType, "application/json")) } @@ -183,11 +184,11 @@ internal class RequestFormatTests { .generateContentStream( GenerateContentRequest( model = "unused", - contents = listOf(Content(parts = listOf(TextPart("Arbitrary")))), + contents = listOf(Content.Internal(parts = listOf(TextPart.Internal("Arbitrary")))), toolConfig = - ToolConfig( - FunctionCallingConfig( - mode = FunctionCallingConfig.Mode.ANY, + ToolConfig.Internal( + FunctionCallingConfig.Internal( + mode = FunctionCallingConfig.Internal.Mode.ANY, allowedFunctionNames = listOf("allowedFunctionName") ) ) @@ -205,7 +206,7 @@ internal class RequestFormatTests { @Test fun `headers from HeaderProvider are added to the request`() = doBlocking { - val response = JSON.encodeToString(CountTokensResponse(totalTokens = 10)) + val response = JSON.encodeToString(CountTokensResponse.Internal(totalTokens = 10)) val mockEngine = MockEngine { respond(response, HttpStatusCode.OK, headersOf(HttpHeaders.ContentType, "application/json")) } @@ -237,7 +238,7 @@ internal class RequestFormatTests { @Test fun `headers from HeaderProvider are ignored if timeout`() = doBlocking { - val response = JSON.encodeToString(CountTokensResponse(totalTokens = 10)) + val response = JSON.encodeToString(CountTokensResponse.Internal(totalTokens = 10)) val mockEngine = MockEngine { respond(response, HttpStatusCode.OK, headersOf(HttpHeaders.ContentType, "application/json")) } @@ -291,8 +292,8 @@ internal class RequestFormatTests { .generateContentStream( GenerateContentRequest( model = "unused", - contents = listOf(Content(parts = listOf(TextPart("Arbitrary")))), - tools = listOf(Tool(codeExecution = JsonObject(emptyMap()))), + contents = listOf(Content.Internal(parts = listOf(TextPart.Internal("Arbitrary")))), + tools = listOf(Tool.Internal(codeExecution = JsonObject(emptyMap()))), ) ) .collect { channel.close() } @@ -351,7 +352,7 @@ internal class ModelNamingTests(private val modelName: String, private val actua internal fun textGenerateContentRequest(prompt: String) = GenerateContentRequest( model = "unused", - contents = listOf(Content(parts = listOf(TextPart(prompt)))), + contents = listOf(Content.Internal(parts = listOf(TextPart.Internal(prompt)))), ) internal fun textCountTokenRequest(prompt: String) = diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/EnumUpdateTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/EnumUpdateTests.kt index ddee4dbabf1..769adbd4cd8 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/EnumUpdateTests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/EnumUpdateTests.kt @@ -16,7 +16,6 @@ package com.google.firebase.vertexai.common -import com.google.firebase.vertexai.internal.util.toInternal import com.google.firebase.vertexai.type.HarmBlockMethod import com.google.firebase.vertexai.type.HarmBlockThreshold import com.google.firebase.vertexai.type.HarmCategory diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/StreamingSnapshotTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/StreamingSnapshotTests.kt index 9d470c95ea6..2d29ad38ba7 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/StreamingSnapshotTests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/StreamingSnapshotTests.kt @@ -16,11 +16,11 @@ package com.google.firebase.vertexai.common -import com.google.firebase.vertexai.common.server.BlockReason -import com.google.firebase.vertexai.common.server.FinishReason -import com.google.firebase.vertexai.common.shared.HarmCategory -import com.google.firebase.vertexai.common.shared.TextPart import com.google.firebase.vertexai.common.util.goldenStreamingFile +import com.google.firebase.vertexai.type.BlockReason +import com.google.firebase.vertexai.type.FinishReason +import com.google.firebase.vertexai.type.HarmCategory +import com.google.firebase.vertexai.type.TextPart import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -43,7 +43,7 @@ internal class StreamingSnapshotTests { withTimeout(testTimeout) { val responseList = responses.toList() responseList.isEmpty() shouldBe false - responseList.first().candidates?.first()?.finishReason shouldBe FinishReason.STOP + responseList.first().candidates?.first()?.finishReason shouldBe FinishReason.Internal.STOP responseList.first().candidates?.first()?.content?.parts?.isEmpty() shouldBe false responseList.first().candidates?.first()?.safetyRatings?.isEmpty() shouldBe false } @@ -58,7 +58,7 @@ internal class StreamingSnapshotTests { val responseList = responses.toList() responseList.isEmpty() shouldBe false responseList.forEach { - it.candidates?.first()?.finishReason shouldBe FinishReason.STOP + it.candidates?.first()?.finishReason shouldBe FinishReason.Internal.STOP it.candidates?.first()?.content?.parts?.isEmpty() shouldBe false it.candidates?.first()?.safetyRatings?.isEmpty() shouldBe false } @@ -75,7 +75,7 @@ internal class StreamingSnapshotTests { responseList.isEmpty() shouldBe false responseList.any { it.candidates?.any { - it.safetyRatings?.any { it.category == HarmCategory.UNKNOWN } ?: false + it.safetyRatings?.any { it.category == HarmCategory.Internal.UNKNOWN } ?: false } ?: false } shouldBe true @@ -91,7 +91,8 @@ internal class StreamingSnapshotTests { val responseList = responses.toList() responseList.isEmpty() shouldBe false - val part = responseList.first().candidates?.first()?.content?.parts?.first() as? TextPart + val part = + responseList.first().candidates?.first()?.content?.parts?.first() as? TextPart.Internal part.shouldNotBeNull() part.text shouldContain "\"" } @@ -104,7 +105,7 @@ internal class StreamingSnapshotTests { withTimeout(testTimeout) { val exception = shouldThrow { responses.collect() } - exception.response.promptFeedback?.blockReason shouldBe BlockReason.SAFETY + exception.response.promptFeedback?.blockReason shouldBe BlockReason.Internal.SAFETY } } @@ -131,7 +132,7 @@ internal class StreamingSnapshotTests { withTimeout(testTimeout) { val exception = shouldThrow { responses.collect() } - exception.response.candidates?.first()?.finishReason shouldBe FinishReason.SAFETY + exception.response.candidates?.first()?.finishReason shouldBe FinishReason.Internal.SAFETY } } @@ -170,7 +171,8 @@ internal class StreamingSnapshotTests { withTimeout(testTimeout) { val exception = shouldThrow { responses.collect() } - exception.response.candidates?.first()?.finishReason shouldBe FinishReason.RECITATION + exception.response.candidates?.first()?.finishReason shouldBe + FinishReason.Internal.RECITATION } } diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/UnarySnapshotTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/UnarySnapshotTests.kt index 66e6a3f53a5..33ebdda5322 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/UnarySnapshotTests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/UnarySnapshotTests.kt @@ -16,15 +16,15 @@ package com.google.firebase.vertexai.common -import com.google.firebase.vertexai.common.server.BlockReason -import com.google.firebase.vertexai.common.server.FinishReason -import com.google.firebase.vertexai.common.server.HarmProbability -import com.google.firebase.vertexai.common.server.HarmSeverity -import com.google.firebase.vertexai.common.shared.FunctionCallPart -import com.google.firebase.vertexai.common.shared.HarmCategory -import com.google.firebase.vertexai.common.shared.TextPart import com.google.firebase.vertexai.common.util.goldenUnaryFile import com.google.firebase.vertexai.common.util.shouldNotBeNullOrEmpty +import com.google.firebase.vertexai.type.BlockReason +import com.google.firebase.vertexai.type.FinishReason +import com.google.firebase.vertexai.type.FunctionCallPart +import com.google.firebase.vertexai.type.HarmCategory +import com.google.firebase.vertexai.type.HarmProbability +import com.google.firebase.vertexai.type.HarmSeverity +import com.google.firebase.vertexai.type.TextPart import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.nulls.shouldNotBeNull @@ -53,7 +53,7 @@ internal class UnarySnapshotTests { val response = apiController.generateContent(textGenerateContentRequest("prompt")) response.candidates?.isEmpty() shouldBe false - response.candidates?.first()?.finishReason shouldBe FinishReason.STOP + response.candidates?.first()?.finishReason shouldBe FinishReason.Internal.STOP response.candidates?.first()?.content?.parts?.isEmpty() shouldBe false response.candidates?.first()?.safetyRatings?.isEmpty() shouldBe false } @@ -67,7 +67,7 @@ internal class UnarySnapshotTests { val response = apiController.generateContent(textGenerateContentRequest("prompt")) response.candidates?.isEmpty() shouldBe false - response.candidates?.first()?.finishReason shouldBe FinishReason.STOP + response.candidates?.first()?.finishReason shouldBe FinishReason.Internal.STOP response.candidates?.first()?.content?.parts?.isEmpty() shouldBe false response.candidates?.first()?.safetyRatings?.isEmpty() shouldBe false } @@ -81,7 +81,7 @@ internal class UnarySnapshotTests { response.candidates?.isNullOrEmpty() shouldBe false val candidate = response.candidates?.first() - candidate?.safetyRatings?.any { it.category == HarmCategory.UNKNOWN } shouldBe true + candidate?.safetyRatings?.any { it.category == HarmCategory.Internal.UNKNOWN } shouldBe true } } @@ -94,12 +94,12 @@ internal class UnarySnapshotTests { response.candidates?.isEmpty() shouldBe false response.candidates?.first()?.safetyRatings?.isEmpty() shouldBe false response.candidates?.first()?.safetyRatings?.all { - it.probability == HarmProbability.NEGLIGIBLE + it.probability == HarmProbability.Internal.NEGLIGIBLE } shouldBe true response.candidates?.first()?.safetyRatings?.all { it.probabilityScore != null } shouldBe true response.candidates?.first()?.safetyRatings?.all { - it.severity == HarmSeverity.NEGLIGIBLE + it.severity == HarmSeverity.Internal.NEGLIGIBLE } shouldBe true response.candidates?.first()?.safetyRatings?.all { it.severityScore != null } shouldBe true } @@ -111,7 +111,7 @@ internal class UnarySnapshotTests { withTimeout(testTimeout) { shouldThrow { apiController.generateContent(textGenerateContentRequest("prompt")) - } should { it.response.promptFeedback?.blockReason shouldBe BlockReason.SAFETY } + } should { it.response.promptFeedback?.blockReason shouldBe BlockReason.Internal.SAFETY } } } @@ -153,7 +153,7 @@ internal class UnarySnapshotTests { shouldThrow { apiController.generateContent(textGenerateContentRequest("prompt")) } - exception.response.candidates?.first()?.finishReason shouldBe FinishReason.SAFETY + exception.response.candidates?.first()?.finishReason shouldBe FinishReason.Internal.SAFETY } } @@ -191,7 +191,7 @@ internal class UnarySnapshotTests { val response = apiController.generateContent(textGenerateContentRequest("prompt")) response.candidates?.isEmpty() shouldBe false - response.candidates?.first()?.finishReason shouldBe FinishReason.STOP + response.candidates?.first()?.finishReason shouldBe FinishReason.Internal.STOP response.usageMetadata shouldNotBe null response.usageMetadata?.totalTokenCount shouldBe 363 } @@ -204,7 +204,7 @@ internal class UnarySnapshotTests { val response = apiController.generateContent(textGenerateContentRequest("prompt")) response.candidates?.isEmpty() shouldBe false - response.candidates?.first()?.finishReason shouldBe FinishReason.STOP + response.candidates?.first()?.finishReason shouldBe FinishReason.Internal.STOP response.usageMetadata shouldNotBe null response.usageMetadata?.promptTokenCount shouldBe 6 response.usageMetadata?.totalTokenCount shouldBe null @@ -231,7 +231,12 @@ internal class UnarySnapshotTests { response.candidates?.isEmpty() shouldBe false with( - response.candidates?.first()?.content?.parts?.first()?.shouldBeInstanceOf() + response.candidates + ?.first() + ?.content + ?.parts + ?.first() + ?.shouldBeInstanceOf() ) { shouldNotBeNull() JSON.decodeFromString>(text).shouldNotBeEmpty() @@ -315,7 +320,8 @@ internal class UnarySnapshotTests { goldenUnaryFile("success-function-call-null.json") { withTimeout(testTimeout) { val response = apiController.generateContent(textGenerateContentRequest("prompt")) - val callPart = (response.candidates!!.first().content!!.parts.first() as FunctionCallPart) + val callPart = + (response.candidates!!.first().content!!.parts.first() as FunctionCallPart.Internal) callPart.functionCall.args shouldNotBe null callPart.functionCall.args?.get("season") shouldBe null @@ -333,7 +339,7 @@ internal class UnarySnapshotTests { content.let { it.shouldNotBeNull() it.parts.shouldNotBeEmpty() - it.parts.first().shouldBeInstanceOf() + it.parts.first().shouldBeInstanceOf() } callPart.functionCall.args shouldNotBe null @@ -349,7 +355,7 @@ internal class UnarySnapshotTests { val response = apiController.generateContent(textGenerateContentRequest("prompt")) val content = response.candidates.shouldNotBeNullOrEmpty().first().content content.shouldNotBeNull() - val callPart = content.parts.shouldNotBeNullOrEmpty().first() as FunctionCallPart + val callPart = content.parts.shouldNotBeNullOrEmpty().first() as FunctionCallPart.Internal callPart.functionCall.name shouldBe "current_time" callPart.functionCall.args shouldBe null diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/util/tests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/util/tests.kt index 8d1e3bf9f00..5e52b1827b0 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/util/tests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/common/util/tests.kt @@ -20,12 +20,12 @@ package com.google.firebase.vertexai.common.util import com.google.firebase.vertexai.common.APIController import com.google.firebase.vertexai.common.GenerateContentRequest -import com.google.firebase.vertexai.common.GenerateContentResponse import com.google.firebase.vertexai.common.JSON -import com.google.firebase.vertexai.common.server.Candidate -import com.google.firebase.vertexai.common.shared.Content -import com.google.firebase.vertexai.common.shared.TextPart +import com.google.firebase.vertexai.type.Candidate +import com.google.firebase.vertexai.type.Content +import com.google.firebase.vertexai.type.GenerateContentResponse import com.google.firebase.vertexai.type.RequestOptions +import com.google.firebase.vertexai.type.TextPart import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.nulls.shouldNotBeNull import io.ktor.client.engine.mock.MockEngine @@ -43,15 +43,16 @@ import kotlinx.serialization.encodeToString private val TEST_CLIENT_ID = "genai-android/test" -internal fun prepareStreamingResponse(response: List): List = - response.map { "data: ${JSON.encodeToString(it)}$SSE_SEPARATOR".toByteArray() } +internal fun prepareStreamingResponse( + response: List +): List = response.map { "data: ${JSON.encodeToString(it)}$SSE_SEPARATOR".toByteArray() } -internal fun prepareResponse(response: GenerateContentResponse) = +internal fun prepareResponse(response: GenerateContentResponse.Internal) = JSON.encodeToString(response).toByteArray() @OptIn(ExperimentalSerializationApi::class) internal fun createRequest(vararg text: String): GenerateContentRequest { - val contents = text.map { Content(parts = listOf(TextPart(it))) } + val contents = text.map { Content.Internal(parts = listOf(TextPart.Internal(it))) } return GenerateContentRequest("gemini", contents) } @@ -59,10 +60,11 @@ internal fun createRequest(vararg text: String): GenerateContentRequest { internal fun createResponse(text: String) = createResponses(text).single() @OptIn(ExperimentalSerializationApi::class) -internal fun createResponses(vararg text: String): List { - val candidates = text.map { Candidate(Content(parts = listOf(TextPart(it)))) } +internal fun createResponses(vararg text: String): List { + val candidates = + text.map { Candidate.Internal(Content.Internal(parts = listOf(TextPart.Internal(it)))) } - return candidates.map { GenerateContentResponse(candidates = listOf(it)) } + return candidates.map { GenerateContentResponse.Internal(candidates = listOf(it)) } } /** diff --git a/firebase-vertexai/update_responses.sh b/firebase-vertexai/update_responses.sh index b13b94c8229..70e438090bd 100755 --- a/firebase-vertexai/update_responses.sh +++ b/firebase-vertexai/update_responses.sh @@ -17,7 +17,7 @@ # This script replaces mock response files for Vertex AI unit tests with a fresh # clone of the shared repository of Vertex AI test data. -RESPONSES_VERSION='v3.*' # The major version of mock responses to use +RESPONSES_VERSION='v6.*' # The major version of mock responses to use REPO_NAME="vertexai-sdk-test-data" REPO_LINK="https://github.com/FirebaseExtended/$REPO_NAME.git" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 67c092adc35..21442a483d3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ browser = "1.3.0" cardview = "1.0.0" checkerQual = "2.5.2" constraintlayout = "2.1.4" -coreKtx = "1.9.0" +coreKtx = "1.12.0" coroutines = "1.7.3" dagger = "2.43.2" dexmaker = "2.28.1" @@ -114,12 +114,9 @@ findbugs-jsr305 = { module = "com.google.code.findbugs:jsr305", version = "3.0.2 firebase-appdistribution-gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppdistributionGradle" } firebase-common = { module = "com.google.firebase:firebase-common", version.ref = "firebaseCommon" } firebase-components = { module = "com.google.firebase:firebase-components", version.ref = "firebaseComponents" } -firebase-crashlytics-gradle = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsGradle" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } google-api-client = { module = "com.google.api-client:google-api-client", version.ref = "googleApiClient" } google-dexmaker = { module = "com.google.dexmaker:dexmaker", version.ref = "dexmakerVersion" } -google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" } -gradle-errorprone-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "gradleErrorpronePlugin" } grpc-android = { module = "io.grpc:grpc-android", version.ref = "grpc" } grpc-kotlin-stub = { module = "io.grpc:grpc-kotlin-stub", version.ref = "grpcKotlin" } grpc-okhttp = { module = "io.grpc:grpc-okhttp", version.ref = "grpc" } @@ -173,7 +170,6 @@ playservices-base = { module = "com.google.android.gms:play-services-base", vers playservices-basement = { module = "com.google.android.gms:play-services-basement", version = "18.3.0" } playservices-tasks = { module = "com.google.android.gms:play-services-tasks", version = "18.1.0" } proto-google-common-protos = { module = "com.google.api.grpc:proto-google-common-protos", version.ref = "protoGoogleCommonProtos" } -protobuf-gradle-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobufGradlePlugin" } protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "javalite" } protobuf-java-lite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "javalite" } protobuf-kotlin-lite = { module = "com.google.protobuf:protobuf-kotlin-lite", version.ref = "javalite" } @@ -235,3 +231,7 @@ maven-resolver = [ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "serialization-plugin" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +protobuf = { id = "com.google.protobuf", version.ref = "protobufGradlePlugin" } +errorprone = { id = "net.ltgt.errorprone", version.ref = "gradleErrorpronePlugin" } +google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } +crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsGradle" } diff --git a/gradle/projectSettings.gradle b/gradle/projectSettings.gradle deleted file mode 100644 index 69228b202d8..00000000000 --- a/gradle/projectSettings.gradle +++ /dev/null @@ -1,41 +0,0 @@ -// 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. - -ext { - /** - * Parses the input file and returns a list of subprojects. - * - *

Expected file format: - *

    - *
  • Empty lines are ignored. - *
  • Lines that start with # are ignored(considered comments). - *
  • Other lines are considered project paths with initial ":" removed. - *
- */ - discoverSubprojects = {File subprojectsFile -> - return subprojectsFile.readLines().collect {it.trim()}.findAll { !it.empty && !it.startsWith('#')} - } - - /** Recursively renames build scripts to ${project.name}.gradle. */ - renameBuildScripts = {ProjectDescriptor project -> - def ktsFile = "${project.name}.gradle.kts" - def projectFile = new File(project.projectDir, ktsFile).exists() ? ktsFile : "${project.name}.gradle" - project.buildFileName = project.parent ? projectFile : 'build.gradle' - - project.children.each { - renameBuildScripts(it) - } - - } -} diff --git a/health-metrics/apk-size/apk-size.gradle b/health-metrics/apk-size/apk-size.gradle index 620e2c7e9be..3970dc842a0 100644 --- a/health-metrics/apk-size/apk-size.gradle +++ b/health-metrics/apk-size/apk-size.gradle @@ -28,8 +28,6 @@ plugins { id "com.dorongold.task-tree" version "3.0.0" } -apply from: '../../sdkProperties.gradle' - task clean(type: Delete) { delete rootProject.buildDir } diff --git a/health-metrics/apk-size/app/default.gradle b/health-metrics/apk-size/app/default.gradle index a2bd7151e0d..ce9d60b3dc7 100644 --- a/health-metrics/apk-size/app/default.gradle +++ b/health-metrics/apk-size/app/default.gradle @@ -29,7 +29,7 @@ android { defaultConfig { applicationId 'com.google.apksize' - minSdkVersion project.targetSdkVersion + minSdkVersion 34 multiDexEnabled true targetSdkVersion 33 versionCode 1 diff --git a/buildSrc/README.md b/plugins/README.md similarity index 97% rename from buildSrc/README.md rename to plugins/README.md index bc32b0f0449..ba4fd5a9252 100644 --- a/buildSrc/README.md +++ b/plugins/README.md @@ -1,4 +1,4 @@ -## Build Source +## Plugins > [!NOTE] > Eventually, this will be merged with our [contributor documentation](https://firebase.github.io/firebase-android-sdk/). diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts new file mode 100644 index 00000000000..85bad8507be --- /dev/null +++ b/plugins/build.gradle.kts @@ -0,0 +1,125 @@ +/* + * 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. + */ + +plugins { + alias(libs.plugins.kotlinx.serialization) + alias(libs.plugins.spotless) + `kotlin-dsl` +} + +repositories { + mavenLocal() + maven(url = "https://maven.google.com/") + mavenCentral() + maven(url = "https://storage.googleapis.com/android-ci/mvn/") + maven(url = "https://plugins.gradle.org/m2/") +} + +group = "com.google.firebase" + +spotless { + java { + target("src/**/*.java") + targetExclude("**/test/resources/**") + googleJavaFormat("1.22.0").reorderImports(true).skipJavadocFormatting() + } + kotlin { + target("src/**/*.kt") + ktfmt("0.52").googleStyle() + } +} + +// Refer latest "perf-plugin" released version on +// https://maven.google.com/web/index.html?q=perf-plugin#com.google.firebase:perf-plugin +// The System property allows us to integrate with an unreleased version from https://bityl.co/3oYt. +// Refer go/fireperf-plugin-test-on-head for more details. +val perfPluginVersion = System.getenv("FIREBASE_PERF_PLUGIN_VERSION") ?: "1.4.1" + +dependencies { + // Firebase performance plugin, it should be added here because of how gradle dependency + // resolution works, otherwise it breaks Fireperf Test Apps. + // See https://github.com/gradle/gradle/issues/12286 + implementation("com.google.firebase:perf-plugin:$perfPluginVersion") + implementation("com.google.auto.value:auto-value-annotations:1.8.1") + annotationProcessor("com.google.auto.value:auto-value:1.6.5") + implementation(kotlin("gradle-plugin", "1.8.22")) + implementation(libs.org.json) + implementation(libs.bundles.maven.resolver) + + implementation("com.google.guava:guava:31.1-jre") + implementation("org.ow2.asm:asm-tree:9.5") + implementation("org.eclipse.jgit:org.eclipse.jgit:6.3.0.202209071007-r") + implementation(libs.kotlinx.serialization.json) + implementation("com.google.code.gson:gson:2.8.9") + implementation(libs.android.gradlePlugin.gradle) + implementation(libs.android.gradlePlugin.builder.test.api) + implementation("io.github.pdvrieze.xmlutil:serialization-jvm:0.90.3") { + exclude("org.jetbrains.kotlinx", "kotlinx-serialization-json") + exclude("org.jetbrains.kotlinx", "kotlinx-serialization-core") + } + + testImplementation(gradleTestKit()) + testImplementation(libs.bundles.kotest) + testImplementation(libs.mockk) + testImplementation(libs.junit) + testImplementation(libs.truth) + testImplementation("commons-io:commons-io:2.15.1") + testImplementation(kotlin("test")) +} + +gradlePlugin { + plugins { + register("licensePlugin") { + id = "LicenseResolverPlugin" + implementationClass = "com.google.firebase.gradle.plugins.license.LicenseResolverPlugin" + } + register("continuousIntegrationPlugin") { + id = "firebase-ci" + implementationClass = "com.google.firebase.gradle.plugins.ci.ContinuousIntegrationPlugin" + } + register("smokeTestsPlugin") { + id = "smoke-tests" + implementationClass = "com.google.firebase.gradle.plugins.ci.SmokeTestsPlugin" + } + register("publishingPlugin") { + id = "PublishingPlugin" + implementationClass = "com.google.firebase.gradle.plugins.PublishingPlugin" + } + register("firebaseLibraryPlugin") { + id = "firebase-library" + implementationClass = "com.google.firebase.gradle.plugins.FirebaseAndroidLibraryPlugin" + } + register("firebaseJavaLibraryPlugin") { + id = "firebase-java-library" + implementationClass = "com.google.firebase.gradle.plugins.FirebaseJavaLibraryPlugin" + } + register("firebaseVendorPlugin") { + id = "firebase-vendor" + implementationClass = "com.google.firebase.gradle.plugins.VendorPlugin" + } + register("copyGoogleServicesPlugin") { + id = "copy-google-services" + implementationClass = "com.google.firebase.gradle.plugins.CopyGoogleServicesPlugin" + } + } +} + +tasks.withType { + testLogging { + // Make sure output from standard out or error is shown in Gradle output. + showStandardStreams = true + } +} diff --git a/buildSrc/resources/dummy.apk b/plugins/resources/dummy.apk similarity index 100% rename from buildSrc/resources/dummy.apk rename to plugins/resources/dummy.apk diff --git a/buildSrc/settings.gradle.kts b/plugins/settings.gradle.kts similarity index 100% rename from buildSrc/settings.gradle.kts rename to plugins/settings.gradle.kts diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/NdkBinaryFixTask.kt b/plugins/src/main/java/com/google/firebase/gradle/NdkBinaryFixTask.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/NdkBinaryFixTask.kt rename to plugins/src/main/java/com/google/firebase/gradle/NdkBinaryFixTask.kt diff --git a/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateBomReleaseNotesTask.kt b/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateBomReleaseNotesTask.kt new file mode 100644 index 00000000000..75bb991dd60 --- /dev/null +++ b/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateBomReleaseNotesTask.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2025 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.bomgenerator + +import com.google.firebase.gradle.plugins.createIfAbsent +import com.google.firebase.gradle.plugins.datamodels.ArtifactDependency +import com.google.firebase.gradle.plugins.datamodels.PomElement +import com.google.firebase.gradle.plugins.datamodels.fullArtifactName +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction + +/** + * Generates the release notes for a bom. + * + * @see GenerateBomTask + */ +abstract class GenerateBomReleaseNotesTask : DefaultTask() { + @get:InputFile abstract val currentBom: RegularFileProperty + + @get:Input abstract val previousBom: Property + + @get:OutputFile abstract val releaseNotesFile: RegularFileProperty + + @get:Internal abstract val previousBomVersions: MapProperty + + @TaskAction + fun generate() { + val bom = PomElement.fromFile(currentBom.asFile.get()) + val currentDeps = bom.dependencyManagement?.dependencies.orEmpty() + val previousDeps = previousBom.get().dependencyManagement?.dependencies.orEmpty() + previousBomVersions.set(previousDeps.associate { it.fullArtifactName to it.version }) + + val sortedDependencies = currentDeps.sortedBy { it.toString() } + + val headingId = "{: #bom_v${bom.version.replace(".", "-")}}" + + releaseNotesFile.asFile + .get() + .createIfAbsent() + .writeText( + """ + |### {{firebase_bom_long}} ({{bill_of_materials}}) version ${bom.version} $headingId + |{% comment %} + |These library versions must be flat-typed, do not use variables. + |The release note for this BoM version is a library-version snapshot. + |{% endcomment %} + | + | + | + """ + .trimMargin() + ) + } + + private fun artifactToListEntry(artifact: ArtifactDependency): String { + val previousVersion = previousBomVersions.get()[artifact.fullArtifactName] ?: "N/A" + val artifactName = "${artifact.groupId}:${artifact.artifactId}" + + return if (artifact.version != previousVersion) { + """ + | + | ${artifactName} + | $previousVersion + | ${artifact.version} + | + """ + .trimMargin() + } else { + """ + | + | ${artifactName} + | $previousVersion + | ${artifact.version} + | + """ + .trimMargin() + } + } +} diff --git a/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateBomTask.kt b/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateBomTask.kt new file mode 100644 index 00000000000..9fb7fe35130 --- /dev/null +++ b/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateBomTask.kt @@ -0,0 +1,221 @@ +/* + * Copyright 2025 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.bomgenerator + +import com.google.firebase.gradle.plugins.ModuleVersion +import com.google.firebase.gradle.plugins.VersionType +import com.google.firebase.gradle.plugins.createIfAbsent +import com.google.firebase.gradle.plugins.datamodels.ArtifactDependency +import com.google.firebase.gradle.plugins.datamodels.DependencyManagementElement +import com.google.firebase.gradle.plugins.datamodels.LicenseElement +import com.google.firebase.gradle.plugins.datamodels.PomElement +import com.google.firebase.gradle.plugins.datamodels.fullArtifactName +import com.google.firebase.gradle.plugins.datamodels.moduleVersion +import com.google.firebase.gradle.plugins.orEmpty +import com.google.firebase.gradle.plugins.pairBy +import com.google.firebase.gradle.plugins.partitionNotNull +import com.google.firebase.gradle.plugins.services.GMavenService +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.services.ServiceReference +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +/** + * Generates the firebase bom, using gmaven as a source of truth for artifacts and versions. + * + * @see validateArtifacts + * @see GenerateBomReleaseNotesTask + * @see GenerateTutorialBundleTask + */ +abstract class GenerateBomTask : DefaultTask() { + /** + * Artifacts to include in the bom. + * + * ``` + * bomArtifacts.set(listOf( + * "com.google.firebase:firebase-firestore", + * "com.google.firebase:firebase-storage" + * )) + * ``` + */ + @get:Input abstract val bomArtifacts: ListProperty + + /** + * Artifacts to exclude from the bom. + * + * These are artifacts that are under the `com.google.firebase` namespace, but are intentionally + * not included in the bom. + * + * ``` + * bomArtifacts.set(listOf( + * "com.google.firebase:crashlytics", + * "com.google.firebase:crash-plugin" + * )) + * ``` + */ + @get:Input abstract val ignoredArtifacts: ListProperty + + /** + * Optional map of versions to use instead of the versions on gmaven. + * + * ``` + * versionOverrides.set(mapOf( + * "com.google.firebase:firebase-firestore" to "10.0.0" + * )) + * ``` + */ + @get:Input abstract val versionOverrides: MapProperty + + /** Directory to save the bom under. */ + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + + @get:ServiceReference("gmaven") abstract val gmaven: Property + + @TaskAction + fun generate() { + val versionOverrides = versionOverrides.getOrElse(emptyMap()) + + val validatedArtifactsToPublish = validateArtifacts() + val artifactsToPublish = + validatedArtifactsToPublish.map { + val version = versionOverrides[it.fullArtifactName] ?: it.version + logger.debug("Using ${it.fullArtifactName} with version $version") + + it.copy(version = version) + } + + val newVersion = determineNewBomVersion(artifactsToPublish) + + val pom = + PomElement( + namespace = "http://maven.apache.org/POM/4.0.0", + schema = "http://www.w3.org/2001/XMLSchema-instance", + schemaLocation = + "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd", + modelVersion = "4.0.0", + groupId = "com.google.firebase", + artifactId = "firebase-bom", + version = newVersion.toString(), + packaging = "pom", + licenses = + listOf( + LicenseElement( + name = "The Apache Software License, Version 2.0", + url = "http://www.apache.org/licenses/LICENSE-2.0.txt", + distribution = "repo", + ) + ), + dependencyManagement = DependencyManagementElement(artifactsToPublish), + ) + + val bomFile = + outputDirectory.file( + "com/google/firebase/firebase-bom/$newVersion/firebase-bom-$newVersion.pom" + ) + + pom.toFile(bomFile.get().asFile.createIfAbsent()) + } + + private fun determineNewBomVersion( + releasingDependencies: List + ): ModuleVersion { + logger.info("Determining the new bom version") + + val oldBom = gmaven.get().latestPom("com.google.firebase", "firebase-bom") + val oldBomVersion = ModuleVersion.fromString(oldBom.artifactId, oldBom.version) + + val oldBomDependencies = oldBom.dependencyManagement?.dependencies.orEmpty() + val changedDependencies = oldBomDependencies.pairBy(releasingDependencies) { it.artifactId } + + val versionBumps = + changedDependencies.mapNotNull { (old, new) -> + if (old == null) { + logger.warn("Dependency was added: ${new?.fullArtifactName}") + + VersionType.MINOR + } else if (new === null) { + logger.warn("Dependency was removed: ${old.fullArtifactName}") + + VersionType.MAJOR + } else { + old.moduleVersion.bumpFrom(new.moduleVersion) + } + } + + val finalBump = versionBumps.minOrNull() + return oldBomVersion.bump(finalBump) + } + + /** + * Validates that the provided bom artifacts satisfy the following constraints: + * - All are released and live on gmaven. + * - They include _all_ of the firebase artifacts on gmaven, unless they're specified in + * [ignoredArtifacts].+ + * + * @return The validated artifacts to release. + * @throws RuntimeException If any of the validations fail. + */ + private fun validateArtifacts(): List { + logger.info("Validating bom artifacts") + + val firebaseArtifacts = bomArtifacts.get().toSet() + val ignoredArtifacts = ignoredArtifacts.orEmpty().toSet() + + val allFirebaseArtifacts = + gmaven + .get() + .groupIndex("com.google.firebase") + .map { "${it.groupId}:${it.artifactId}" } + .toSet() + + val (released, unreleased) = + firebaseArtifacts + .associateWith { gmaven.get().groupIndexArtifactOrNull(it) } + .partitionNotNull() + + if (unreleased.isNotEmpty()) { + throw RuntimeException( + """ + |Some artifacts required for bom generation are not live on gmaven yet: + |${unreleased.joinToString("\n")} + """ + .trimMargin() + ) + } + + val requiredArtifacts = allFirebaseArtifacts - ignoredArtifacts + val missingArtifacts = requiredArtifacts - firebaseArtifacts + if (missingArtifacts.isNotEmpty()) { + throw RuntimeException( + """ + |There are Firebase artifacts missing from the provided bom artifacts. + |Add the artifacts to the ignoredArtifacts property to ignore them or to the bomArtifacts property to include them in the bom. + |Dependencies missing: + |${missingArtifacts.joinToString("\n")} + """ + .trimMargin() + ) + } + + return released.values.map { it.toArtifactDependency() } + } +} diff --git a/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateTutorialBundleTask.kt b/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateTutorialBundleTask.kt new file mode 100644 index 00000000000..b315e7188aa --- /dev/null +++ b/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/GenerateTutorialBundleTask.kt @@ -0,0 +1,312 @@ +/* + * Copyright 2025 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.bomgenerator + +import com.google.firebase.gradle.plugins.createIfAbsent +import com.google.firebase.gradle.plugins.multiLine +import com.google.firebase.gradle.plugins.orEmpty +import com.google.firebase.gradle.plugins.services.GMavenService +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.services.ServiceReference +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction + +/** + * Generates the tutorial bundle recipe for a release. + * + * This task uses gmaven as a source of truth, and as such, should be ran _after_ the artifacts are + * live on gmaven. + * + * @see GenerateBomTask + */ +abstract class GenerateTutorialBundleTask : DefaultTask() { + /** + * Firebase library artifacts. + * + * ``` + * firebaseArtifacts.set(listOf( + * "com.google.firebase:firebase-analytics", + * "com.google.firebase:firebase-crashlytics" + * )) + * ``` + */ + @get:Input abstract val firebaseArtifacts: ListProperty + + /** + * Common artifacts and dependencies whose versions also need to be tracked during releases. + * + * ``` + * commonArtifacts.set(listOf( + * "com.google.gms:google-services", + * )) + * ``` + */ + @get:Input abstract val commonArtifacts: ListProperty + + /** + * Firebase gradle plugins. + * + * ``` + * gradlePlugins.set(listOf( + * "com.google.firebase:firebase-appdistribution-gradle", + * "com.google.firebase:firebase-crashlytics-gradle" + * )) + * ``` + */ + @get:Input abstract val gradlePlugins: ListProperty + + /** + * Performance monitoring related artifacts. + * + * ``` + * firebaseArtifacts.set(listOf( + * "com.google.firebase:perf-plugin" + * )) + * ``` + */ + @get:Input abstract val perfArtifacts: ListProperty + + /** + * All artifacts that are expected to be present. + * + * You can use this to verify that the input doesn't exclude any artifacts. + * + * ``` + * requiredArtifacts.set(listOf( + * "com.google.firebase:firebase-analytics", + * "com.google.firebase:perf-plugin" + * )) + * ``` + */ + @get:Input abstract val requiredArtifacts: ListProperty + + /** + * Optional map of versions to use instead of the versions on gmaven. + * + * ``` + * versionOverrides.set(mapOf( + * "com.google.firebase:firebase-firestore" to "10.0.0" + * )) + * ``` + */ + @get:Input abstract val versionOverrides: MapProperty + + /** The file to save the generated tutorial to. */ + @get:OutputFile abstract val tutorialFile: RegularFileProperty + + @get:ServiceReference("gmaven") abstract val gmaven: Property + + @TaskAction + fun generate() { + val firebaseArtifacts = firebaseArtifacts.orEmpty() + val commonArtifacts = commonArtifacts.orEmpty() + val gradlePlugins = gradlePlugins.orEmpty() + val perfArtifacts = perfArtifacts.orEmpty() + val requiredArtifacts = requiredArtifacts.orEmpty() + + val allArtifacts = firebaseArtifacts + commonArtifacts + gradlePlugins + perfArtifacts + + val missingArtifacts = requiredArtifacts - allArtifacts.toSet() + if (missingArtifacts.isNotEmpty()) { + throw RuntimeException( + multiLine( + "Artifacts required for the tutorial bundle are missing from the provided input:", + missingArtifacts, + ) + ) + } + + val sections = + listOfNotNull( + generateSection("Common Firebase dependencies", commonArtifacts), + generateSection("Firebase SDK libraries", firebaseArtifacts), + generateSection("Firebase Gradle plugins", gradlePlugins), + generateSection("Performance Monitoring", perfArtifacts), + ) + + tutorialFile + .get() + .asFile + .createIfAbsent() + .writeText( + """ + | + | + """ + .trimMargin() + ) + } + + private fun generateSection(name: String, artifacts: List): String? { + if (artifacts.isEmpty()) { + logger.warn("Skipping section, since no data was provided: $name") + return null + } else { + logger.info("Using artifacts for section ($name): ${artifacts.joinToString()}") + } + + val mappingKeys = mappings.keys + + val (supported, unsupported) = artifacts.partition { mappingKeys.contains(it) } + if (unsupported.isNotEmpty()) { + logger.info( + multiLine( + "The following artifacts are missing mapping keys.", + "This is likely intentional, but the artifacts will be listed for debugging purposes:", + unsupported, + ) + ) + } + + val sortedArtifacts = supported.sortedBy { mappingKeys.indexOf(it) } + val artifactSection = sortedArtifacts.map { artifactVariableString(it) } + + return multiLine("", artifactSection).prependIndent(" ") + } + + private fun versionString(fullArtifactName: String): String { + val overrideVersion = versionOverrides.orEmpty()[fullArtifactName] + + if (overrideVersion != null) { + logger.info("Using a version override for an artifact ($fullArtifactName): $overrideVersion") + + return overrideVersion + } else { + logger.info("Fetching the latest version for an artifact: $fullArtifactName") + + return gmaven.get().latestVersionOrNull(fullArtifactName) + ?: throw RuntimeException( + "An artifact required for the tutorial bundle is missing from gmaven: $fullArtifactName" + ) + } + } + + private fun artifactVariableString(fullArtifactName: String): String { + val (name, alias, extra) = mappings[fullArtifactName]!! + + return multiLine( + "", + "", + extra, + ) + } + + companion object { + /** + * A linked mapping for artifact ids to their respective [ArtifactTutorialMapping] metadata. + * + * Since this is a _linked_ map, the order is preserved in the tutorial output. + */ + private val mappings = + linkedMapOf( + "com.google.gms:google-services" to + ArtifactTutorialMapping( + "Google Services Plugin", + "google-services-plugin-class", + listOf( + "", + "", + ), + ), + "com.google.firebase:firebase-analytics" to + ArtifactTutorialMapping("Analytics", "analytics-dependency"), + "com.google.firebase:firebase-crashlytics" to + ArtifactTutorialMapping("Crashlytics", "crashlytics-dependency"), + "com.google.firebase:firebase-perf" to + ArtifactTutorialMapping("Performance Monitoring", "perf-dependency"), + "com.google.firebase:firebase-vertexai" to + ArtifactTutorialMapping("Vertex AI in Firebase", "vertex-dependency"), + "com.google.firebase:firebase-messaging" to + ArtifactTutorialMapping("Cloud Messaging", "messaging-dependency"), + "com.google.firebase:firebase-auth" to + ArtifactTutorialMapping("Authentication", "auth-dependency"), + "com.google.firebase:firebase-database" to + ArtifactTutorialMapping("Realtime Database", "database-dependency"), + "com.google.firebase:firebase-storage" to + ArtifactTutorialMapping("Cloud Storage", "storage-dependency"), + "com.google.firebase:firebase-config" to + ArtifactTutorialMapping("Remote Config", "remote-config-dependency"), + "com.google.android.gms:play-services-ads" to + ArtifactTutorialMapping("Admob", "ads-dependency"), + "com.google.firebase:firebase-firestore" to + ArtifactTutorialMapping("Cloud Firestore", "firestore-dependency"), + "com.google.firebase:firebase-functions" to + ArtifactTutorialMapping("Firebase Functions", "functions-dependency"), + "com.google.firebase:firebase-inappmessaging-display" to + ArtifactTutorialMapping("FIAM Display", "fiamd-dependency"), + "com.google.firebase:firebase-ml-vision" to + ArtifactTutorialMapping("Firebase MLKit Vision", "ml-vision-dependency"), + "com.google.firebase:firebase-appdistribution-gradle" to + ArtifactTutorialMapping( + "App Distribution", + "appdistribution-plugin-class", + listOf(""), + ), + "com.google.firebase:firebase-crashlytics-gradle" to + ArtifactTutorialMapping( + "Crashlytics", + "crashlytics-plugin-class", + listOf(""), + ), + "com.google.firebase:perf-plugin" to + ArtifactTutorialMapping( + "Perf Plugin", + "perf-plugin-class", + listOf(""), + ), + ) + } +} + +/** + * Metadata for an artifact to use in generation of the tutorial. + * + * For example, given the following: + * ``` + * ArtifactTutorialMapping( + * "Perf Plugin", + * "perf-plugin-class", + * listOf("") + * ) + * ``` + * + * The tutorial will generate the following output: + * ```html + * + * + * + * ``` + * + * _Assuming the latest version on gmaven is `1.2.3`._ + * + * @property name The space separated, capitalized, full name of the artifact. + * @property alias The internal alias of the artifact. + * @property extra Optional additional data to add after the metadata entry in the tutorial. + * @see GenerateTutorialBundleTask.mappings + */ +private data class ArtifactTutorialMapping( + val name: String, + val alias: String, + val extra: List = emptyList(), +) diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/tagging/GitClient.java b/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/tagging/GitClient.java similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/tagging/GitClient.java rename to plugins/src/main/java/com/google/firebase/gradle/bomgenerator/tagging/GitClient.java diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/tagging/ShellExecutor.java b/plugins/src/main/java/com/google/firebase/gradle/bomgenerator/tagging/ShellExecutor.java similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/bomgenerator/tagging/ShellExecutor.java rename to plugins/src/main/java/com/google/firebase/gradle/bomgenerator/tagging/ShellExecutor.java diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/BaseFirebaseLibraryPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/BaseFirebaseLibraryPlugin.kt similarity index 98% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/BaseFirebaseLibraryPlugin.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/BaseFirebaseLibraryPlugin.kt index 3a146524a84..b8f2c5108fb 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/BaseFirebaseLibraryPlugin.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/BaseFirebaseLibraryPlugin.kt @@ -18,6 +18,7 @@ package com.google.firebase.gradle.plugins import com.android.build.gradle.LibraryExtension import com.google.firebase.gradle.plugins.ci.Coverage +import com.google.firebase.gradle.plugins.services.GMavenService import java.io.File import java.nio.file.Paths import org.gradle.api.Plugin @@ -52,6 +53,7 @@ import org.w3c.dom.Element abstract class BaseFirebaseLibraryPlugin : Plugin { protected fun setupDefaults(project: Project, library: FirebaseLibraryExtension) { with(library) { + project.gradle.sharedServices.registerIfAbsent("gmaven") previewMode.convention("") publishJavadoc.convention(true) artifactId.convention(project.name) diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/Changelog.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/Changelog.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/Changelog.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/Changelog.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/CopyGoogleServicesPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/CopyGoogleServicesPlugin.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/CopyGoogleServicesPlugin.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/CopyGoogleServicesPlugin.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/DackkaGenerationTask.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/DackkaGenerationTask.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/DackkaGenerationTask.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/DackkaGenerationTask.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/DackkaPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/DackkaPlugin.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/DackkaPlugin.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/DackkaPlugin.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseAndroidLibraryPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseAndroidLibraryPlugin.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseAndroidLibraryPlugin.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseAndroidLibraryPlugin.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseJavaLibraryPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseJavaLibraryPlugin.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseJavaLibraryPlugin.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseJavaLibraryPlugin.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseLibraryExtension.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseLibraryExtension.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseLibraryExtension.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseLibraryExtension.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseStaticAnalysis.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseStaticAnalysis.java similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseStaticAnalysis.java rename to plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseStaticAnalysis.java diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FiresiteTransformTask.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/FiresiteTransformTask.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/FiresiteTransformTask.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/FiresiteTransformTask.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GitSubmodulePlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/GitSubmodulePlugin.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/GitSubmodulePlugin.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/GitSubmodulePlugin.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GmavenHelper.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/GmavenHelper.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/GmavenHelper.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/GmavenHelper.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GmavenVersionChecker.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/GmavenVersionChecker.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/GmavenVersionChecker.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/GmavenVersionChecker.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt similarity index 56% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt index 9a071df3f69..88cd9927045 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt @@ -16,13 +16,25 @@ package com.google.firebase.gradle.plugins +import com.android.build.api.variant.LibraryAndroidComponentsExtension +import com.android.build.api.variant.LibraryVariant +import java.io.BufferedOutputStream import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream import org.gradle.api.DefaultTask import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.artifacts.Dependency import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.file.Directory +import org.gradle.api.plugins.PluginManager import org.gradle.api.provider.Provider +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters +import org.gradle.api.services.BuildServiceRegistry +import org.gradle.api.services.BuildServiceSpec import org.gradle.kotlin.dsl.apply import org.gradle.workers.WorkAction import org.gradle.workers.WorkParameters @@ -165,7 +177,7 @@ inline fun attributeFrom(name: String) = Attribute.of(name, T::class * attribute(Attribute.of(name, T::class.java), value) * ``` */ -inline fun AttributeContainer.attribute(name: String, value: T) = +inline fun AttributeContainer.attribute(name: String, value: T) = attribute(attributeFrom(name), value) /** @@ -174,8 +186,7 @@ inline fun AttributeContainer.attribute(name: String, value: T) = * pluginManager.apply(T::class) * ``` */ -inline fun org.gradle.api.plugins.PluginManager.`apply`(): Unit = - `apply`(T::class) +inline fun PluginManager.apply(): Unit = apply(T::class) /** * The name provided to this artifact when published. @@ -186,4 +197,113 @@ inline fun org.gradle.api.plugins.PluginManager.`apply`(): Uni * ``` */ val Dependency.artifactName: String - get() = listOf(group, name, version).filterNotNull().joinToString(":") + get() = listOfNotNull(group, name, version).joinToString(":") + +/** + * Creates an archive of this directory at the [dest] file. + * + * Should only be ran within the context of a [Task], as outside of a [Task] so you should likely be + * using the `copy` or `sync` tasks instead. + */ +fun File.zipFilesTo(task: Task, dest: File): File { + val logger = task.logger + + logger.info("Zipping '$absolutePath' to '${dest.absolutePath}'") + + logger.debug("Ensuring parent directories are present for zip file") + dest.parentFile?.mkdirs() + + logger.debug("Creating empty zip file to write to") + dest.createNewFile() + + logger.debug("Packing file contents into zip") + ZipOutputStream(BufferedOutputStream(dest.outputStream())).use { zipFile -> + for (file in walk().filter { it.isFile }) { + val relativePath = file.relativeTo(this).unixPath + logger.debug("Adding file to zip: $relativePath") + + zipFile.putNextEntry(ZipEntry(relativePath)) + file.inputStream().use { it.copyTo(zipFile) } + zipFile.closeEntry() + } + } + + return dest +} + +/** + * Bind a callback to run whenever there are release variants for this android build. + * + * Syntax sugar for: + * ``` + * components.onVariants(components.selector().withBuildType("release")) { + * // ... + * } + * ``` + * + * @see LibraryAndroidComponentsExtension.onVariants + */ +fun LibraryAndroidComponentsExtension.onReleaseVariants( + callback: (variant: LibraryVariant) -> Unit +) { + onVariants(selector().withBuildType("release"), callback) +} + +/** + * Register a build service under the specified [name], if it hasn't been registered already. + * + * ``` + * project.gradle.sharedServices.registerIfAbsent("gmaven") + * ``` + * + * @param T The build service class to register + * @param P The parameters class for the build service to register + * @param name The name to register the build service under + * @param config An optional configuration block to setup the build service with + */ +inline fun , reified P : BuildServiceParameters> BuildServiceRegistry + .registerIfAbsent(name: String, noinline config: BuildServiceSpec

.() -> Unit = {}) = + registerIfAbsent(name, T::class.java, config) + +/** + * The value of this provider if present, or an empty list if it's not present. + * + * @return The value of this provider or an empty list. + */ +fun > Provider.orEmpty() = orNull.orEmpty() + +/** + * The value of this provider if present, or an empty map if it's not present. + * + * @return The value of this provider or an map list. + */ +fun > Provider.orEmpty() = orNull.orEmpty() + +/** + * Maps to the single file (non directory) within this directory, or throws an exception if it can't + * find a file or if there's more than one file. + * + * Helper wrapper around [Directory.nestedFile] for providers. + */ +val Provider.nestedFile: Provider + get() = map { it.nestedFile } + +/** + * Maps to the single file (non directory) within this directory, or throws an exception if it can't + * find a file or if there's more than one file. + * + * Useful in situations where a directory merely acts as a container for a nested file whose name + * isn't known at compile time. + * + * For example, given the following directory structure: + * ``` + * com/ + * google/ + * firebase/ + * firebase-bom-34.8.0.pom + * ``` + * + * This will result in the `firebase-bom-34.8.0.pom` file being returned. + */ +val Directory.nestedFile: File + get() = asFileTree.single { it.isFile } diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/KotlinUtils.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinExtensions.kt similarity index 58% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/KotlinUtils.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinExtensions.kt index c261d89d075..8f058603e3b 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/KotlinUtils.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinExtensions.kt @@ -16,7 +16,10 @@ package com.google.firebase.gradle.plugins +import java.io.File +import java.io.InputStream import org.w3c.dom.Element +import org.w3c.dom.Node import org.w3c.dom.NodeList /** Replaces all matching substrings with an empty string (nothing) */ @@ -26,16 +29,48 @@ fun String.remove(regex: Regex) = replace(regex, "") fun String.remove(str: String) = replace(str, "") /** - * Returns a sequence containing all elements. - * - * The operation is _terminal_. + * Joins a variable amount of [objects][Any.toString] to a single [String] split by newlines (`\n`). * - * Syntax sugar for: + * For example: + * ```kotlin + * multiLine("Hello", "World", "!") shouldBeText + * """ + * Hello + * World + * ! + * """.trimIndent() * ``` - * take(count()) + * + * If any of the elements are collections, their elements will be recursively joined instead. + * + * ```kotlin + * multiLine( + * "Hello", + * listOf("World"), + * listOf("Goodbye", listOf("World", "!"), + * emptyList() + * ) shouldBeText + * """ + * Hello + * World + * Goodbye + * World + * ! + * """.trimIndent() * ``` + * + * _Note:_ Empty collections will not be rendered. */ -public fun Sequence.takeAll(): Sequence = take(count()) +fun multiLine(vararg strings: Any?): String = + strings + .filter { it !is Collection<*> || it.isNotEmpty() } + .joinToString("\n") { + if (it is Collection<*>) { + multiLine(*it.toTypedArray()) + } else { + it.toString() + } + } /** * Converts an [Element] to an Artifact string. @@ -77,19 +112,30 @@ fun Element.toArtifactString() = * ``` * * @throws NoSuchElementException if the [Element] does not have descendant [Element]s with tags - * that match the components of an Artifact string; groupId, artifactId, version. + * that match the components of an Artifact string; groupId and artifactId. */ fun Element.toMavenName() = "${textByTag("groupId")}:${textByTag("artifactId")}" /** - * Finds a descendant [Element] by a given [tag], and returns the [textContent] - * [Element.getTextContent] of it. + * Finds a descendant [Element] by a [tag], and returns the [textContent][Element.getTextContent] of + * it. * * @param tag the XML tag to filter for (the special value "*" matches all tags) * @throws NoSuchElementException if an [Element] with the given [tag] does not exist * @see findElementsByTag + * @see textByTagOrNull */ -fun Element.textByTag(tag: String) = findElementsByTag(tag).first().textContent +fun Element.textByTag(tag: String) = + textByTagOrNull(tag) ?: throw RuntimeException("Element tag was missing: $tag") + +/** + * Finds a descendant [Element] by a [tag], and returns the [textContent][Element.getTextContent] of + * it, or null if it couldn't be found. + * + * @param tag the XML tag to filter for (the special value "*" matches all tags) + * @see textByTag + */ +fun Element.textByTagOrNull(tag: String) = findElementsByTag(tag).firstOrNull()?.textContent /** * Finds a descendant [Element] by a given [tag], or creates a new one. @@ -99,7 +145,7 @@ fun Element.textByTag(tag: String) = findElementsByTag(tag).first().textContent * @param tag the XML tag to filter for (the special value "*" matches all tags) * @see findElementsByTag */ -fun Element.findOrCreate(tag: String) = +fun Element.findOrCreate(tag: String): Element = findElementsByTag(tag).firstOrNull() ?: ownerDocument.createElement(tag).also { appendChild(it) } /** @@ -114,31 +160,32 @@ fun Element.findOrCreate(tag: String) = fun Element.findElementsByTag(tag: String) = getElementsByTagName(tag).children().mapNotNull { it as? Element } +/** + * Returns the text of an attribute, if it exists. + * + * @param name The name of the attribute to get the text for + */ +fun Node.textByAttributeOrNull(name: String) = attributes?.getNamedItem(name)?.textContent + /** * Yields the items of this [NodeList] as a [Sequence]. * * [NodeList] does not typically offer an iterator. This extension method offers a means to loop - * through a NodeList's [item][NodeList.item] method, while also taking into account its [length] - * [NodeList.getLength] property to avoid an [IndexOutOfBoundsException]. + * through a NodeList's [item][NodeList.item] method, while also taking into account the element's + * [length][NodeList.getLength] property to avoid an [IndexOutOfBoundsException]. * * Additionally, this operation is _intermediate_ and _stateless_. */ -fun NodeList.children() = sequence { - for (index in 0..length) { - yield(item(index)) +fun NodeList.children(removeDOMSections: Boolean = true) = sequence { + for (index in 0 until length) { + val child = item(index) + + if (!removeDOMSections || !child.nodeName.startsWith("#")) { + yield(child) + } } } -/** - * Joins a variable amount of [strings][Any.toString] to a single [String] split by newlines (`\n`). - * - * For example: - * ```kotlin - * println(multiLine("Hello", "World", "!")) // "Hello\nWorld\n!" - * ``` - */ -fun multiLine(vararg strings: Any?) = strings.joinToString("\n") - /** * Returns the first match of a regular expression in the [input], beginning at the specified * [startIndex]. @@ -152,6 +199,26 @@ fun Regex.findOrThrow(input: CharSequence, startIndex: Int = 0) = find(input, startIndex) ?: throw RuntimeException(multiLine("No match found for the given input:", input.toString())) +/** + * Returns the value of the first capture group. + * + * Intended to be used in [MatchResult] that are only supposed to capture a single entry. + */ +val MatchResult.firstCapturedValue: String + get() = groupValues[1] + +/** + * Returns a sequence containing all elements. + * + * The operation is _terminal_. + * + * Syntax sugar for: + * ``` + * take(count()) + * ``` + */ +fun Sequence.takeAll(): Sequence = take(count()) + /** * Creates a [Pair] out of an [Iterable] with only two elements. * @@ -212,14 +279,6 @@ fun List.replaceMatches(regex: Regex, transform: (MatchResult) -> String } } -/** - * Returns the value of the first capture group. - * - * Intended to be used in [MatchResult] that are only supposed to capture a single entry. - */ -val MatchResult.firstCapturedValue: String - get() = groupValues[1] - /** * Creates a diff between two lists. * @@ -237,6 +296,43 @@ infix fun List.diff(other: List): List> { return firstList.zip(secondList).filter { it.first != it.second } } +/** + * Creates a list of pairs between two lists, matching according to the provided [mapper]. + * + * ```kotlin + * data class Person(name: String, age: Int) + * + * val firstList = listOf( + * Person("Mike", 5), + * Person("Rachel", 6) + * ) + * + * val secondList = listOf( + * Person("Michael", 4), + * Person("Mike", 1) + * ) + * + * val diffList = firstList.pairBy(secondList) { + * it.name + * } + * + * diffList shouldBeEqualTo listOf( + * Person("Mike", 5) to Person("Mike", 1) + * Person("Rachel", 6) to null + * null to Person("Mike", 1) + * ) + * ``` + */ +inline fun List.pairBy(other: List, mapper: (T) -> R): List> { + val firstMap = associateBy { mapper(it) } + val secondMap = other.associateBy { mapper(it) } + + val changedOrRemoved = firstMap.map { it.value to secondMap[it.key] } + val added = secondMap.filterKeys { it !in firstMap }.map { null to it.value } + + return changedOrRemoved + added +} + /** * Creates a list that is forced to certain size. * @@ -250,3 +346,91 @@ infix fun List.diff(other: List): List> { * ``` */ fun List.coerceToSize(targetSize: Int) = List(targetSize) { getOrNull(it) } + +/** + * Writes the [InputStream] to this file. + * + * While this method _does_ close the generated output stream, it's the callers responsibility to + * close the passed [stream]. + * + * @return This [File] instance for chaining. + */ +fun File.writeStream(stream: InputStream): File { + outputStream().use { stream.copyTo(it) } + return this +} + +/** + * Creates the the path to a file if it doesn't already exist. + * + * This includes creating the directories for this file. + * + * @return This [File] instance for chaining. + */ +fun File.createIfAbsent(): File { + parentFile?.mkdirs() + createNewFile() + return this +} + +/** + * The [path][File.path] represented as a qualified unix path. + * + * Useful when a system expects a unix path, but you need to be able to run it on non unix systems. + * + * @see absoluteUnixPath + */ +val File.unixPath: String + get() = path.replace("\\", "/") + +/** + * The [absolutePath][File.getAbsolutePath] represented as a qualified unix path. + * + * Useful when a system expects a unix path, but you need to be able to run it on non unix systems. + * + * @see unixPath + */ +val File.absoluteUnixPath: String + get() = absolutePath.replace("\\", "/") + +/** + * Partitions a map with nullable values into a map of non null values and a list of keys with null + * values. + * + * For example: + * ``` + * val weekdays = mapOf( + * "Monday" to 0, + * "Tuesday" to 1, + * "Wednesday" to null, + * "Thursday" to 3, + * "Friday" to null, + * ) + * + * val (validDays, invalidDays) = weekdays.partitionNotNull() + * + * validDays shouldEqual mapOf( + * "Monday" to 0, + * "Tuesday" to 1, + * "Thursday" to 3, + * ) + * invalidDays shouldContainExactly listOf("Wednesday", "Friday") + * ``` + * + * @return A pair where the first component is a map of all the non null values and the second + * component is a list of the keys with null values. + */ +fun Map.partitionNotNull(): Pair, List> { + val nonNullEntries = mutableMapOf() + val nullEntries = mutableListOf() + + for ((key, value) in this) { + if (value !== null) { + nonNullEntries[key] = value + } else { + nullEntries.add(key) + } + } + + return nonNullEntries to nullEntries +} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/LibraryGroups.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/LibraryGroups.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/LibraryGroups.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/LibraryGroups.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/LibraryType.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/LibraryType.java similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/LibraryType.java rename to plugins/src/main/java/com/google/firebase/gradle/plugins/LibraryType.java diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/MakeReleaseNotesTask.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/MakeReleaseNotesTask.kt similarity index 100% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/MakeReleaseNotesTask.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/MakeReleaseNotesTask.kt diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/Metalava.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/Metalava.kt similarity index 97% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/Metalava.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/Metalava.kt index 56cd758a1ac..4d04701b7f7 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/Metalava.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/Metalava.kt @@ -40,7 +40,7 @@ val Project.metalavaConfig: Configuration ?: configurations.create("metalavaArtifacts") { this.dependencies.add( this@metalavaConfig.dependencies.create( - "com.android.tools.metalava:metalava:1.0.0-alpha06" + "com.android.tools.metalava:metalava:1.0.0-alpha11" ) ) } @@ -128,7 +128,7 @@ abstract class GenerateApiTxtTask : DefaultTask() { classPath.joinToString(":"), "--api", apiTxtFile.get().asFile.absolutePath, - "--format=v2", + "--format=v3", ) + if (updateBaseline.get()) listOf("--update-baseline") else if (baselineFile.get().asFile.exists()) @@ -170,7 +170,7 @@ abstract class ApiInformationTask : DefaultTask() { classPath.joinToString(":"), "--api", outputApiFile.get().asFile.absolutePath, - "--format=v2", + "--format=v3", ), ignoreFailure = true, ) @@ -187,7 +187,7 @@ abstract class ApiInformationTask : DefaultTask() { "AddedMethod", "--error", "AddedField", - "--format=v2", + "--format=v3", "--no-color", ) + if (updateBaseline.get()) listOf("--update-baseline") diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ModuleVersion.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/ModuleVersion.kt similarity index 71% rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ModuleVersion.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ModuleVersion.kt index ad3c45c59f3..3479c536699 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ModuleVersion.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/ModuleVersion.kt @@ -92,7 +92,7 @@ data class PreReleaseVersion(val type: PreReleaseVersionType, val build: Int = 1 */ fun fromStringsOrNull(type: String, build: String): PreReleaseVersion? = runCatching { - val preType = PreReleaseVersionType.valueOf(type.toUpperCase()) + val preType = PreReleaseVersionType.valueOf(type.uppercase()) val buildNumber = build.takeUnless { it.isBlank() }?.toInt() ?: 1 PreReleaseVersion(preType, buildNumber) @@ -115,7 +115,7 @@ data class PreReleaseVersion(val type: PreReleaseVersionType, val build: Int = 1 * PreReleaseVersion(RC, 12).toString() // "rc12" * ``` */ - override fun toString() = "${type.name.toLowerCase()}${build.toString().padStart(2, '0')}" + override fun toString() = "${type.name.lowercase()}${build.toString().padStart(2, '0')}" } /** @@ -140,7 +140,7 @@ data class ModuleVersion( ) : Comparable { /** Formatted as `MAJOR.MINOR.PATCH-PRE` */ - override fun toString() = "$major.$minor.$patch${pre?.let { "-${it.toString()}" } ?: ""}" + override fun toString() = "$major.$minor.$patch${pre?.let { "-$it" } ?: ""}" override fun compareTo(other: ModuleVersion) = compareValuesBy( @@ -149,7 +149,7 @@ data class ModuleVersion( { it.major }, { it.minor }, { it.patch }, - { it.pre == null }, // a version with no prerelease version takes precedence + { it.pre == null }, // a version with no pre-release version takes precedence { it.pre }, ) @@ -176,7 +176,7 @@ data class ModuleVersion( * ``` */ val VERSION_REGEX = - "(?\\d+)\\.(?\\d+)\\.(?\\d+)(?:\\-\\b)?(?

\\w\\D+)?(?\\B\\d+)?"
+      "(?\\d+)\\.(?\\d+)\\.(?\\d+)(?:-\\b)?(?
\\w\\D+)?(?\\B\\d+)?"
         .toRegex()
 
     /**
@@ -209,6 +209,53 @@ data class ModuleVersion(
           }
         }
         .getOrNull()
+
+    /**
+     * Parse a [ModuleVersion] from a string.
+     *
+     * You should use [fromStringOrNull] when you don't know the `artifactId` of the corresponding
+     * artifact, if you don't need to throw on failure, or if you need to throw a more specific
+     * message.
+     *
+     * This method exists to cover the common ground of getting [ModuleVersion] representations of
+     * artifacts.
+     *
+     * @param artifactId The artifact that this version belongs to. Will be used in the error
+     *   message on failure.
+     * @param version The version to parse into a [ModuleVersion].
+     * @return A [ModuleVersion] created from the string.
+     * @throws IllegalArgumentException If the string doesn't represent a valid semver version.
+     * @see fromStringOrNull
+     */
+    fun fromString(artifactId: String, version: String): ModuleVersion =
+      fromStringOrNull(version)
+        ?: throw IllegalArgumentException(
+          "Invalid module version found for '${artifactId}': $version"
+        )
+  }
+
+  /**
+   * Determine the [VersionType] representing the bump that would be required to reach [other], if
+   * any.
+   *
+   * ```
+   * ModuleVersion(1,0,0).bumpFrom(ModuleVersion(2,1,3)).shouldBeEqual(VersionType.MAJOR)
+   * ModuleVersion(1,0,0).bumpFrom(ModuleVersion(1,1,3)).shouldBeEqual(VersionType.MINOR)
+   * ModuleVersion(1,0,0).bumpFrom(ModuleVersion(1,0,3)).shouldBeEqual(VersionType.PATCH)
+   * ModuleVersion(1,0,0).bumpFrom(ModuleVersion(1,0,0)).shouldBeNull()
+   * ```
+   *
+   * @param other The target version to get the bump for.
+   * @return A [VersionType] representing the bump that this version would need to reach [other], or
+   *   null if they're the same version.
+   */
+  fun bumpFrom(other: ModuleVersion): VersionType? {
+    if (other.major != this.major) return VersionType.MAJOR
+    if (other.minor != this.minor) return VersionType.MINOR
+    if (other.patch != this.patch) return VersionType.PATCH
+    if (other.pre != this.pre) return VersionType.PRE
+
+    return null
   }
 
   /**
@@ -222,9 +269,10 @@ data class ModuleVersion(
       .let { it ?: if (pre != null) VersionType.PRE else VersionType.PATCH }
       .let {
         when (it) {
-          VersionType.MAJOR -> copy(major = major + 1)
-          VersionType.MINOR -> copy(minor = minor + 1)
-          VersionType.PATCH -> copy(patch = patch + 1)
+          VersionType.MAJOR ->
+            copy(major = major + 1, minor = 0, patch = 0, pre = pre?.copy(build = 1))
+          VersionType.MINOR -> copy(minor = minor + 1, patch = 0, pre = pre?.copy(build = 1))
+          VersionType.PATCH -> copy(patch = patch + 1, pre = pre?.copy(build = 1))
           VersionType.PRE -> copy(pre = pre?.bump())
         }
       }
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/MoveUnreleasedChangesTask.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/MoveUnreleasedChangesTask.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/MoveUnreleasedChangesTask.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/MoveUnreleasedChangesTask.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PomValidator.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/PomValidator.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/PomValidator.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/PomValidator.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PostReleasePlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/PostReleasePlugin.kt
similarity index 98%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/PostReleasePlugin.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/PostReleasePlugin.kt
index 4b34c9f737f..7bc3d81be20 100644
--- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PostReleasePlugin.kt
+++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/PostReleasePlugin.kt
@@ -43,6 +43,7 @@ class PostReleasePlugin : Plugin {
     val updatePinnedDependencies = registerUpdatePinnedDependenciesTask(project)
 
     project.tasks.register("postReleaseCleanup") {
+      // TODO(b/394606626): Add task for tagging releases
       dependsOn(versionBump, moveUnreleasedChanges, updatePinnedDependencies)
     }
   }
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ProjectUtils.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/ProjectExtensions.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ProjectUtils.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ProjectExtensions.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt
similarity index 65%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt
index da1c9e2b724..82a2aaae858 100644
--- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt
+++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt
@@ -16,7 +16,9 @@
 
 package com.google.firebase.gradle.plugins
 
-import com.google.firebase.gradle.bomgenerator.BomGeneratorTask
+import com.google.firebase.gradle.bomgenerator.GenerateBomReleaseNotesTask
+import com.google.firebase.gradle.bomgenerator.GenerateBomTask
+import com.google.firebase.gradle.bomgenerator.GenerateTutorialBundleTask
 import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.BUILD_BOM_ZIP_TASK
 import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.BUILD_KOTLINDOC_ZIP_TASK
 import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.BUILD_MAVEN_ZIP_TASK
@@ -25,7 +27,7 @@ import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.FIREBASE_PU
 import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.GENERATE_BOM_TASK
 import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.GENERATE_KOTLINDOC_FOR_RELEASE_TASK
 import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.PUBLISH_RELEASING_LIBS_TO_BUILD_TASK
-import com.google.firebase.gradle.plugins.semver.ApiDiffer
+import com.google.firebase.gradle.plugins.services.gmavenService
 import org.gradle.api.GradleException
 import org.gradle.api.Plugin
 import org.gradle.api.Project
@@ -40,7 +42,7 @@ import org.gradle.kotlin.dsl.register
 /**
  * Plugin for providing tasks to release [FirebaseLibrary][FirebaseAndroidLibraryPlugin] projects.
  *
- * Projects to release are computed via [computeReleasingLibraries]. A multitude of tasks are then
+ * Projects to release are computed via [computeReleaseMetadata]. A multitude of tasks are then
  * registered at the root project.
  *
  * The following pertain specifically to a release:
@@ -60,6 +62,10 @@ import org.gradle.kotlin.dsl.register
  * cycle):
  * - [BUILD_BOM_ZIP_TASK] -> Creates a zip file of the contents of [GENERATE_BOM_TASK]
  *   [registerGenerateBomTask]
+ * - [BUILD_BOM_BUNDLE_ZIP_TASK] -> Creates a zip file of the contents of [BUILD_BOM_ZIP_TASK]
+ *   [registerGenerateBomTask],
+ *   [GENERATE_BOM_RELEASE_NOTES_TASK][registerGenerateBomReleaseNotesTask] and
+ *   [GENERATE_TUTORIAL_BUNDLE_TASK][registerGenerateTutorialBundleTask]
  * - [RELEASE_GENEATOR_TASK][registerGenerateReleaseConfigFilesTask]
  * - [RELEASE_REPORT_GENERATOR_TASK][registerGenerateReleaseReportFilesTask]
  * - [PUBLISH_RELEASING_LIBS_TO_LOCAL_TASK][registerPublishReleasingLibrariesToMavenLocalTask]
@@ -83,6 +89,8 @@ abstract class PublishingPlugin : Plugin {
       val releasingProjects = releasingFirebaseLibraries.map { it.project }
 
       val generateBom = registerGenerateBomTask(project)
+      val generateBomReleaseNotes = registerGenerateBomReleaseNotesTask(project, generateBom)
+      val generateTutorialBundle = registerGenerateTutorialBundleTask(project)
       val validatePomForRelease = registerValidatePomForReleaseTask(project, releasingProjects)
       val checkHeadDependencies =
         registerCheckHeadDependenciesTask(project, releasingFirebaseLibraries)
@@ -134,9 +142,16 @@ abstract class PublishingPlugin : Plugin {
           destinationDirectory.set(project.layout.buildDirectory)
         }
 
-      project.tasks.register(BUILD_BOM_ZIP_TASK) {
-        from(generateBom)
-        archiveFileName.set("bom.zip")
+      val buildBomZip =
+        project.tasks.register(BUILD_BOM_ZIP_TASK) {
+          from(generateBom)
+          archiveFileName.set("bom.zip")
+          destinationDirectory.set(project.layout.buildDirectory)
+        }
+
+      project.tasks.register(BUILD_BOM_BUNDLE_ZIP_TASK) {
+        from(buildBomZip, generateBomReleaseNotes, generateTutorialBundle)
+        archiveFileName.set("bomBundle.zip")
         destinationDirectory.set(project.layout.projectDirectory)
       }
 
@@ -180,19 +195,19 @@ abstract class PublishingPlugin : Plugin {
    * Metadata can be provided either via the project properties or a [ReleaseConfig] file.
    *
    * The expected project properties can be defined as such:
-   * - `projectsToPublish` -> A comma seperated list of the publishing project(s) `artifactId`.
+   * - `projectsToPublish` -> A comma separated list of the publishing project(s) `artifactId`.
    * - `releaseName` -> The name of the release (such as `m123`)
    *
-   * When using project properties, this method will take into account [librariesToRelease]
-   * [FirebaseLibraryExtension.getLibrariesToRelease] -> so there's no need to specify multiple of
-   * the same co-releasing libs.
+   * When using project properties, this method will take into account the sibling libraries per
+   * [libraryGroup][FirebaseLibraryExtension.libraryGroup] -> so there's no need to specify multiple
+   * of the same co-releasing libs.
    *
-   * The [ReleaseConfig] is a pre-defined set of data for a release. It expects a valid [file]
-   * [ReleaseConfig.fromFile], which provides a list of libraries via their [path]
-   * [FirebaseLibraryExtension.getPath]. Additionally, it does __NOT__ take into account
-   * co-releasing libraries-> meaning libraries that should be releasing alongside one another will
-   * need to be individually specified in the [ReleaseConfig], otherwise it will likely cause an
-   * error during the release process.
+   * The [ReleaseConfig] is a pre-defined set of data for a release. It expects a valid
+   * `release.json` [file][ReleaseConfig.fromFile], which provides a list of libraries via their
+   * respective project [paths][FirebaseLibraryExtension.path]. Additionally, it does __NOT__ take
+   * into account co-releasing libraries-> meaning libraries that should be releasing alongside one
+   * another will need to be individually specified in the [ReleaseConfig], otherwise it will likely
+   * cause an error during the release process.
    *
    * **Project properties take priority over a [ReleaseConfig].**
    *
@@ -270,9 +285,11 @@ abstract class PublishingPlugin : Plugin {
         computeMissingLibrariesToRelease(librariesToRelease, libraryGroups)
       if (missingLibrariesToRelease.isNotEmpty()) {
         throw GradleException(
-          "Invalid release configuration. " +
-            "It's should include the following libraries due to library groups: \n" +
-            "${missingLibrariesToRelease.joinToString("\n"){ it.artifactName }}"
+          multiLine(
+            "Invalid release configuration.",
+            "It should include the following libraries due to library groups:",
+            missingLibrariesToRelease,
+          )
         )
       }
 
@@ -286,11 +303,160 @@ abstract class PublishingPlugin : Plugin {
    * Generates a BOM for a release, although it relies on gmaven to be updated- so it should be
    * invoked manually later on in the release process.
    *
-   * @see BomGeneratorTask
+   * @see GenerateBomTask
    */
   private fun registerGenerateBomTask(project: Project) =
-    project.tasks.register(GENERATE_BOM_TASK) {
-      bomDirectory.convention(project.layout.projectDirectory.dir(BOM_DIR_NAME))
+    project.tasks.register("generateBom") {
+      bomArtifacts.set(BOM_ARTIFACTS)
+      ignoredArtifacts.set(
+        listOf(
+          "com.google.firebase:crash-plugin",
+          "com.google.firebase:firebase-ml-vision",
+          "com.google.firebase:firebase-ads",
+          "com.google.firebase:firebase-ads-lite",
+          "com.google.firebase:firebase-abt",
+          "com.google.firebase:firebase-analytics-impl",
+          "com.google.firebase:firebase-analytics-impl-license",
+          "com.google.firebase:firebase-analytics-license",
+          "com.google.firebase:firebase-annotations",
+          "com.google.firebase:firebase-appcheck-interop",
+          "com.google.firebase:firebase-appcheck-safetynet",
+          "com.google.firebase:firebase-appdistribution-gradle",
+          "com.google.firebase:firebase-appindexing-license",
+          "com.google.firebase:firebase-appindexing",
+          "com.google.firebase:firebase-iid",
+          "com.google.firebase:firebase-core",
+          "com.google.firebase:firebase-auth-common",
+          "com.google.firebase:firebase-auth-impl",
+          "com.google.firebase:firebase-auth-interop",
+          "com.google.firebase:firebase-auth-license",
+          "com.google.firebase:firebase-encoders-json",
+          "com.google.firebase:firebase-encoders-proto",
+          "com.google.firebase:firebase-auth-module",
+          "com.google.firebase:firebase-bom",
+          "com.google.firebase:firebase-common-license",
+          "com.google.firebase:firebase-components",
+          "com.google.firebase:firebase-config-license",
+          "com.google.firebase:firebase-config-interop",
+          "com.google.firebase:firebase-crash",
+          "com.google.firebase:firebase-crash-license",
+          "com.google.firebase:firebase-crashlytics-buildtools",
+          "com.google.firebase:firebase-crashlytics-gradle",
+          "com.google.firebase:firebase-database-collection",
+          "com.google.firebase:firebase-database-connection",
+          "com.google.firebase:firebase-database-connection-license",
+          "com.google.firebase:firebase-database-license",
+          "com.google.firebase:firebase-dataconnect",
+          "com.google.firebase:firebase-datatransport",
+          "com.google.firebase:firebase-appdistribution-ktx",
+          "com.google.firebase:firebase-appdistribution",
+          "com.google.firebase:firebase-appdistribution-api",
+          "com.google.firebase:firebase-appdistribution-api-ktx",
+          "com.google.firebase:firebase-dynamic-module-support",
+          "com.google.firebase:firebase-dynamic-links-license",
+          "com.google.firebase:firebase-functions-license",
+          "com.google.firebase:firebase-iid-interop",
+          "com.google.firebase:firebase-iid-license",
+          "com.google.firebase:firebase-invites",
+          "com.google.firebase:firebase-measurement-connector",
+          "com.google.firebase:firebase-measurement-connector-impl",
+          "com.google.firebase:firebase-messaging-license",
+          "com.google.firebase:firebase-ml-common",
+          "com.google.firebase:firebase-ml-vision-internal-vkp",
+          "com.google.firebase:firebase-ml-model-interpreter",
+          "com.google.firebase:firebase-perf-license",
+          "com.google.firebase:firebase-plugins",
+          "com.google.firebase:firebase-sessions",
+          "com.google.firebase:firebase-storage-common",
+          "com.google.firebase:firebase-storage-common-license",
+          "com.google.firebase:firebase-storage-license",
+          "com.google.firebase:perf-plugin",
+          "com.google.firebase:protolite-well-known-types",
+          "com.google.firebase:testlab-instr-lib",
+          "com.google.firebase:firebase-installations-interop",
+          "com.google.firebase:firebase-ml-vision-automl",
+          "com.google.firebase:firebase-ml-vision-barcode-model",
+          "com.google.firebase:firebase-ml-vision-face-model",
+          "com.google.firebase:firebase-ml-vision-image-label-model",
+          "com.google.firebase:firebase-ml-vision-object-detection-model",
+          "com.google.firebase:firebase-ml-natural-language",
+          "com.google.firebase:firebase-ml-natural-language-language-id-model",
+          "com.google.firebase:firebase-ml-natural-language-smart-reply",
+          "com.google.firebase:firebase-ml-natural-language-smart-reply-model",
+          "com.google.firebase:firebase-ml-natural-language-translate",
+          "com.google.firebase:firebase-ml-natural-language-translate-model",
+          "com.crashlytics.sdk.android:crashlytics:crashlytics",
+          "com.google.android.gms:play-services-ads",
+          "com.google.gms:google-services",
+          "com.android.tools.build:gradle",
+        )
+      )
+
+      outputDirectory.set(project.layout.buildDirectory.dir(BOM_DIR_NAME))
+    }
+
+  /**
+   * Registers the [GENERATE_TUTORIAL_BUNDLE_TASK] task.
+   *
+   * Generates a tutorial bundle for a release.
+   *
+   * @see GenerateTutorialBundleTask
+   */
+  private fun registerGenerateTutorialBundleTask(project: Project) =
+    project.tasks.register(GENERATE_TUTORIAL_BUNDLE_TASK) {
+      commonArtifacts.set(listOf("com.google.gms:google-services"))
+      gradlePlugins.set(
+        listOf(
+          "com.google.firebase:firebase-appdistribution-gradle",
+          "com.google.firebase:firebase-crashlytics-gradle",
+        )
+      )
+      perfArtifacts.set(listOf("com.google.firebase:perf-plugin"))
+      requiredArtifacts.set(
+        listOf(
+          "com.google.gms:google-services",
+          "com.google.firebase:firebase-analytics",
+          "com.google.firebase:firebase-crashlytics",
+          "com.google.firebase:firebase-perf",
+          "com.google.firebase:firebase-vertexai",
+          "com.google.firebase:firebase-messaging",
+          "com.google.firebase:firebase-auth",
+          "com.google.firebase:firebase-database",
+          "com.google.firebase:firebase-storage",
+          "com.google.firebase:firebase-config",
+          "com.google.android.gms:play-services-ads",
+          "com.google.firebase:firebase-firestore",
+          "com.google.firebase:firebase-functions",
+          "com.google.firebase:firebase-inappmessaging-display",
+          "com.google.firebase:firebase-ml-vision",
+          "com.google.firebase:firebase-appdistribution-gradle",
+          "com.google.firebase:firebase-crashlytics-gradle",
+          "com.google.firebase:perf-plugin",
+        )
+      )
+      firebaseArtifacts.set(BOM_ARTIFACTS + EXTRA_TUTORIAL_ARTIFACTS)
+
+      tutorialFile.set(project.layout.buildDirectory.file("recipeVersionUpdate.txt"))
+    }
+
+  /**
+   * Registers the [GENERATE_BOM_RELEASE_NOTES_TASK] task.
+   *
+   * Generates the release notes for a bom during a release.
+   *
+   * @see GenerateBomReleaseNotesTask
+   */
+  private fun registerGenerateBomReleaseNotesTask(
+    project: Project,
+    generateBomTask: TaskProvider,
+  ) =
+    project.tasks.register(GENERATE_BOM_RELEASE_NOTES_TASK) {
+      currentBom.set(project.layout.file(generateBomTask.flatMap { it.outputDirectory.nestedFile }))
+      previousBom.set(
+        project.gmavenService.map { it.latestPom("com.google.firebase", "firebase-bom") }
+      )
+
+      releaseNotesFile.set(project.layout.buildDirectory.file("bomReleaseNotes.md"))
     }
 
   /**
@@ -377,9 +543,11 @@ abstract class PublishingPlugin : Plugin {
           computeMissingLibrariesToRelease(librariesToRelease, libraryGroups)
         if (missingLibrariesToRelease.isNotEmpty()) {
           throw GradleException(
-            "Invalid release configuration. " +
-              "It's should include the following libraries due to library groups: \n" +
-              "${missingLibrariesToRelease.joinToString("\n")}"
+            multiLine(
+              "Invalid release configuration.",
+              "It should include the following libraries due to library groups:",
+              missingLibrariesToRelease,
+            )
           )
         }
       }
@@ -580,6 +748,8 @@ abstract class PublishingPlugin : Plugin {
     const val BOM_DIR_NAME = "bom"
 
     const val GENERATE_BOM_TASK = "generateBom"
+    const val GENERATE_BOM_RELEASE_NOTES_TASK = "generateBomReleaseNotes"
+    const val GENERATE_TUTORIAL_BUNDLE_TASK = "generateTutorialBundle"
     const val VALIDATE_PROJECTS_TO_PUBLISH_TASK = "validateProjectsToPublish"
     const val VALIDATE_LIBRARY_GROUPS_TO_PUBLISH_TASK = "validateLibraryGroupsToPublish"
     const val SEMVER_CHECK_TASK = "semverCheckForRelease"
@@ -596,10 +766,61 @@ abstract class PublishingPlugin : Plugin {
     const val BUILD_KOTLINDOC_ZIP_TASK = "buildKotlindocZip"
     const val BUILD_RELEASE_NOTES_ZIP_TASK = "buildReleaseNotesZip"
     const val BUILD_BOM_ZIP_TASK = "buildBomZip"
+    const val BUILD_BOM_BUNDLE_ZIP_TASK = "buildBomBundleZip"
     const val FIREBASE_PUBLISH_TASK = "firebasePublish"
     const val PUBLISH_ALL_TO_BUILD_TASK = "publishAllToBuildDir"
 
     const val BUILD_DIR_REPOSITORY_DIR = "m2repository"
+
+    /** Artifacts included in the bom. */
+    val BOM_ARTIFACTS =
+      listOf(
+        "com.google.firebase:firebase-analytics",
+        "com.google.firebase:firebase-analytics-ktx",
+        "com.google.firebase:firebase-appcheck-debug",
+        "com.google.firebase:firebase-appcheck-debug-testing",
+        "com.google.firebase:firebase-appcheck-ktx",
+        "com.google.firebase:firebase-appcheck-playintegrity",
+        "com.google.firebase:firebase-appcheck",
+        "com.google.firebase:firebase-auth",
+        "com.google.firebase:firebase-auth-ktx",
+        "com.google.firebase:firebase-common",
+        "com.google.firebase:firebase-common-ktx",
+        "com.google.firebase:firebase-config",
+        "com.google.firebase:firebase-config-ktx",
+        "com.google.firebase:firebase-crashlytics",
+        "com.google.firebase:firebase-crashlytics-ktx",
+        "com.google.firebase:firebase-crashlytics-ndk",
+        "com.google.firebase:firebase-database",
+        "com.google.firebase:firebase-database-ktx",
+        "com.google.firebase:firebase-dynamic-links",
+        "com.google.firebase:firebase-dynamic-links-ktx",
+        "com.google.firebase:firebase-encoders",
+        "com.google.firebase:firebase-firestore",
+        "com.google.firebase:firebase-firestore-ktx",
+        "com.google.firebase:firebase-functions",
+        "com.google.firebase:firebase-functions-ktx",
+        "com.google.firebase:firebase-inappmessaging",
+        "com.google.firebase:firebase-inappmessaging-display",
+        "com.google.firebase:firebase-inappmessaging-display-ktx",
+        "com.google.firebase:firebase-inappmessaging-ktx",
+        "com.google.firebase:firebase-installations",
+        "com.google.firebase:firebase-installations-ktx",
+        "com.google.firebase:firebase-messaging",
+        "com.google.firebase:firebase-messaging-directboot",
+        "com.google.firebase:firebase-messaging-ktx",
+        "com.google.firebase:firebase-ml-modeldownloader",
+        "com.google.firebase:firebase-ml-modeldownloader-ktx",
+        "com.google.firebase:firebase-perf",
+        "com.google.firebase:firebase-perf-ktx",
+        "com.google.firebase:firebase-storage",
+        "com.google.firebase:firebase-storage-ktx",
+        "com.google.firebase:firebase-vertexai",
+      )
+
+    /** Artifacts that we use in the tutorial bundle, but _not_ in the bom. */
+    val EXTRA_TUTORIAL_ARTIFACTS =
+      listOf("com.google.android.gms:play-services-ads", "com.google.firebase:firebase-ml-vision")
   }
 }
 
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseNotesConfigurationExtension.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/ReleaseNotesConfigurationExtension.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseNotesConfigurationExtension.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ReleaseNotesConfigurationExtension.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/SdkUtil.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/SdkUtil.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/SdkUtil.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/SdkUtil.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/Tasks.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/Tasks.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/Tasks.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/Tasks.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/UpdatePinnedDependenciesTask.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/UpdatePinnedDependenciesTask.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/UpdatePinnedDependenciesTask.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/UpdatePinnedDependenciesTask.kt
diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt
new file mode 100644
index 00000000000..dfd22e2c50b
--- /dev/null
+++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2020 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
+
+import com.android.build.api.artifact.ScopedArtifact
+import com.android.build.api.variant.LibraryAndroidComponentsExtension
+import com.android.build.api.variant.ScopedArtifacts
+import com.android.build.gradle.LibraryPlugin
+import com.google.firebase.gradle.plugins.license.LicenseResolverPlugin
+import java.io.File
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.file.ArchiveOperations
+import org.gradle.api.file.Directory
+import org.gradle.api.file.FileSystemOperations
+import org.gradle.api.file.ProjectLayout
+import org.gradle.api.file.RegularFile
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Classpath
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.kotlin.dsl.apply
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.kotlin.dsl.register
+import org.gradle.kotlin.dsl.withType
+import org.gradle.process.ExecOperations
+import org.jetbrains.kotlin.gradle.utils.extendsFrom
+
+/**
+ * Gradle plugin for vendoring dependencies in an android library.
+ *
+ * We vendor dependencies by moving the dependency into the published package, and renaming all
+ * imports to reference the vendored package.
+ *
+ * Registers the `vendor` configuration to be used for specifying vendored dependencies.
+ *
+ * Note that you should exclude any `java` or `javax` transitive dependencies, as `jarjar` (what we
+ * use to do the actual vendoring) unconditionally skips them.
+ *
+ * ```
+ * vendor("com.google.dagger:dagger:2.27") {
+ *   exclude(group = "javax.inject", module = "javax.inject")
+ * }
+ * ```
+ *
+ * @see VendorTask
+ */
+class VendorPlugin : Plugin {
+  override fun apply(project: Project) {
+    project.apply()
+    project.plugins.withType().configureEach { configureAndroid(project) }
+  }
+
+  private fun configureAndroid(project: Project) {
+    val vendor = project.configurations.register("vendor")
+    val configurations =
+      listOf(
+        "releaseCompileOnly",
+        "debugImplementation",
+        "testImplementation",
+        "androidTestImplementation",
+      )
+
+    for (configuration in configurations) {
+      project.configurations.named(configuration).extendsFrom(vendor)
+    }
+
+    val jarJarArtifact =
+      project.configurations.register("firebaseJarJarArtifact") {
+        dependencies.add(project.dependencies.create("org.pantsbuild:jarjar:1.7.2"))
+      }
+
+    val androidComponents = project.extensions.getByType()
+
+    androidComponents.onReleaseVariants {
+      val vendorTask =
+        project.tasks.register("${it.name}VendorTransform") {
+          vendorDependencies.set(vendor)
+          packageName.set(it.namespace)
+          jarJar.set(jarJarArtifact)
+        }
+
+      it.artifacts
+        .forScope(ScopedArtifacts.Scope.PROJECT)
+        .use(vendorTask)
+        .toTransform(
+          ScopedArtifact.CLASSES,
+          VendorTask::inputJars,
+          VendorTask::inputDirs,
+          VendorTask::outputJar,
+        )
+    }
+  }
+}
+
+/**
+ * Executes the actual vendoring of a library.
+ *
+ * @see VendorPlugin
+ */
+abstract class VendorTask
+@Inject
+constructor(
+  private val exec: ExecOperations,
+  private val archive: ArchiveOperations,
+  private val fs: FileSystemOperations,
+  private val layout: ProjectLayout,
+) : DefaultTask() {
+  /** Dependencies that should be vendored. */
+  @get:[InputFiles Classpath]
+  abstract val vendorDependencies: Property
+
+  /** Configuration pointing to the `.jar` file for JarJar. */
+  @get:[InputFiles Classpath]
+  abstract val jarJar: Property
+
+  /**
+   * The name of the package (or namespace) that we're vendoring for.
+   *
+   * We use this to rename the [vendorDependencies].
+   */
+  @get:Input abstract val packageName: Property
+
+  /** The jars generated for this package during a release. */
+  @get:InputFiles abstract val inputJars: ListProperty
+
+  /** The directories generated for this package during a release. */
+  @get:InputFiles abstract val inputDirs: ListProperty
+
+  /** The jar file to save the vendored artifact. */
+  @get:OutputFile abstract val outputJar: RegularFileProperty
+
+  @TaskAction
+  fun taskAction() {
+    val unzippedDir = temporaryDir.childFile("unzipped")
+
+    logger.info("Unpacking input directories")
+    fs.sync {
+      from(inputDirs)
+      into(unzippedDir)
+    }
+
+    logger.info("Unpacking input jars")
+    fs.copy {
+      for (jar in inputJars.get()) {
+        from(archive.zipTree(jar))
+      }
+      into(unzippedDir)
+      exclude { it.path.contains("META-INF") }
+    }
+
+    val ownPackageNames = inferPackageNames(unzippedDir)
+
+    logger.info("Unpacking vendored files")
+    fs.copy {
+      for (jar in vendorDependencies.get()) {
+        from(archive.zipTree(jar))
+      }
+      into(unzippedDir)
+      exclude { it.path.contains("META-INF") }
+    }
+
+    val externalPackageNames = inferPackageNames(unzippedDir) subtract ownPackageNames
+    val java = unzippedDir.childFile("java")
+    val javax = unzippedDir.childFile("javax")
+    if (java.exists() || javax.exists()) {
+      // JarJar unconditionally skips any classes whose package name starts with "java" or "javax".
+      val dependencies = vendorDependencies.get().resolvedConfiguration.resolvedArtifacts
+      throw GradleException(
+        """
+          |Vendoring java or javax packages is not supported.
+          |Please exclude one of the direct or transitive dependencies:
+          |${dependencies.joinToString("\n")}
+        """
+          .trimMargin()
+      )
+    }
+
+    val inputJar = temporaryDir.childFile("intermediate.jar")
+    unzippedDir.zipFilesTo(this, inputJar)
+
+    transform(inputJar, ownPackageNames, externalPackageNames)
+  }
+
+  private fun transform(inputJar: File, ownPackages: Set, packagesToVendor: Set) {
+    val parentPackage = packageName.get()
+    val rulesFile = temporaryDir.childFile("$parentPackage.jarjar")
+
+    rulesFile.printWriter().use {
+      for (packageName in ownPackages) {
+        it.println("keep $packageName.**")
+      }
+      for (externalPackageName in packagesToVendor) {
+        it.println("rule $externalPackageName.** $parentPackage.@0")
+      }
+    }
+
+    logger.info("The following JarJar configuration will be used:\n ${rulesFile.readText()}")
+
+    exec
+      .javaexec {
+        mainClass.set("org.pantsbuild.jarjar.Main")
+        classpath = layout.files(jarJar)
+        args =
+          listOf(
+            "process",
+            rulesFile.absolutePath,
+            inputJar.absolutePath,
+            outputJar.asFile.get().absolutePath,
+          )
+        systemProperties = mapOf("verbose" to "true", "misplacedClassStrategy" to "FATAL")
+      }
+      .assertNormalExitValue()
+  }
+
+  /** Given a directory of class files, constructs a list of all the class files. */
+  private fun inferPackageNames(dir: File): Set {
+    return dir
+      .walk()
+      .filter { it.name.endsWith(".class") }
+      .map { it.parentFile.toRelativeString(dir).replace(File.separator, ".") }
+      .toSet()
+  }
+}
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/VersionBumpTask.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/VersionBumpTask.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/VersionBumpTask.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/VersionBumpTask.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/AffectedProjectFinder.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/ChangedModulesTask.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/ChangedModulesTask.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/ChangedModulesTask.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/ChangedModulesTask.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationExtension.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationExtension.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationExtension.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationExtension.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationPlugin.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationPlugin.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationPlugin.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/ContinuousIntegrationPlugin.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/Coverage.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/Coverage.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/Coverage.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/Coverage.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/Environment.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/Environment.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/Environment.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/Environment.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestLabExtension.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestLabExtension.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestLabExtension.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestLabExtension.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestLabPlugin.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestLabPlugin.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestLabPlugin.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestLabPlugin.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java
similarity index 98%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java
index 9445f9ba952..a13eb035e91 100644
--- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java
+++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/ci/device/FirebaseTestServer.java
@@ -63,7 +63,7 @@ public void uploadApks(String variantName, File testApk, File testedApk) {
     String testedApkPath =
         testedApk != null
             ? testedApk.toString()
-            : project.getRootDir() + "/buildSrc/resources/dummy.apk";
+            : project.getRootDir() + "/plugins/resources/dummy.apk";
     project
         .getLogger()
         .lifecycle("Uploading for {}: testApk={}, testedApk={}", variantName, testApk, testedApk);
diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/datamodels/PomElement.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/datamodels/PomElement.kt
new file mode 100644
index 00000000000..dda8db2896d
--- /dev/null
+++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/datamodels/PomElement.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2025 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.datamodels
+
+import com.google.firebase.gradle.plugins.ModuleVersion
+import java.io.File
+import javax.xml.parsers.DocumentBuilderFactory
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+import nl.adaptivity.xmlutil.XmlDeclMode
+import nl.adaptivity.xmlutil.newReader
+import nl.adaptivity.xmlutil.serialization.XML
+import nl.adaptivity.xmlutil.serialization.XmlChildrenName
+import nl.adaptivity.xmlutil.serialization.XmlElement
+import nl.adaptivity.xmlutil.serialization.XmlSerialName
+import nl.adaptivity.xmlutil.xmlStreaming
+import org.w3c.dom.Element
+
+/**
+ * Representation of a `` element in a a pom file.
+ *
+ * @see PomElement
+ */
+@Serializable
+@XmlSerialName("license")
+data class LicenseElement(
+  @XmlElement val name: String,
+  @XmlElement val url: String? = null,
+  @XmlElement val distribution: String? = null,
+) : java.io.Serializable
+
+/**
+ * Representation of an `` element in a a pom file.
+ *
+ * @see PomElement
+ */
+@Serializable
+@XmlSerialName("scm")
+data class SourceControlManagement(
+  @XmlElement val connection: String,
+  @XmlElement val url: String,
+) : java.io.Serializable
+
+/**
+ * Representation of a `` element in a pom file.
+ *
+ * @see PomElement
+ */
+@Serializable
+@XmlSerialName("dependency")
+data class ArtifactDependency(
+  @XmlElement val groupId: String,
+  @XmlElement val artifactId: String,
+  // Can be null if the artifact derives its version from a bom
+  @XmlElement val version: String? = null,
+  @XmlElement val type: String? = null,
+  @XmlElement val scope: String? = null,
+) : java.io.Serializable {
+  /**
+   * Returns the artifact dependency as a a gradle dependency string.
+   *
+   * ```
+   * implementation("com.google.firebase:firebase-firestore:1.0.0")
+   * ```
+   *
+   * @see configuration
+   * @see simpleDepString
+   */
+  override fun toString() = "$configuration(\"$simpleDepString\")"
+}
+
+/**
+ * The artifact type of this dependency, or the default inferred by gradle.
+ *
+ * We use a separate variable instead of inferring the default in the constructor so we can
+ * serialize instances of [ArtifactDependency] that should specifically _not_ have a type in the
+ * output (like in [DependencyManagementElement] instances).
+ */
+val ArtifactDependency.typeOrDefault: String
+  get() = type ?: "jar"
+
+/**
+ * The artifact scope of this dependency, or the default inferred by gradle.
+ *
+ * We use a separate variable instead of inferring the default in the constructor so we can
+ * serialize instances of [ArtifactDependency] that should specifically _not_ have a scope in the
+ * output (like in [DependencyManagementElement] instances).
+ */
+val ArtifactDependency.scopeOrDefault: String
+  get() = scope ?: "compile"
+
+/**
+ * The [version][ArtifactDependency.version] represented as a [ModuleVersion].
+ *
+ * @throws RuntimeException if the version isn't valid semver, or it's missing.
+ */
+val ArtifactDependency.moduleVersion: ModuleVersion
+  get() =
+    version?.let { ModuleVersion.fromString(artifactId, it) }
+      ?: throw RuntimeException(
+        "Missing required version property for artifact dependency: $artifactId"
+      )
+
+/**
+ * The fully qualified name of the artifact.
+ *
+ * Shorthand for:
+ * ```
+ * "${artifact.groupId}:${artifact.artifactId}"
+ * ```
+ */
+val ArtifactDependency.fullArtifactName: String
+  get() = "$groupId:$artifactId"
+
+/**
+ * A string representing the dependency as a maven artifact marker.
+ *
+ * ```
+ * "com.google.firebase:firebase-common:21.0.0"
+ * ```
+ */
+val ArtifactDependency.simpleDepString: String
+  get() = "$fullArtifactName${version?.let { ":$it" } ?: ""}"
+
+/** The gradle configuration that this dependency would apply to (eg; `api` or `implementation`). */
+val ArtifactDependency.configuration: String
+  get() = if (scopeOrDefault == "compile") "api" else "implementation"
+
+@Serializable
+@XmlSerialName("dependencyManagement")
+data class DependencyManagementElement(
+  @XmlChildrenName("dependency") val dependencies: List? = null
+) : java.io.Serializable
+
+/** Representation of a `` element within a `pom.xml` file. */
+@Serializable
+@XmlSerialName("project")
+data class PomElement(
+  @XmlSerialName("xmlns") val namespace: String? = null,
+  @XmlSerialName("xmlns:xsi") val schema: String? = null,
+  @XmlSerialName("xsi:schemaLocation") val schemaLocation: String? = null,
+  @XmlElement val modelVersion: String,
+  @XmlElement val groupId: String,
+  @XmlElement val artifactId: String,
+  @XmlElement val version: String,
+  @XmlElement val packaging: String? = null,
+  @XmlChildrenName("license") val licenses: List? = null,
+  @XmlElement val scm: SourceControlManagement? = null,
+  @XmlElement val dependencyManagement: DependencyManagementElement? = null,
+  @XmlChildrenName("dependency") val dependencies: List? = null,
+) : java.io.Serializable {
+  /**
+   * Serializes this pom element into a valid XML element and saves it to the specified [file].
+   *
+   * @param file Where to save the serialized pom to
+   * @return The provided file, for chaining purposes.
+   * @see fromFile
+   */
+  fun toFile(file: File): File {
+    file.writeText(toString())
+    return file
+  }
+
+  /**
+   * Serializes this pom element into a valid XML element.
+   *
+   * @see toFile
+   */
+  override fun toString(): String {
+    return xml.encodeToString(this)
+  }
+
+  companion object {
+    private val xml = XML {
+      indent = 2
+      xmlDeclMode = XmlDeclMode.None
+    }
+
+    /**
+     * Deserializes a [PomElement] from a `pom.xml` file.
+     *
+     * @param file The file that contains the pom element.
+     * @return The deserialized [PomElement]
+     * @see toFile
+     * @see fromElement
+     */
+    fun fromFile(file: File): PomElement =
+      fromElement(
+        DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file).documentElement
+      )
+
+    /**
+     * Deserializes a [PomElement] from a document [Element].
+     *
+     * @param element The HTML element representing the pom element.
+     * @return The deserialized [PomElement]
+     * @see fromFile
+     */
+    fun fromElement(element: Element): PomElement =
+      xml.decodeFromReader(xmlStreaming.newReader(element))
+  }
+}
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/license/GenerateLicensesTask.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/license/GenerateLicensesTask.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/license/GenerateLicensesTask.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/license/GenerateLicensesTask.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/license/LicenseResolverPlugin.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/license/LicenseResolverPlugin.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/license/LicenseResolverPlugin.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/license/LicenseResolverPlugin.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/license/ThirdPartyLicensesExtension.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/license/ThirdPartyLicensesExtension.java
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/license/ThirdPartyLicensesExtension.java
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/license/ThirdPartyLicensesExtension.java
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/AccessDescriptor.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/semver/AccessDescriptor.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/AccessDescriptor.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/semver/AccessDescriptor.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/ApiDiffer.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/semver/ApiDiffer.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/ApiDiffer.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/semver/ApiDiffer.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/ClassInfo.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/semver/ClassInfo.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/ClassInfo.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/semver/ClassInfo.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/Delta.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/semver/Delta.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/Delta.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/semver/Delta.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/DeltaType.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/semver/DeltaType.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/DeltaType.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/semver/DeltaType.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/GmavenCopier.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/semver/GmavenCopier.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/GmavenCopier.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/semver/GmavenCopier.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/UtilityClass.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/semver/UtilityClass.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/UtilityClass.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/semver/UtilityClass.kt
diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/VersionDelta.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/semver/VersionDelta.kt
similarity index 100%
rename from buildSrc/src/main/java/com/google/firebase/gradle/plugins/semver/VersionDelta.kt
rename to plugins/src/main/java/com/google/firebase/gradle/plugins/semver/VersionDelta.kt
diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/services/DocumentService.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/services/DocumentService.kt
new file mode 100644
index 00000000000..2af0e9a9e89
--- /dev/null
+++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/services/DocumentService.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2025 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.services
+
+import com.google.firebase.gradle.plugins.writeStream
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.InputStream
+import java.net.URL
+import javax.xml.parsers.DocumentBuilderFactory
+import org.w3c.dom.Document
+
+/**
+ * Wrapper around [Documents][Document].
+ *
+ * Abstracts some common download functionality when dealing with documents, and also allows the
+ * behavior to be more easily mocked and tested.
+ */
+class DocumentService {
+  /**
+   * Opens an [InputStream] at the specified [url].
+   *
+   * It's the caller's responsibility to _close_ the stream when done.
+   */
+  fun openStream(url: String): InputStream = URL(url).openStream()
+
+  /**
+   * Downloads the [Document] from the specified [url], and saves it to a [file].
+   *
+   * @return The same [file] instance when the document is downloaded, or null if the document
+   *   wasn't found.
+   */
+  fun downloadToFile(url: String, file: File): File? =
+    try {
+      openStream(url).use { file.writeStream(it) }
+    } catch (e: FileNotFoundException) {
+      null
+    }
+
+  /**
+   * Downloads the [Document] from the specified [url].
+   *
+   * @return The downloaded [Document] instance, or null if the document wasn't found.
+   */
+  fun downloadDocument(url: String): Document? =
+    try {
+      DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(openStream(url))
+    } catch (e: FileNotFoundException) {
+      null
+    }
+}
diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/services/GmavenService.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/services/GmavenService.kt
new file mode 100644
index 00000000000..b6093225f65
--- /dev/null
+++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/services/GmavenService.kt
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2025 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.services
+
+import com.google.common.annotations.VisibleForTesting
+import com.google.firebase.gradle.plugins.children
+import com.google.firebase.gradle.plugins.datamodels.ArtifactDependency
+import com.google.firebase.gradle.plugins.datamodels.PomElement
+import com.google.firebase.gradle.plugins.multiLine
+import com.google.firebase.gradle.plugins.registerIfAbsent
+import com.google.firebase.gradle.plugins.textByAttributeOrNull
+import java.io.File
+import java.nio.file.Path
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.io.path.createTempDirectory
+import kotlin.io.path.createTempFile
+import org.gradle.api.Project
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.api.provider.Provider
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+import org.w3c.dom.Node
+
+/**
+ * Gradle build service for facilitating communication with [GMaven](https://maven.google.com).
+ *
+ * ### Overview
+ *
+ * Files fetched from GMaven are saved in a local cache on a per-build basis; meaning for any given
+ * build, no matter what project or task uses this service, any identical requests will not result
+ * in multiple requests to the GMaven servers.
+ *
+ * This service is also thread-safe; meaning you can safely utilize this service in a concurrent
+ * environment (across multiple projects/tasks).
+ *
+ * ### Bypassing the cache
+ *
+ * If you need to bypass the cache for any reason, all cached values have a `force-` variant.
+ *
+ * For example, instead of calling [pomOrNull], you can call [forceGetPom].
+ *
+ * Calling these methods will fetch the latest files from GMaven, regardless of the state of the
+ * cache.
+ *
+ * *Note: These methods do **not** update the cache either.*
+ *
+ * ### Usage
+ *
+ * The build service should ideally be used from within a Task directly.
+ *
+ * To use the service, you can inject it into your task as needed.
+ *
+ * ```
+ * abstract class MyTask: DefaultTask() {
+ *   @get:ServiceReference("gmaven")
+ *   abstract val gmaven: Property
+ * }
+ * ```
+ *
+ * #### Plugin usage
+ *
+ * If you need to access the service from within a plugin, you can do so via the [gmavenService]
+ * helper property.
+ *
+ * ```
+ * val latestVersion = project.gmavenService.map {
+ *   it.latestVersion("com.google.firebase", "firebase-common")
+ * }
+ * ```
+ */
+abstract class GMavenService : BuildService {
+  private val controller = GMavenServiceController()
+
+  /**
+   * Gets the latest version of the artifact that has been uploaded to GMaven for a given group.
+   *
+   * Effectively just a short metadata list of all the published packages and their versions.
+   *
+   * ```
+   * gmaven.groupIndex("com.google.firebase", "com.google.firebase")
+   * ```
+   *
+   * @param groupId The group to search for.
+   * @throws RuntimeException If the group index doesn't exist
+   * @see forceGetGroupIndex
+   * @see GroupIndexArtifact
+   */
+  fun groupIndex(groupId: String): List = controller.groupIndex(groupId)
+
+  /**
+   * Gets the [GroupIndexArtifact] representing a given artifact from gmaven, if any.
+   *
+   * ```
+   * gmaven.groupIndexArtifactOrNull("com.google.firebase", "com.google.firebase")
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to search for.
+   * @return An [GroupIndexArtifact] representing the artifact, or null if the artifact couldn't be
+   *   found.
+   * @see groupIndex
+   */
+  fun groupIndexArtifactOrNull(groupId: String, artifactId: String): GroupIndexArtifact? =
+    groupIndex(groupId).find { it.artifactId == artifactId }
+
+  /**
+   * Gets the [GroupIndexArtifact] representing a given artifact from gmaven, if any.
+   *
+   * ```
+   * gmaven.groupIndexArtifactOrNull("com.google.firebase:com.google.firebase")
+   * ```
+   *
+   * @param fullArtifactName The artifact to search for, represented as "groupId:artifactId".
+   * @return An [GroupIndexArtifact] representing the artifact, or null if the artifact couldn't be
+   *   found.
+   * @see groupIndex
+   */
+  fun groupIndexArtifactOrNull(fullArtifactName: String): GroupIndexArtifact? {
+    val (groupId, artifactId) = fullArtifactName.split(":")
+    return groupIndexArtifactOrNull(groupId, artifactId)
+  }
+
+  /**
+   * Gets the latest version of the artifact that has been uploaded to GMaven, if any.
+   *
+   * ```
+   * gmaven.latestVersionOrNull("com.google.firebase", "firebase-components") // "18.0.1"
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to search for.
+   * @return The latest released version as a string, or null if the artifact couldn't be found.
+   * @see latestVersion
+   */
+  fun latestVersionOrNull(groupId: String, artifactId: String) =
+    controller.latestVersionOrNull(groupId, artifactId)
+
+  /**
+   * Gets the latest version of the artifact that has been uploaded to GMaven, if any.
+   *
+   * ```
+   * gmaven.latestVersionOrNull("com.google.firebase", "firebase-components") // "18.0.1"
+   * ```
+   *
+   * @param fullArtifactName The artifact to search for, represented as "groupId:artifactId".
+   * @return The latest released version as a string, or null if the artifact couldn't be found.
+   * @see latestVersion
+   */
+  fun latestVersionOrNull(fullArtifactName: String): String? {
+    val (groupId, artifactId) = fullArtifactName.split(":")
+    return latestVersionOrNull(groupId, artifactId)
+  }
+
+  /**
+   * Gets the latest version of the artifact that has been uploaded to GMaven.
+   *
+   * ```
+   * gmaven.latestVersion("com.google.firebase", "firebase-components") // "18.0.1"
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to search for.
+   * @return The latest released version as a string.
+   * @see latestVersionOrNull
+   */
+  fun latestVersion(groupId: String, artifactId: String) =
+    latestVersionOrNull(groupId, artifactId)
+      ?: throw RuntimeException(
+        "Failed to get the latest version from gmaven for \'$artifactId\'. Has it been published?"
+      )
+
+  /**
+   * Checks if an artifact has been published to GMaven.
+   *
+   * ```
+   * gmaven.hasReleasedArtifact("com.google.firebase", "firebase-common") // true
+   * gmaven.hasReleasedArtifact("com.google.firebase", "fake-artifact") // false
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to search for.
+   * @return True if the artifact has been published to GMaven
+   * @see hasReleasedVersion
+   */
+  fun hasReleasedArtifact(groupId: String, artifactId: String) =
+    controller.hasReleasedArtifact(groupId, artifactId)
+
+  /**
+   * Checks if a version of an artifact has been published to GMaven.
+   *
+   * ```
+   * gmaven.hasReleasedVersion("com.google.firebase", "firebase-common", "21.0.0") // true
+   * gmaven.hasReleasedVersion("com.google.firebase", "firebase-common", "0.0.0") // false
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to search for.
+   * @param version The version of the artifact to search for.
+   * @return True if the artifact version has been published to GMaven
+   * @see hasReleasedArtifact
+   */
+  fun hasReleasedVersion(groupId: String, artifactId: String, version: String) =
+    controller.hasReleasedVersion(groupId, artifactId, version)
+
+  /**
+   * Downloads the jar or AAR file for an artifact from GMaven, if any.
+   *
+   * Will first check for an AAR file, then a jar if it doesn't find an AAR.
+   *
+   * ```
+   * val aarFile = gmaven.artifactOrNull("com.google.firebase", "firebase-common", "21.0.0")
+   * val jarFile = gmaven.artifactOrNull("com.google.firebase", "firebase-encoders", "17.0.0")
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to download.
+   * @param version The version of the artifact to download.
+   * @return A [File] containing the contents of the AAR or jar, or null if it couldn't be found
+   * @see forceGetArtifact
+   * @see artifact
+   */
+  fun artifactOrNull(groupId: String, artifactId: String, version: String) =
+    controller.artifactOrNull(groupId, artifactId, version)
+
+  /**
+   * Downloads the jar or AAR file for an artifact from GMaven.
+   *
+   * Will first check for an AAR file, then a jar if it doesn't find an AAR.
+   *
+   * ```
+   * val aarFile = gmaven.artifact("com.google.firebase", "firebase-common", "21.0.0")
+   * val jarFile = gmaven.artifact("com.google.firebase", "firebase-encoders", "17.0.0")
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to download.
+   * @param version The version of the artifact to download.
+   * @return A [File] containing the contents of the AAR or jar
+   * @see artifactOrNull
+   * @see latestArtifact
+   */
+  fun artifact(groupId: String, artifactId: String, version: String) =
+    artifactOrNull(groupId, artifactId, version)
+      ?: throw RuntimeException(
+        "Failed to get the artifact from gmaven for '$artifactId-$version'. Has it been published?"
+      )
+
+  /**
+   * Downloads the _latest_ jar or AAR file for an artifact from GMaven.
+   *
+   * Will first check for an AAR file, then a jar if it doesn't find an AAR.
+   *
+   * ```
+   * val aarFile = gmaven.latestArtifact("com.google.firebase", "firebase-common")
+   * val jarFile = gmaven.latestArtifact("com.google.firebase", "firebase-encoders")
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to download.
+   * @return A [File] containing the contents of the AAR or jar
+   * @see artifact
+   */
+  fun latestArtifact(groupId: String, artifactId: String) =
+    artifact(groupId, artifactId, latestVersion(groupId, artifactId))
+
+  /**
+   * Downloads the POM for an artifact from GMaven, if any.
+   *
+   * ```
+   * val pom = gmaven.pomOrNull("com.google.firebase", "firebase-common", "21.0.0")
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to download the pom for.
+   * @param version The version of the pom to download.
+   * @return A [PomElement] matching the pom on GMaven, or null if it couldn't be found
+   * @see forceGetPom
+   * @see pom
+   */
+  fun pomOrNull(groupId: String, artifactId: String, version: String) =
+    controller.pomOrNull(groupId, artifactId, version)
+
+  /**
+   * Downloads the POM for an artifact from GMaven.
+   *
+   * ```
+   * val pom = gmaven.pom("com.google.firebase", "firebase-common", "21.0.0")
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to download the pom for.
+   * @param version The version of the pom to download.
+   * @return A [PomElement] matching the pom on GMaven
+   * @see pomOrNull
+   * @see latestPom
+   */
+  fun pom(groupId: String, artifactId: String, version: String) =
+    pomOrNull(groupId, artifactId, version)
+      ?: throw RuntimeException(
+        "Failed to get the pom from gmaven for '$artifactId-$version'. Has it been published?"
+      )
+
+  /**
+   * Downloads the _latest_ POM for an artifact from GMaven.
+   *
+   * ```
+   * val pom = gmaven.latestPom("com.google.firebase", "firebase-common")
+   * ```
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to download the pom for.
+   * @return A [PomElement] matching the pom on GMaven
+   * @see pom
+   */
+  fun latestPom(groupId: String, artifactId: String) =
+    pom(groupId, artifactId, latestVersion(groupId, artifactId))
+
+  /**
+   * Downloads the jar or AAR file for an artifact from GMaven, if any.
+   *
+   * In contrast to [artifactOrNull], this method doesn't check the cache, nor does it cache the
+   * return value.
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to download the pom for.
+   * @param version The version of the pom to download.
+   * @return A [PomElement] matching the pom on GMaven, or null if it couldn't be found
+   * @see artifactOrNull
+   */
+  fun forceGetArtifact(groupId: String, artifactId: String, version: String) =
+    controller.forceGetArtifact(groupId, artifactId, version)
+
+  /**
+   * Downloads the POM for an artifact from GMaven, if any.
+   *
+   * In contrast to [pomOrNull], this method doesn't check the cache, nor does it cache the return
+   * value.
+   *
+   * @param groupId The group to search under.
+   * @param artifactId The artifact to download the pom for.
+   * @param version The version of the pom to download.
+   * @return A [PomElement] matching the pom on GMaven, or null if it couldn't be found
+   * @see pomOrNull
+   */
+  fun forceGetPom(groupId: String, artifactId: String, version: String) =
+    controller.forceGetPom(groupId, artifactId, version)
+
+  /**
+   * Downloads the `group-index.xml` file from GMaven.
+   *
+   * This does _not_ update the cached [groupIndex].
+   *
+   * @param groupId The group to search under.
+   * @return A list of [GroupIndexArtifact] parsed from the `group-index.xml`
+   * @see groupIndex
+   */
+  fun forceGetGroupIndex(groupId: String) = controller.forceGetGroupIndex(groupId)
+}
+
+/**
+ * Helper property for getting the [GMavenService] instance from a plugin instance.
+ *
+ * Will register the service if it isn't found.
+ */
+val Project.gmavenService: Provider
+  get() = gradle.sharedServices.registerIfAbsent("gmaven")
+
+/**
+ * Controller for facilitating communication with GMaven, and caching the result.
+ *
+ * Ideally, you should be using [GMavenService] instead of this. This primarily exists so that we
+ * can test the service outside the scope of Gradle.
+ *
+ * @see GMavenService
+ */
+@VisibleForTesting
+class GMavenServiceController(
+  private val downloadDirectory: Path = createTempDirectory(),
+  private val gmaven: DocumentService = DocumentService(),
+  private val logger: Logger = Logging.getLogger(GMavenServiceController::class.java),
+) {
+  private val groupIndexCache = ConcurrentHashMap>()
+  private val artifactCache = ConcurrentHashMap()
+  private val pomFileCache = ConcurrentHashMap()
+
+  /** @see GMavenService.latestVersionOrNull */
+  fun latestVersionOrNull(groupId: String, artifactId: String): String? {
+    return findFirebaseArtifact(groupId, artifactId)?.latestVersion
+  }
+
+  /** @see GMavenService.hasReleasedArtifact */
+  fun hasReleasedArtifact(groupId: String, artifactId: String): Boolean {
+    return findFirebaseArtifact(groupId, artifactId) !== null
+  }
+
+  /** @see GMavenService.hasReleasedVersion */
+  fun hasReleasedVersion(groupId: String, artifactId: String, version: String): Boolean {
+    return findFirebaseArtifact(groupId, artifactId)?.versions?.contains(version) ?: false
+  }
+
+  /** @see GMavenService.artifactOrNull */
+  fun artifactOrNull(groupId: String, artifactId: String, version: String): File? {
+    val name = artifactName(artifactId, version)
+    val cacheName = artifactCacheName(groupId, name)
+    logger.debug("Getting artifact '$cacheName'")
+
+    return artifactCache.computeIfAbsent(cacheName) {
+      logger.info("Artifact missing from cache '$cacheName'")
+
+      if (hasReleasedVersion(groupId, artifactId, version)) {
+        forceGetArtifact(groupId, artifactId, version)
+      } else {
+        null
+      }
+    }
+  }
+
+  /** @see GMavenService.pomOrNull */
+  fun pomOrNull(groupId: String, artifactId: String, version: String): PomElement? {
+    val name = artifactName(artifactId, version)
+    val cacheName = artifactCacheName(groupId, name)
+    logger.debug("Getting pom '$cacheName'")
+
+    return pomFileCache.computeIfAbsent(cacheName) {
+      logger.info("Pom missing from cache: '$cacheName'")
+
+      if (hasReleasedVersion(groupId, artifactId, version)) {
+        forceGetPom(groupId, artifactId, version)
+      } else {
+        null
+      }
+    }
+  }
+
+  /** @see GMavenService.groupIndex */
+  fun groupIndex(groupId: String): List {
+    logger.debug("Getting group index '$groupId'")
+
+    return groupIndexCache.computeIfAbsent(groupId) {
+      logger.info("Group index missing from cache: '$groupId'")
+
+      forceGetGroupIndex(groupId)
+    }
+  }
+
+  /** @see GMavenService.forceGetArtifact */
+  fun forceGetArtifact(groupId: String, artifactId: String, version: String): File? {
+    logger.debug("Fetching artifact '$groupId:$artifactId-$version' from gmaven")
+
+    return forceGetAAR(groupId, artifactId, version) ?: forceGetJar(groupId, artifactId, version)
+  }
+
+  /** @see GMavenService.forceGetPom */
+  fun forceGetPom(groupId: String, artifactId: String, version: String): PomElement? {
+    val name = artifactName(artifactId, version)
+    val cacheName = artifactCacheName(groupId, name)
+    logger.info("Fetching pom '$cacheName' from gmaven")
+
+    val artifactPath = "${rootForGroupId(groupId)}/$artifactId/$version/$name.pom"
+    val document = gmaven.downloadDocument(artifactPath)
+
+    if (document === null) {
+      logger.info("Pom not present in gmaven: '$cacheName'")
+    }
+
+    return document?.let { PomElement.fromElement(it.documentElement) }
+  }
+
+  /** @see GMavenService.forceGetGroupIndex */
+  fun forceGetGroupIndex(groupId: String): List {
+    logger.info("Fetching group index from gmaven for group: $groupId")
+
+    val document =
+      gmaven.downloadDocument("${rootForGroupId(groupId)}/group-index.xml")
+        ?: throw RuntimeException("Failed to find the group index file. Is GMaven offline?")
+
+    val groups =
+      document.childNodes
+        .children()
+        .flatMap { group ->
+          group.childNodes.children().map { GroupIndexArtifact.fromNode(group.nodeName, it) }
+        }
+        .groupBy { it.groupId }
+
+    return groups[groupId] ?: emptyList()
+  }
+
+  private fun forceGetJar(groupId: String, artifactId: String, version: String): File? {
+    val name = artifactName(artifactId, version)
+    val cacheName = artifactCacheName(groupId, name)
+    logger.debug("Fetching jar '$cacheName' from gmaven")
+
+    return gmaven
+      .downloadToFile(
+        "${rootForGroupId(groupId)}/$artifactId/$version/$name.jar",
+        createTempFile(downloadDirectory, name, ".jar").toFile(),
+      )
+      .also { it ?: logger.info("jar not present in gmaven '$cacheName'") }
+  }
+
+  private fun forceGetAAR(groupId: String, artifactId: String, version: String): File? {
+    val name = artifactName(artifactId, version)
+    val cacheName = artifactCacheName(groupId, name)
+    logger.debug("Fetching AAR '$cacheName' from gmaven")
+
+    return gmaven
+      .downloadToFile(
+        "${rootForGroupId(groupId)}/$artifactId/$version/$name.aar",
+        createTempFile(downloadDirectory, name, ".aar").toFile(),
+      )
+      .also { it ?: logger.info("AAR not present in gmaven '$cacheName'") }
+  }
+
+  /** Searches the cached group index for the given artifact. */
+  private fun findFirebaseArtifact(groupId: String, artifactId: String): GroupIndexArtifact? {
+    return groupIndex(groupId)
+      .find { it.artifactId == artifactId }
+      .also { it ?: logger.info("Artifact not found in the group index: '$groupId:$artifactId'") }
+  }
+
+  private fun artifactName(artifactId: String, version: String) = "$artifactId-$version"
+
+  private fun artifactCacheName(groupId: String, artifactName: String) = "$groupId:$artifactName"
+
+  private fun rootForGroupId(groupId: String) =
+    "$GMAVEN_ROOT/${groupId.split(".").joinToString("/")}"
+
+  companion object {
+    private const val GMAVEN_ROOT = "https://dl.google.com/dl/android/maven2/"
+  }
+}
+
+/**
+ * Representation of an artifact entry in a `group-index.xml` file.
+ *
+ * @see GMavenService.groupIndex
+ */
+data class GroupIndexArtifact(
+  val groupId: String,
+  val artifactId: String,
+  val versions: List,
+  val latestVersion: String = versions.last(),
+) {
+
+  /**
+   * Converts this artifact into a [ArtifactDependency], using the [latestVersion] as the
+   * corresponding version.
+   */
+  fun toArtifactDependency() =
+    ArtifactDependency(groupId = groupId, artifactId = artifactId, version = latestVersion)
+
+  /**
+   * Returns this artifact as a fully qualified dependency string.
+   *
+   * ```
+   * "com.google.firebase:firebase-firestore:1.0.0"
+   * ```
+   */
+  override fun toString(): String {
+    return "$groupId:$artifactId:$latestVersion"
+  }
+
+  companion object {
+    /**
+     * Create a [GroupIndexArtifact] from an html [Node].
+     *
+     * @param groupId The group that this artifact belongs to.
+     * @param node The HTML node that contains the data for this artifact.
+     * @return An instance of [GroupIndexArtifact] representing the provided [node].
+     * @throws RuntimeException If the node couldn't be parsed for whatever reason.
+     */
+    fun fromNode(groupId: String, node: Node): GroupIndexArtifact {
+      val versions =
+        node.textByAttributeOrNull("versions")?.split(",")
+          ?: throw RuntimeException(
+            "GroupIndex node is missing a versions attribute: ${node.nodeName}"
+          )
+
+      if (versions.isEmpty())
+        throw RuntimeException(
+          multiLine(
+            "GroupIndex node has a versions attribute without any content: ${node.nodeName}",
+            "This shouldn't happen. If this is happening, and is expected behavior, then this check should be removed.",
+          )
+        )
+
+      return GroupIndexArtifact(groupId, node.nodeName, versions)
+    }
+  }
+}
diff --git a/buildSrc/src/test/fixtures/license.txt b/plugins/src/test/fixtures/license.txt
similarity index 100%
rename from buildSrc/src/test/fixtures/license.txt
rename to plugins/src/test/fixtures/license.txt
diff --git a/plugins/src/test/kotlin/com/google/firebase/gradle/TestUtil.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/TestUtil.kt
new file mode 100644
index 00000000000..9988e05a373
--- /dev/null
+++ b/plugins/src/test/kotlin/com/google/firebase/gradle/TestUtil.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2025 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
+
+import io.kotest.assertions.throwables.shouldThrowAny
+import io.kotest.matchers.equals.shouldBeEqual
+import io.kotest.matchers.string.shouldContain
+import io.mockk.MockKMatcherScope
+import java.io.File
+import kotlin.test.assertEquals
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+
+/**
+ * Create a [GradleRunner] and run it.
+ *
+ * @param directory The project directory to run gradle from
+ * @param arguments Task arguments to pass to gradle
+ * @see createGradleRunner
+ */
+fun runGradle(directory: File, vararg arguments: String): BuildResult =
+  createGradleRunner(directory, *arguments).build()
+
+/**
+ * Creates a [GradleRunner], with preconfigured values for tests.
+ *
+ * @param directory The project directory to run gradle from
+ * @param arguments Task arguments to pass to gradle
+ * @see runGradle
+ */
+fun createGradleRunner(directory: File, vararg arguments: String): GradleRunner =
+  GradleRunner.create()
+    .withProjectDir(directory)
+    .withPluginClasspath()
+    .forwardOutput()
+    .withArguments(
+      *arguments,
+      "--stacktrace",
+      "-Dorg.gradle.kotlin.dsl.scriptCompilationAvoidance=false",
+    )
+
+/** Match arguments that end with the specified [str]. */
+fun MockKMatcherScope.endsWith(str: String) = match { it.endsWith(str) }
+
+/**
+ * Asserts that an exception is thrown with a message that contains the provided [substrings].
+ *
+ * ```
+ * shouldThrowSubstring("fetching", "firebase-common") {
+ *   throw RuntimeException("We ran into a problem while fetching the firebase-common artifact")
+ * }
+ * ```
+ *
+ * @param substrings A variable amount of strings that the exception message should contain.
+ * @param block The callback that should throw the exception.
+ */
+inline fun shouldThrowSubstring(vararg substrings: String, block: () -> Unit) {
+  val exception = shouldThrowAny { block() }
+
+  for (str in substrings) {
+    exception.message.shouldContain(str)
+  }
+}
+
+/**
+ * Asserts that this string is equal to the [expected] string.
+ *
+ * Should be used in place of [shouldBeEqual] when working with multi-line strings, as this method
+ * will provide a proper diff in the console _and_ IDE of which parts of the string were different.
+ *
+ * Works around [kotest/issues/1084](https://github.com/kotest/kotest/issues/1084).
+ *
+ * @param expected The string that this string should have the same contents of.
+ */
+infix fun String.shouldBeText(expected: String) = assertEquals(expected, this)
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/bomgenerator/tagging/GitClientTest.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/bomgenerator/tagging/GitClientTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/bomgenerator/tagging/GitClientTest.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/bomgenerator/tagging/GitClientTest.kt
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/FirebaseTestController.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/FirebaseTestController.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/FirebaseTestController.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/FirebaseTestController.kt
diff --git a/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateBomReleaseNotesTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateBomReleaseNotesTests.kt
new file mode 100644
index 00000000000..182d7d0ffb2
--- /dev/null
+++ b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateBomReleaseNotesTests.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2025 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
+
+import com.google.firebase.gradle.bomgenerator.GenerateBomReleaseNotesTask
+import com.google.firebase.gradle.plugins.datamodels.ArtifactDependency
+import com.google.firebase.gradle.plugins.datamodels.DependencyManagementElement
+import com.google.firebase.gradle.plugins.datamodels.PomElement
+import com.google.firebase.gradle.shouldBeText
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.file.shouldExist
+import java.io.File
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.register
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class GenerateBomReleaseNotesTests : FunSpec() {
+  @Rule @JvmField val testProjectDir = TemporaryFolder()
+
+  @Test
+  fun `generates the release notes`() {
+    val dependencies =
+      listOf(
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-auth",
+          version = "10.0.0",
+        ),
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-firestore",
+          version = "10.0.0",
+        ),
+      )
+    val bom = makeBom("1.0.0", dependencies)
+    val file = makeReleaseNotes(bom, bom)
+
+    file.readText().trim() shouldBeText
+      """
+            ### {{firebase_bom_long}} ({{bill_of_materials}}) version 1.0.0 {: #bom_v1-0-0}
+            {% comment %}
+            These library versions must be flat-typed, do not use variables.
+            The release note for this BoM version is a library-version snapshot.
+            {% endcomment %}
+           
+            
+        """
+        .trimIndent()
+  }
+
+  @Test
+  fun `sorts the entries alphabetically`() {
+    val dependencies =
+      listOf(
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-firestore",
+          version = "10.0.0",
+        ),
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-auth",
+          version = "10.0.0",
+        ),
+      )
+    val bom = makeBom("1.0.0", dependencies)
+    val file = makeReleaseNotes(bom, bom)
+
+    file.readText().trim() shouldBeText
+      """
+            ### {{firebase_bom_long}} ({{bill_of_materials}}) version 1.0.0 {: #bom_v1-0-0}
+            {% comment %}
+            These library versions must be flat-typed, do not use variables.
+            The release note for this BoM version is a library-version snapshot.
+            {% endcomment %}
+           
+            
+        """
+        .trimIndent()
+  }
+
+  @Test
+  fun `correctly formats changed dependencies`() {
+    val oldDependencies =
+      listOf(
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-auth",
+          version = "10.0.0",
+        ),
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-analytics",
+          version = "10.0.0",
+        ),
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-vertexai",
+          version = "10.0.0",
+        ),
+      )
+    val newDependencies =
+      listOf(
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-auth",
+          version = "10.0.0",
+        ),
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-firestore",
+          version = "10.0.0",
+        ),
+        ArtifactDependency(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-vertexai",
+          version = "11.0.0",
+        ),
+      )
+    val oldBom = makeBom("1.0.0", oldDependencies)
+    val newBom = makeBom("2.0.0", newDependencies)
+    val file = makeReleaseNotes(oldBom, newBom)
+
+    file.readText().trim() shouldBeText
+      """
+          ### {{firebase_bom_long}} ({{bill_of_materials}}) version 2.0.0 {: #bom_v2-0-0}
+          {% comment %}
+          These library versions must be flat-typed, do not use variables.
+          The release note for this BoM version is a library-version snapshot.
+          {% endcomment %}
+
+          
+      """
+        .trimIndent()
+  }
+
+  private fun makeTask(
+    configure: GenerateBomReleaseNotesTask.() -> Unit
+  ): TaskProvider {
+    val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build()
+
+    return project.tasks.register("generateBomReleaseNotes") {
+      releaseNotesFile.set(project.layout.buildDirectory.file("bomReleaseNotes.md"))
+
+      configure()
+    }
+  }
+
+  private fun makeReleaseNotes(previousBom: PomElement, currentBom: PomElement): File {
+    val currentBomFile = testProjectDir.newFile("current.bom")
+    currentBom.toFile(currentBomFile)
+
+    val task = makeTask {
+      this.currentBom.set(currentBomFile)
+      this.previousBom.set(previousBom)
+    }
+
+    val file =
+      task.get().let {
+        it.generate()
+        it.releaseNotesFile.asFile.get()
+      }
+
+    file.shouldExist()
+
+    return file
+  }
+
+  private fun makeBom(version: String, dependencies: List): PomElement {
+    return PomElement.fromFile(emptyBom)
+      .copy(version = version, dependencyManagement = DependencyManagementElement(dependencies))
+  }
+
+  companion object {
+    private val resourcesDirectory = File("src/test/resources")
+    private val testResources = resourcesDirectory.childFile("Bom")
+    private val emptyBom = testResources.childFile("empty-bom.pom")
+  }
+}
diff --git a/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateBomTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateBomTests.kt
new file mode 100644
index 00000000000..a1bd5fffbf1
--- /dev/null
+++ b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateBomTests.kt
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2025 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
+
+import com.google.firebase.gradle.bomgenerator.GenerateBomTask
+import com.google.firebase.gradle.plugins.datamodels.ArtifactDependency
+import com.google.firebase.gradle.plugins.datamodels.DependencyManagementElement
+import com.google.firebase.gradle.plugins.datamodels.PomElement
+import com.google.firebase.gradle.plugins.services.GMavenService
+import com.google.firebase.gradle.plugins.services.GroupIndexArtifact
+import com.google.firebase.gradle.shouldBeText
+import com.google.firebase.gradle.shouldThrowSubstring
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.collections.shouldContain
+import io.kotest.matchers.collections.shouldNotBeEmpty
+import io.kotest.matchers.collections.shouldNotContain
+import io.kotest.matchers.equals.shouldBeEqual
+import io.kotest.matchers.file.shouldExist
+import io.mockk.clearMocks
+import io.mockk.every
+import io.mockk.mockk
+import java.io.File
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.register
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class GenerateBomTests : FunSpec() {
+  @Rule @JvmField val testProjectDir = TemporaryFolder()
+
+  private val service = mockk()
+
+  @Before
+  fun setup() {
+    clearMocks(service)
+  }
+
+  @Test
+  fun `ignores the configured ignored dependencies`() {
+    val ignoredDependency =
+      GroupIndexArtifact(
+        groupId = "com.google.firebase",
+        artifactId = "firebase-functions",
+        versions = listOf("1.0.0"),
+      )
+
+    val dependencies =
+      listOf(
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-common",
+          versions = listOf("21.0.0", "21.0.1"),
+        )
+      )
+
+    makeOldBom(dependencies)
+    linkGroupIndex(dependencies + ignoredDependency)
+
+    val file =
+      makeNewBom(
+        bomArtifacts = listOf("com.google.firebase:firebase-common"),
+        ignoredArtifacts = listOf("com.google.firebase:firebase-functions"),
+      )
+
+    val newPom = PomElement.fromFile(file)
+    val deps = newPom.dependencyManagement?.dependencies.shouldNotBeEmpty()
+    deps.shouldNotContain(ignoredDependency.toArtifactDependency())
+  }
+
+  @Test
+  fun `major bumps the bom version when artifacts are removed`() {
+    val dependencies =
+      listOf(
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-common",
+          versions = listOf("21.0.0", "21.0.1"),
+        ),
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-functions",
+          versions = listOf("1.0.0", "1.0.1"),
+        ),
+      )
+
+    makeOldBom(dependencies)
+    linkGroupIndex(dependencies)
+
+    val file =
+      makeNewBom(
+        bomArtifacts = listOf("com.google.firebase:firebase-common"),
+        ignoredArtifacts = listOf("com.google.firebase:firebase-functions"),
+      )
+
+    val newPom = PomElement.fromFile(file)
+    val deps = newPom.dependencyManagement?.dependencies.shouldNotBeEmpty().map { it.artifactId }
+    deps.shouldNotContain("firebase-functions")
+
+    newPom.version shouldBeEqual "2.0.0"
+  }
+
+  @Test
+  fun `minor bumps the bom version when artifacts are added`() {
+    val dependencies =
+      listOf(
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-common",
+          versions = listOf("21.0.0", "21.0.1"),
+        )
+      )
+
+    val newArtifact =
+      GroupIndexArtifact(
+        groupId = "com.google.firebase",
+        artifactId = "firebase-functions",
+        versions = listOf("1.0.0"),
+      )
+
+    makeOldBom(dependencies)
+    linkGroupIndex(dependencies + newArtifact)
+
+    val file =
+      makeNewBom(
+        bomArtifacts =
+          listOf("com.google.firebase:firebase-common", "com.google.firebase:firebase-functions")
+      )
+
+    val newPom = PomElement.fromFile(file)
+    val deps = newPom.dependencyManagement?.dependencies.shouldNotBeEmpty().map { it.artifactId }
+    deps.shouldContain("firebase-functions")
+
+    newPom.version shouldBeEqual "1.1.0"
+  }
+
+  @Test
+  fun `bumps the bom version per the biggest artifact bump`() {
+    val dependencies =
+      listOf(
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-common",
+          versions = listOf("21.0.0", "21.0.1"),
+        ),
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-functions",
+          versions = listOf("10.1.2", "11.0.0"),
+        ),
+      )
+
+    makeOldBom(dependencies)
+    linkGroupIndex(dependencies)
+
+    val file =
+      makeNewBom(
+        bomArtifacts =
+          listOf("com.google.firebase:firebase-common", "com.google.firebase:firebase-functions")
+      )
+
+    val newPom = PomElement.fromFile(file)
+    newPom.version shouldBeEqual "2.0.0"
+  }
+
+  @Test
+  fun `allows versions to be overridden`() {
+    val dependencies =
+      listOf(
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-common",
+          versions = listOf("21.0.0", "21.0.1"),
+        )
+      )
+
+    makeOldBom(dependencies)
+    linkGroupIndex(dependencies)
+
+    val file =
+      makeNewBom(bomArtifacts = listOf("com.google.firebase:firebase-common")) {
+        versionOverrides.set(mapOf("com.google.firebase:firebase-common" to "22.0.0"))
+      }
+
+    val newPom = PomElement.fromFile(file)
+    val deps = newPom.dependencyManagement?.dependencies.shouldNotBeEmpty()
+    deps.shouldContain(
+      ArtifactDependency(
+        groupId = "com.google.firebase",
+        artifactId = "firebase-common",
+        version = "22.0.0",
+      )
+    )
+  }
+
+  @Test
+  fun `generates in the expected format`() {
+    val dependencies =
+      listOf(
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-common",
+          versions = listOf("21.0.0", "21.0.1"),
+        ),
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-functions",
+          versions = listOf("1.0.0", "1.0.1"),
+        ),
+      )
+
+    makeOldBom(dependencies)
+    linkGroupIndex(dependencies)
+
+    val file =
+      makeNewBom(
+        bomArtifacts =
+          listOf("com.google.firebase:firebase-common", "com.google.firebase:firebase-functions")
+      )
+
+    file.readText().trim() shouldBeText
+      """
+      
+        4.0.0
+        com.google.firebase
+        firebase-bom
+        1.0.1
+        pom
+        
+          
+            The Apache Software License, Version 2.0
+            http://www.apache.org/licenses/LICENSE-2.0.txt
+            repo
+          
+        
+        
+          
+            
+              com.google.firebase
+              firebase-common
+              21.0.1
+            
+            
+              com.google.firebase
+              firebase-functions
+              1.0.1
+            
+          
+        
+      
+    """
+        .trimIndent()
+  }
+
+  @Test
+  fun `throws an error if artifacts are not live on gmaven yet`() {
+    val dependencies =
+      listOf(
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-common",
+          versions = listOf("21.0.0", "21.0.1"),
+        )
+      )
+
+    makeOldBom(dependencies)
+    linkGroupIndex(dependencies)
+
+    shouldThrowSubstring("not live on gmaven yet", "com.google.firebase:firebase-functions") {
+      makeNewBom(
+        bomArtifacts =
+          listOf("com.google.firebase:firebase-common", "com.google.firebase:firebase-functions")
+      )
+    }
+  }
+
+  @Test
+  fun `throws an error if there are firebase artifacts missing`() {
+    val dependencies =
+      listOf(
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-common",
+          versions = listOf("21.0.0", "21.0.1"),
+        ),
+        GroupIndexArtifact(
+          groupId = "com.google.firebase",
+          artifactId = "firebase-functions",
+          versions = listOf("1.0.0", "1.0.1"),
+        ),
+      )
+
+    makeOldBom(dependencies)
+    linkGroupIndex(dependencies)
+
+    shouldThrowSubstring("artifacts missing", "com.google.firebase:firebase-functions") {
+      makeNewBom(bomArtifacts = listOf("com.google.firebase:firebase-common"))
+    }
+  }
+
+  private fun makeTask(configure: GenerateBomTask.() -> Unit): TaskProvider {
+    val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build()
+
+    return project.tasks.register("generateBom") {
+      outputDirectory.set(project.layout.buildDirectory.dir("bom"))
+      gmaven.set(service)
+
+      configure()
+    }
+  }
+
+  private fun makeNewBom(
+    bomArtifacts: List = emptyList(),
+    ignoredArtifacts: List = emptyList(),
+    configure: GenerateBomTask.() -> Unit = {},
+  ): File {
+    val task = makeTask {
+      this.bomArtifacts.set(bomArtifacts)
+      this.ignoredArtifacts.set(ignoredArtifacts)
+
+      configure()
+    }
+
+    val file =
+      task.get().let {
+        it.generate()
+        it.outputDirectory.get().nestedFile
+      }
+
+    file.shouldExist()
+
+    return file
+  }
+
+  private fun linkGroupIndex(dependencies: List) {
+    every { service.groupIndex("com.google.firebase") } answers { dependencies }
+    every { service.groupIndexArtifactOrNull(any()) } answers { null }
+
+    for (artifact in dependencies) {
+      every {
+        service.groupIndexArtifactOrNull("${artifact.groupId}:${artifact.artifactId}")
+      } answers { artifact }
+    }
+  }
+
+  private fun makeOldBom(dependencies: List): PomElement {
+    val artifacts =
+      dependencies.map { it.toArtifactDependency().copy(version = it.versions.first()) }
+    val bom =
+      PomElement.fromFile(emptyBom)
+        .copy(dependencyManagement = DependencyManagementElement(artifacts))
+
+    every { service.latestPom("com.google.firebase", "firebase-bom") } answers { bom }
+
+    return bom
+  }
+
+  companion object {
+    private val resourcesDirectory = File("src/test/resources")
+    private val testResources = resourcesDirectory.childFile("Bom")
+    private val emptyBom = testResources.childFile("empty-bom.pom")
+  }
+}
diff --git a/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateTutorialBundleTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateTutorialBundleTests.kt
new file mode 100644
index 00000000000..11f1ae0bc72
--- /dev/null
+++ b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/GenerateTutorialBundleTests.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2025 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
+
+import com.google.firebase.gradle.bomgenerator.GenerateTutorialBundleTask
+import com.google.firebase.gradle.plugins.services.GMavenService
+import com.google.firebase.gradle.shouldBeText
+import com.google.firebase.gradle.shouldThrowSubstring
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.file.shouldExist
+import io.mockk.clearMocks
+import io.mockk.every
+import io.mockk.mockk
+import java.io.File
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.register
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class GenerateTutorialBundleTests : FunSpec() {
+  @Rule @JvmField val testProjectDir = TemporaryFolder()
+
+  private val service = mockk()
+
+  @Before
+  fun setup() {
+    clearMocks(service)
+  }
+
+  @Test
+  fun `generates the tutorial bundle`() {
+    val tutorialFile =
+      makeTutorial(
+        commonArtifacts = listOf("com.google.gms:google-services:1.2.3"),
+        firebaseArtifacts =
+          listOf(
+            "com.google.firebase:firebase-analytics:1.2.4",
+            "com.google.firebase:firebase-crashlytics:12.0.0",
+            "com.google.firebase:firebase-perf:10.2.3",
+            "com.google.firebase:firebase-vertexai:9.2.3",
+          ),
+        gradlePlugins =
+          listOf(
+            "com.google.firebase:firebase-appdistribution-gradle:1.21.3",
+            "com.google.firebase:firebase-crashlytics-gradle:15.1.0",
+            "com.google.firebase:perf-plugin:20.5.6",
+          ),
+      ) {
+        requiredArtifacts.set(listOf("com.google.firebase:firebase-crashlytics"))
+      }
+
+    tutorialFile.readText().trim() shouldBeText
+      """
+        
+          
+          
+          
+          
+
+          
+          
+          
+          
+          
+          
+          
+          
+          
+
+          
+          
+          
+          
+          
+          
+          
+          
+          
+          
+        ]>
+      """
+        .trimIndent()
+  }
+
+  @Test
+  fun `does not include empty sections`() {
+    val tutorialFile = makeTutorial()
+
+    tutorialFile.readText().trim() shouldBeText
+      """
+      
+    """
+        .trimIndent()
+  }
+
+  @Test
+  fun `allows versions to be overridden`() {
+    val tutorialFile =
+      makeTutorial(
+        commonArtifacts =
+          listOf(
+            "com.google.gms:google-services:1.2.3",
+            "com.google.firebase:firebase-perf:10.2.3",
+          ),
+        firebaseArtifacts =
+          listOf(
+            "com.google.firebase:firebase-analytics:1.2.4",
+            "com.google.firebase:firebase-crashlytics:12.0.0",
+          ),
+        gradlePlugins =
+          listOf(
+            "com.google.firebase:firebase-appdistribution-gradle:1.21.3",
+            "com.google.firebase:firebase-crashlytics-gradle:15.1.0",
+          ),
+      ) {
+        versionOverrides.set(
+          mapOf(
+            "com.google.gms:google-services" to "3.2.1",
+            "com.google.firebase:firebase-crashlytics" to "1.2.12",
+            "com.google.firebase:firebase-crashlytics-gradle" to "1.15.0",
+          )
+        )
+      }
+
+    tutorialFile.readText().trim() shouldBeText
+      """
+      
+        
+        
+        
+        
+        
+        
+
+        
+        
+        
+        
+        
+
+        
+        
+        
+        
+        
+        
+        
+      ]>
+    """
+        .trimIndent()
+  }
+
+  @Test
+  fun `enforces the predefined order of artifacts`() {
+    val tutorialFile =
+      makeTutorial(
+        commonArtifacts =
+          listOf(
+            "com.google.firebase:firebase-perf:10.2.3",
+            "com.google.gms:google-services:1.2.3",
+          ),
+        firebaseArtifacts =
+          listOf(
+            "com.google.firebase:firebase-crashlytics:12.0.0",
+            "com.google.firebase:firebase-analytics:1.2.4",
+          ),
+        gradlePlugins =
+          listOf(
+            "com.google.firebase:firebase-crashlytics-gradle:15.1.0",
+            "com.google.firebase:firebase-appdistribution-gradle:1.21.3",
+          ),
+      )
+
+    tutorialFile.readText().trim() shouldBeText
+      """
+      
+        
+        
+        
+        
+        
+        
+
+        
+        
+        
+        
+        
+
+        
+        
+        
+        
+        
+        
+        
+      ]>
+    """
+        .trimIndent()
+  }
+
+  @Test
+  fun `throws an error if required artifacts are missing`() {
+    shouldThrowSubstring(
+      "Artifacts required for the tutorial bundle are missing from the provided input",
+      "com.google.firebase:firebase-auth",
+    ) {
+      makeTutorial(
+        firebaseArtifacts =
+          listOf(
+            "com.google.firebase:firebase-crashlytics:12.0.0",
+            "com.google.firebase:firebase-analytics:1.2.4",
+          )
+      ) {
+        requiredArtifacts.add("com.google.firebase:firebase-auth")
+      }
+    }
+  }
+
+  @Test
+  fun `throws an error if an unreleased artifact is used`() {
+    shouldThrowSubstring("missing from gmaven", "com.google.firebase:firebase-auth") {
+      every { service.latestVersionOrNull(any()) } answers { null }
+
+      val task = makeTask { firebaseArtifacts.set(listOf("com.google.firebase:firebase-auth")) }
+
+      task.get().generate()
+    }
+  }
+
+  @Test
+  fun `allows unreleased artifacts to be used if the version is provided`() {
+    val task = makeTask {
+      firebaseArtifacts.set(listOf("com.google.firebase:firebase-auth"))
+      versionOverrides.set(mapOf("com.google.firebase:firebase-auth" to "10.0.0"))
+    }
+
+    val file =
+      task.get().let {
+        it.generate()
+        it.tutorialFile.get().asFile
+      }
+
+    file.shouldExist()
+    file.readText().trim() shouldBeText
+      """
+        
+          
+          
+        ]>
+      """
+        .trimIndent()
+  }
+
+  private fun makeTask(
+    configure: GenerateTutorialBundleTask.() -> Unit
+  ): TaskProvider {
+    val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build()
+    return project.tasks.register("generateTutorialBundle") {
+      tutorialFile.set(project.layout.buildDirectory.file("tutorial.txt"))
+      gmaven.set(service)
+
+      configure()
+    }
+  }
+
+  private fun artifactsToVersionMap(artifacts: List): Map {
+    return artifacts
+      .associate {
+        val (groupId, artifactId, version) = it.split(":")
+        "$groupId:$artifactId" to version
+      }
+      .onEach { entry -> every { service.latestVersionOrNull(entry.key) } answers { entry.value } }
+  }
+
+  private fun makeTutorial(
+    firebaseArtifacts: List = emptyList(),
+    commonArtifacts: List = emptyList(),
+    gradlePlugins: List = emptyList(),
+    perfArtifacts: List = emptyList(),
+    configure: GenerateTutorialBundleTask.() -> Unit = {},
+  ): File {
+
+    val mappedFirebaseArtifacts = artifactsToVersionMap(firebaseArtifacts)
+    val mappedCommonArtifacts = artifactsToVersionMap(commonArtifacts)
+    val mappedGradlePlugins = artifactsToVersionMap(gradlePlugins)
+    val mappedPerfArtifacts = artifactsToVersionMap(perfArtifacts)
+
+    val task = makeTask {
+      this.firebaseArtifacts.set(mappedFirebaseArtifacts.keys)
+      this.commonArtifacts.set(mappedCommonArtifacts.keys)
+      this.gradlePlugins.set(mappedGradlePlugins.keys)
+      this.perfArtifacts.set(mappedPerfArtifacts.keys)
+      configure()
+    }
+
+    val file =
+      task.get().let {
+        it.generate()
+        it.tutorialFile.get().asFile
+      }
+
+    file.shouldExist()
+
+    return file
+  }
+}
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/LicenseResolverPluginTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/LicenseResolverPluginTests.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/LicenseResolverPluginTests.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/LicenseResolverPluginTests.kt
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/MakeReleaseNotesTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/MakeReleaseNotesTests.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/MakeReleaseNotesTests.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/MakeReleaseNotesTests.kt
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/Memoization.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/Memoization.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/Memoization.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/Memoization.kt
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/ModuleVersionTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/ModuleVersionTests.kt
similarity index 87%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/ModuleVersionTests.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/ModuleVersionTests.kt
index d5027399a0f..c6eb626825d 100644
--- a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/ModuleVersionTests.kt
+++ b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/ModuleVersionTests.kt
@@ -95,6 +95,16 @@ class ModuleVersionTests : FunSpec() {
     ModuleVersion(1, 1, 1).apply { bump(PRE) shouldBe this }
   }
 
+  @Test
+  fun `Bump resets the smaller version types`() {
+    val version = ModuleVersion(1, 1, 1, PreReleaseVersion(ALPHA, 2))
+
+    version.bump(PRE) shouldBe ModuleVersion(1, 1, 1, PreReleaseVersion(ALPHA, 3))
+    version.bump(PATCH) shouldBe ModuleVersion(1, 1, 2, PreReleaseVersion(ALPHA, 1))
+    version.bump(MINOR) shouldBe ModuleVersion(1, 2, 0, PreReleaseVersion(ALPHA, 1))
+    version.bump(MAJOR) shouldBe ModuleVersion(2, 0, 0, PreReleaseVersion(ALPHA, 1))
+  }
+
   @Test
   fun `Bump correctly chooses the smallest by default`() {
     ModuleVersion(1, 1, 1).bump().patch shouldBe 2
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/MoveUnreleasedChangesTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/MoveUnreleasedChangesTests.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/MoveUnreleasedChangesTests.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/MoveUnreleasedChangesTests.kt
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/PublishingPluginTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/PublishingPluginTests.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/PublishingPluginTests.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/PublishingPluginTests.kt
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/UpdatePinnedDependenciesTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/UpdatePinnedDependenciesTests.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/UpdatePinnedDependenciesTests.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/UpdatePinnedDependenciesTests.kt
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/VendorTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/VendorTests.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/VendorTests.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/VendorTests.kt
diff --git a/buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/publishing.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/plugins/publishing.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/google/firebase/gradle/plugins/publishing.kt
rename to plugins/src/test/kotlin/com/google/firebase/gradle/plugins/publishing.kt
diff --git a/plugins/src/test/kotlin/com/google/firebase/gradle/services/GMavenServiceTests.kt b/plugins/src/test/kotlin/com/google/firebase/gradle/services/GMavenServiceTests.kt
new file mode 100644
index 00000000000..5d40ac5f4a9
--- /dev/null
+++ b/plugins/src/test/kotlin/com/google/firebase/gradle/services/GMavenServiceTests.kt
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2025 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.services
+
+import com.google.firebase.gradle.endsWith
+import com.google.firebase.gradle.plugins.childFile
+import com.google.firebase.gradle.plugins.services.DocumentService
+import com.google.firebase.gradle.plugins.services.GMavenServiceController
+import io.kotest.assertions.asClue
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.inspectors.forAll
+import io.kotest.matchers.collections.shouldContain
+import io.kotest.matchers.collections.shouldNotBeEmpty
+import io.kotest.matchers.equals.shouldBeEqual
+import io.kotest.matchers.equals.shouldNotBeEqual
+import io.kotest.matchers.file.shouldExist
+import io.kotest.matchers.file.shouldHaveSameContentAs
+import io.kotest.matchers.nulls.shouldBeNull
+import io.kotest.matchers.nulls.shouldNotBeNull
+import io.kotest.matchers.shouldBe
+import io.mockk.clearMocks
+import io.mockk.every
+import io.mockk.spyk
+import io.mockk.verify
+import java.io.File
+import java.io.FileNotFoundException
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class GMavenServiceTests : FunSpec() {
+  @Rule @JvmField val testProjectDir = TemporaryFolder()
+  private val service = spyk(DocumentService())
+  private val gmaven by lazy { GMavenServiceController(testProjectDir.root.toPath(), service) }
+
+  @Before
+  fun setup() {
+    clearMocks(service)
+
+    every { service.openStream(any()) } throws (FileNotFoundException())
+    every { service.openStream(endsWith("group-index.xml")) } answers
+      {
+        testGroupIndex.inputStream()
+      }
+  }
+
+  @Test
+  fun `fetches group index`() {
+    val artifacts = gmaven.forceGetGroupIndex("com.google.firebase")
+    verify { service.openStream(endsWith("group-index.xml")) }
+
+    artifacts.shouldNotBeEmpty()
+
+    val artifact = artifacts.find { it.artifactId == "firebase-common" }
+
+    artifact.shouldNotBeNull()
+    artifact.asClue {
+      it.versions.shouldContain("21.0.0")
+      it.latestVersion shouldBeEqual "21.0.0"
+    }
+  }
+
+  @Test
+  fun `fetches a released pom file`() {
+    every { service.openStream(endsWith("firebase-common-21.0.0.pom")) } answers
+      {
+        testPOM.inputStream()
+      }
+
+    val pomElement = gmaven.pomOrNull("com.google.firebase", "firebase-common", "21.0.0")
+
+    verify { service.openStream(endsWith("firebase-common-21.0.0.pom")) }
+
+    pomElement.shouldNotBeNull()
+    pomElement.asClue {
+      it.version shouldBe "21.0.0"
+      it.artifactId shouldBe "firebase-common"
+      it.packaging shouldBe "aar"
+      it.dependencies.shouldNotBeEmpty()
+    }
+  }
+
+  @Test
+  fun `fetches a released aar file`() {
+    every { service.openStream(endsWith("firebase-common-21.0.0.aar")) } answers
+      {
+        testAAR.inputStream()
+      }
+
+    val aar = gmaven.artifactOrNull("com.google.firebase", "firebase-common", "21.0.0")
+
+    verify { service.openStream(endsWith("firebase-common-21.0.0.aar")) }
+
+    aar.shouldNotBeNull()
+    aar.shouldExist()
+    aar.shouldHaveSameContentAs(testAAR)
+  }
+
+  @Test
+  fun `fetches a released jar file`() {
+    every { service.openStream(endsWith("firebase-common-21.0.0.jar")) } answers
+      {
+        testAAR.inputStream()
+      }
+
+    val jar = gmaven.artifactOrNull("com.google.firebase", "firebase-common", "21.0.0")
+
+    verify { service.openStream(endsWith("firebase-common-21.0.0.jar")) }
+
+    jar.shouldNotBeNull()
+    jar.shouldExist()
+    jar.shouldHaveSameContentAs(testAAR)
+  }
+
+  @Test
+  fun `returns null when artifact files are not found`() {
+    val artifact = gmaven.artifactOrNull("com.google.firebase", "firebase-common", "21.0.0")
+
+    verify { service.openStream(endsWith("firebase-common-21.0.0.aar")) }
+
+    artifact.shouldBeNull()
+  }
+
+  @Test
+  fun `fetches the latest released version`() {
+    val version = gmaven.latestVersionOrNull("com.google.firebase", "firebase-common")
+
+    verify(exactly = 1) { service.openStream(any()) }
+
+    version.shouldNotBeNull()
+    version shouldBeEqual "21.0.0"
+  }
+
+  @Test
+  fun `checks if an artifact has been published`() {
+    gmaven.hasReleasedArtifact("com.google.firebase", "artifact-that-doesnt-exist") shouldBe false
+    gmaven.hasReleasedArtifact("com.google.firebase", "firebase-common") shouldBe true
+
+    verify(exactly = 1) { service.openStream(any()) }
+  }
+
+  @Test
+  fun `checks if a version has been released`() {
+    gmaven.hasReleasedVersion("com.google.firebase", "firebase-common", "21.0.0") shouldBe true
+    gmaven.hasReleasedVersion("com.google.firebase", "firebase-common", "22.0.0") shouldBe false
+
+    verify(exactly = 1) { service.openStream(any()) }
+  }
+
+  @Test
+  fun `caches pom requests`() {
+    every { service.openStream(endsWith("firebase-common-21.0.0.pom")) } answers
+      {
+        testPOM.inputStream()
+      }
+
+    val firstPom = gmaven.pomOrNull("com.google.firebase", "firebase-common", "21.0.0")
+    val secondPom = gmaven.pomOrNull("com.google.firebase", "firebase-common", "21.0.0")
+
+    verify(exactly = 1) { service.openStream(endsWith("firebase-common-21.0.0.pom")) }
+
+    firstPom.shouldNotBeNull()
+    secondPom.shouldNotBeNull()
+
+    firstPom.shouldBeEqual(secondPom)
+  }
+
+  @Test
+  fun `caches artifact requests`() {
+    every { service.openStream(endsWith("firebase-common-21.0.0.aar")) } answers
+      {
+        testAAR.inputStream()
+      }
+
+    val firstArtifact = gmaven.artifactOrNull("com.google.firebase", "firebase-common", "21.0.0")
+    val secondArtifact = gmaven.artifactOrNull("com.google.firebase", "firebase-common", "21.0.0")
+
+    verify(exactly = 1) { service.openStream(endsWith("firebase-common-21.0.0.aar")) }
+
+    firstArtifact.shouldNotBeNull()
+    secondArtifact.shouldNotBeNull()
+
+    firstArtifact.shouldBeEqual(secondArtifact)
+  }
+
+  @Test
+  fun `does not overwrite previous force fetches`() {
+    every { service.openStream(endsWith("firebase-common-21.0.0.aar")) } answers
+      {
+        testAAR.inputStream()
+      }
+
+    val firstArtifact = gmaven.forceGetArtifact("com.google.firebase", "firebase-common", "21.0.0")
+    val secondArtifact = gmaven.forceGetArtifact("com.google.firebase", "firebase-common", "21.0.0")
+
+    verify(exactly = 2) { service.openStream(endsWith("firebase-common-21.0.0.aar")) }
+
+    firstArtifact.shouldNotBeNull()
+    secondArtifact.shouldNotBeNull()
+
+    firstArtifact.shouldExist()
+    secondArtifact.shouldExist()
+
+    firstArtifact.shouldNotBeEqual(secondArtifact)
+  }
+
+  @Test
+  fun `should be thread safe`() {
+    every { service.openStream(endsWith("firebase-common-21.0.0.aar")) } answers
+      {
+        testAAR.inputStream()
+      }
+
+    every { service.openStream(endsWith("firebase-common-20.0.0.aar")) } answers
+      {
+        testAAR.inputStream()
+      }
+
+    val executor = Executors.newFixedThreadPool(10)
+
+    val firstBatch =
+      (0 until 100).map {
+        executor.queue { gmaven.artifactOrNull("com.google.firebase", "firebase-common", "21.0.0") }
+      }
+
+    val secondBatch =
+      (0 until 100).map {
+        executor.queue { gmaven.artifactOrNull("com.google.firebase", "firebase-common", "20.0.0") }
+      }
+
+    verify(exactly = 0) { service.openStream(any()) }
+
+    executor.shutdown()
+    executor.awaitTermination(5, TimeUnit.SECONDS)
+
+    verify(exactly = 3) { service.openStream(any()) }
+
+    val firstArtifact = gmaven.artifactOrNull("com.google.firebase", "firebase-common", "21.0.0")
+    val secondArtifact = gmaven.artifactOrNull("com.google.firebase", "firebase-common", "20.0.0")
+
+    firstArtifact.shouldNotBeNull()
+    secondArtifact.shouldNotBeNull()
+
+    firstBatch.forAll {
+      it.isDone shouldBe true
+      firstArtifact.shouldBeEqual(it.get().shouldNotBeNull())
+    }
+
+    secondBatch.forAll {
+      it.isDone shouldBe true
+      secondArtifact.shouldBeEqual(it.get().shouldNotBeNull())
+    }
+  }
+
+  @Suppress("ControlFlowWithEmptyBody")
+  private fun  ExecutorService.queue(task: () -> T) =
+    submit {
+      while (!isShutdown) {}
+      task()
+    }
+
+  companion object {
+    private val resourcesDirectory = File("src/test/resources")
+    private val testResources = resourcesDirectory.childFile("GMaven")
+    private val testAAR = testResources.childFile("firebase-common.aar")
+    private val testPOM = testResources.childFile("firebase-common.pom")
+    private val testGroupIndex = testResources.childFile("group-index.xml")
+  }
+}
diff --git a/buildSrc/src/test/resources/BasicProject/build.gradle b/plugins/src/test/resources/BasicProject/build.gradle
similarity index 100%
rename from buildSrc/src/test/resources/BasicProject/build.gradle
rename to plugins/src/test/resources/BasicProject/build.gradle
diff --git a/buildSrc/src/test/resources/BasicProject/firebase-storage/CHANGELOG.md b/plugins/src/test/resources/BasicProject/firebase-storage/CHANGELOG.md
similarity index 100%
rename from buildSrc/src/test/resources/BasicProject/firebase-storage/CHANGELOG.md
rename to plugins/src/test/resources/BasicProject/firebase-storage/CHANGELOG.md
diff --git a/buildSrc/src/test/resources/BasicProject/firebase-storage/build.gradle b/plugins/src/test/resources/BasicProject/firebase-storage/build.gradle
similarity index 100%
rename from buildSrc/src/test/resources/BasicProject/firebase-storage/build.gradle
rename to plugins/src/test/resources/BasicProject/firebase-storage/build.gradle
diff --git a/buildSrc/src/test/resources/BasicProject/firebase-storage/gradle.properties b/plugins/src/test/resources/BasicProject/firebase-storage/gradle.properties
similarity index 100%
rename from buildSrc/src/test/resources/BasicProject/firebase-storage/gradle.properties
rename to plugins/src/test/resources/BasicProject/firebase-storage/gradle.properties
diff --git a/buildSrc/src/test/resources/BasicProject/settings.gradle b/plugins/src/test/resources/BasicProject/settings.gradle
similarity index 100%
rename from buildSrc/src/test/resources/BasicProject/settings.gradle
rename to plugins/src/test/resources/BasicProject/settings.gradle
diff --git a/plugins/src/test/resources/Bom/empty-bom.pom b/plugins/src/test/resources/Bom/empty-bom.pom
new file mode 100644
index 00000000000..206e6413059
--- /dev/null
+++ b/plugins/src/test/resources/Bom/empty-bom.pom
@@ -0,0 +1,18 @@
+
+    4.0.0
+    com.google.firebase
+    firebase-bom
+    1.0.0
+    pom
+    
+        
+            The Apache Software License, Version 2.0
+            http://www.apache.org/licenses/LICENSE-2.0.txt
+            repo
+        
+    
+    
+        
+        
+    
+
\ No newline at end of file
diff --git a/plugins/src/test/resources/Bom/filled-bom.pom b/plugins/src/test/resources/Bom/filled-bom.pom
new file mode 100644
index 00000000000..66bd6420081
--- /dev/null
+++ b/plugins/src/test/resources/Bom/filled-bom.pom
@@ -0,0 +1,223 @@
+
+    4.0.0
+    com.google.firebase
+    firebase-bom
+    33.7.0
+    pom
+    
+        
+            The Apache Software License, Version 2.0
+            http://www.apache.org/licenses/LICENSE-2.0.txt
+            repo
+        
+    
+    
+        
+            
+                com.google.firebase
+                firebase-ml-modeldownloader-ktx
+                25.0.1
+            
+            
+                com.google.firebase
+                firebase-perf
+                21.0.3
+            
+            
+                com.google.firebase
+                firebase-crashlytics-ndk
+                19.3.0
+            
+            
+                com.google.firebase
+                firebase-appcheck
+                18.0.0
+            
+            
+                com.google.firebase
+                firebase-encoders
+                17.0.0
+            
+            
+                com.google.firebase
+                firebase-functions-ktx
+                21.1.0
+            
+            
+                com.google.firebase
+                firebase-storage-ktx
+                21.0.1
+            
+            
+                com.google.firebase
+                firebase-functions
+                21.1.0
+            
+            
+                com.google.firebase
+                firebase-appcheck-debug
+                18.0.0
+            
+            
+                com.google.firebase
+                firebase-perf-ktx
+                21.0.3
+            
+            
+                com.google.firebase
+                firebase-analytics
+                22.1.2
+            
+            
+                com.google.firebase
+                firebase-dynamic-links-ktx
+                22.1.0
+            
+            
+                com.google.firebase
+                firebase-appcheck-debug-testing
+                18.0.0
+            
+            
+                com.google.firebase
+                firebase-auth
+                23.1.0
+            
+            
+                com.google.firebase
+                firebase-config-ktx
+                22.0.1
+            
+            
+                com.google.firebase
+                firebase-inappmessaging-display
+                21.0.1
+            
+            
+                com.google.firebase
+                firebase-installations-ktx
+                18.0.0
+            
+            
+                com.google.firebase
+                firebase-crashlytics-ktx
+                19.3.0
+            
+            
+                com.google.firebase
+                firebase-vertexai
+                16.0.2
+            
+            
+                com.google.firebase
+                firebase-inappmessaging
+                21.0.1
+            
+            
+                com.google.firebase
+                firebase-analytics-ktx
+                22.1.2
+            
+            
+                com.google.firebase
+                firebase-firestore
+                25.1.1
+            
+            
+                com.google.firebase
+                firebase-database-ktx
+                21.0.0
+            
+            
+                com.google.firebase
+                firebase-inappmessaging-display-ktx
+                21.0.1
+            
+            
+                com.google.firebase
+                firebase-ml-modeldownloader
+                25.0.1
+            
+            
+                com.google.firebase
+                firebase-config
+                22.0.1
+            
+            
+                com.google.firebase
+                firebase-common-ktx
+                21.0.0
+            
+            
+                com.google.firebase
+                firebase-firestore-ktx
+                25.1.1
+            
+            
+                com.google.firebase
+                firebase-messaging-directboot
+                24.1.0
+            
+            
+                com.google.firebase
+                firebase-appcheck-playintegrity
+                18.0.0
+            
+            
+                com.google.firebase
+                firebase-appcheck-ktx
+                18.0.0
+            
+            
+                com.google.firebase
+                firebase-messaging
+                24.1.0
+            
+            
+                com.google.firebase
+                firebase-auth-ktx
+                23.1.0
+            
+            
+                com.google.firebase
+                firebase-messaging-ktx
+                24.1.0
+            
+            
+                com.google.firebase
+                firebase-crashlytics
+                19.3.0
+            
+            
+                com.google.firebase
+                firebase-dynamic-links
+                22.1.0
+            
+            
+                com.google.firebase
+                firebase-storage
+                21.0.1
+            
+            
+                com.google.firebase
+                firebase-common
+                21.0.0
+            
+            
+                com.google.firebase
+                firebase-installations
+                18.0.0
+            
+            
+                com.google.firebase
+                firebase-inappmessaging-ktx
+                21.0.1
+            
+            
+                com.google.firebase
+                firebase-database
+                21.0.0
+            
+        
+    
+
\ No newline at end of file
diff --git a/plugins/src/test/resources/GMaven/firebase-common.aar b/plugins/src/test/resources/GMaven/firebase-common.aar
new file mode 100644
index 00000000000..1ee610ddbe0
Binary files /dev/null and b/plugins/src/test/resources/GMaven/firebase-common.aar differ
diff --git a/plugins/src/test/resources/GMaven/firebase-common.pom b/plugins/src/test/resources/GMaven/firebase-common.pom
new file mode 100644
index 00000000000..6e265b71164
--- /dev/null
+++ b/plugins/src/test/resources/GMaven/firebase-common.pom
@@ -0,0 +1,76 @@
+
+
+  4.0.0
+  com.google.firebase
+  firebase-common
+  21.0.0
+  aar
+  
+    
+      The Apache Software License, Version 2.0
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+    
+  
+  
+    scm:git:https://github.com/firebase/firebase-android-sdk.git
+    https://github.com/firebase/firebase-android-sdk
+  
+  
+    
+      org.jetbrains.kotlinx
+      kotlinx-coroutines-play-services
+      1.6.4
+      compile
+      jar
+    
+    
+      com.google.firebase
+      firebase-components
+      18.0.0
+      compile
+      aar
+    
+    
+      com.google.firebase
+      firebase-annotations
+      16.2.0
+      compile
+      jar
+    
+    
+      androidx.annotation
+      annotation
+      1.5.0
+      runtime
+      jar
+    
+    
+      androidx.concurrent
+      concurrent-futures
+      1.1.0
+      runtime
+      jar
+    
+    
+      org.jetbrains.kotlin
+      kotlin-stdlib
+      1.8.22
+      runtime
+      jar
+    
+    
+      com.google.android.gms
+      play-services-basement
+      18.3.0
+      runtime
+      aar
+    
+    
+      com.google.android.gms
+      play-services-tasks
+      18.1.0
+      runtime
+      aar
+    
+  
+
diff --git a/plugins/src/test/resources/GMaven/group-index.xml b/plugins/src/test/resources/GMaven/group-index.xml
new file mode 100644
index 00000000000..fde2bb041b1
--- /dev/null
+++ b/plugins/src/test/resources/GMaven/group-index.xml
@@ -0,0 +1,119 @@
+
+
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+
diff --git a/buildSrc/src/test/resources/MakeReleaseNotes/release-notes.md b/plugins/src/test/resources/MakeReleaseNotes/release-notes.md
similarity index 100%
rename from buildSrc/src/test/resources/MakeReleaseNotes/release-notes.md
rename to plugins/src/test/resources/MakeReleaseNotes/release-notes.md
diff --git a/buildSrc/src/test/resources/MoveUnreleasedChanges/basic.md b/plugins/src/test/resources/MoveUnreleasedChanges/basic.md
similarity index 100%
rename from buildSrc/src/test/resources/MoveUnreleasedChanges/basic.md
rename to plugins/src/test/resources/MoveUnreleasedChanges/basic.md
diff --git a/buildSrc/src/test/resources/MoveUnreleasedChanges/release.json b/plugins/src/test/resources/MoveUnreleasedChanges/release.json
similarity index 100%
rename from buildSrc/src/test/resources/MoveUnreleasedChanges/release.json
rename to plugins/src/test/resources/MoveUnreleasedChanges/release.json
diff --git a/protolite-well-known-types/api.txt b/protolite-well-known-types/api.txt
index d802177e249..da4f6cc18fe 100644
--- a/protolite-well-known-types/api.txt
+++ b/protolite-well-known-types/api.txt
@@ -1 +1 @@
-// Signature format: 2.0
+// Signature format: 3.0
diff --git a/sdkProperties.gradle b/sdkProperties.gradle
deleted file mode 100644
index f9c961bb9b1..00000000000
--- a/sdkProperties.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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.
-
-ext {
-    targetSdkVersion = 34
-    compileSdkVersion = 34
-    minSdkVersion = 21
-}
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index e32f57e51cc..00000000000
--- a/settings.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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.
-
-pluginManagement {
-  includeBuild("firebase-dataconnect/gradleplugin")
-}
-
-rootProject.name = 'com.google.firebase'
-
-//Note: do not add subprojects to this file. Instead add them to subprojects.gradle
-
-apply from: 'gradle/projectSettings.gradle'
-
-discoverSubprojects(file('subprojects.cfg')).each {
-  include ":$it"
-}
-
-renameBuildScripts(rootProject)
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 00000000000..21d672a3b53
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2024 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.
+ */
+
+import java.io.File
+import org.gradle.api.initialization.ProjectDescriptor
+
+pluginManagement {
+  repositories {
+    google()
+    mavenLocal()
+    mavenCentral()
+    gradlePluginPortal()
+    maven("https://storage.googleapis.com/android-ci/mvn/") { metadataSources { artifact() } }
+  }
+
+  includeBuild("./plugins")
+  includeBuild("firebase-dataconnect/gradleplugin")
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+  repositories {
+    google()
+    mavenLocal()
+    mavenCentral()
+    maven("https://storage.googleapis.com/android-ci/mvn/") { metadataSources { artifact() } }
+  }
+}
+
+/**
+ * Parses the input file and returns a list of subprojects.
+ *
+ * Expected file format:
+ * - Empty lines are ignored.
+ * - Lines starting with # are considered comments.
+ * - Other lines are treated as project paths.
+ */
+fun discoverSubprojects(subprojectsFile: File): List {
+  return subprojectsFile
+    .readLines()
+    .map { it.trim() }
+    .filter { it.isNotEmpty() && !it.startsWith("#") }
+}
+
+/**
+ * Recursively links the build scripts for each sub-project.
+ *
+ * Gradle expects build scripts to be named `build`, but we name all of our build scripts after the
+ * project (eg; `firebase-common.gradle.kts`). While this makes it easier to quickly access the
+ * build scripts of certain projects, it also prevents gradle from being able to identify the build
+ * scripts.
+ *
+ * To fix this, we internally tell gradle the actual name of the build file, so that it can properly
+ * find it.
+ */
+fun setBuildScripts(project: ProjectDescriptor) {
+  val names =
+    listOf(
+      "${project.name}.gradle.kts",
+      "${project.name}.gradle",
+      "build.gradle.kts",
+      "build.gradle",
+    )
+
+  val name = names.find { File(project.projectDir, it).exists() }
+
+  if (name !== null) {
+    project.buildFileName = name
+  } else {
+    logger.debug(
+      """
+      Couldn't find a build script for project: "${project.name}".
+      Assuming this is either a container or marker project.
+    """
+        .trimIndent()
+    )
+  }
+
+  for (child in project.children) {
+    setBuildScripts(child)
+  }
+}
+
+/** Note: Do not add subprojects to this file. Instead, add them to `subprojects.cfg`. */
+discoverSubprojects(file("subprojects.cfg")).forEach { include(":$it") }
+
+setBuildScripts(rootProject)
+
+rootProject.name = "com.google.firebase"
diff --git a/transport/transport-api/api.txt b/transport/transport-api/api.txt
index d802177e249..da4f6cc18fe 100644
--- a/transport/transport-api/api.txt
+++ b/transport/transport-api/api.txt
@@ -1 +1 @@
-// Signature format: 2.0
+// Signature format: 3.0
diff --git a/transport/transport-backend-cct/api.txt b/transport/transport-backend-cct/api.txt
index d802177e249..da4f6cc18fe 100644
--- a/transport/transport-backend-cct/api.txt
+++ b/transport/transport-backend-cct/api.txt
@@ -1 +1 @@
-// Signature format: 2.0
+// Signature format: 3.0
diff --git a/transport/transport-runtime/api.txt b/transport/transport-runtime/api.txt
index d802177e249..da4f6cc18fe 100644
--- a/transport/transport-runtime/api.txt
+++ b/transport/transport-runtime/api.txt
@@ -1 +1 @@
-// Signature format: 2.0
+// Signature format: 3.0