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/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/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/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/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/Values.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/model/Values.java index 26b06a5d2cb..834fb2454a3 100644 --- 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 @@ -230,7 +230,7 @@ public static int compare(Value left, Value right) { case TYPE_ORDER_SERVER_TIMESTAMP: return compareTimestamps(getLocalWriteTime(left), getLocalWriteTime(right)); case TYPE_ORDER_STRING: - return left.getStringValue().compareTo(right.getStringValue()); + return Util.compareUtf8Strings(left.getStringValue(), right.getStringValue()); case TYPE_ORDER_BLOB: return Util.compareByteStrings(left.getBytesValue(), right.getBytesValue()); case TYPE_ORDER_REFERENCE: @@ -349,7 +349,7 @@ private static int compareMaps(MapValue left, MapValue right) { while (iterator1.hasNext() && iterator2.hasNext()) { Map.Entry entry1 = iterator1.next(); Map.Entry entry2 = iterator2.next(); - int keyCompare = entry1.getKey().compareTo(entry2.getKey()); + int keyCompare = Util.compareUtf8Strings(entry1.getKey(), entry2.getKey()); if (keyCompare != 0) { return keyCompare; } 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-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/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..c0bfdfb214e 100644 --- a/firebase-vertexai/CHANGELOG.md +++ b/firebase-vertexai/CHANGELOG.md @@ -1,5 +1,10 @@ # Unreleased +* [fixed] Added support for new values sent by the server for `FinishReason` and `BlockReason`. +* [changed] Added support for modality-based token count. (#6658) +* [feature] Added support for generating images with Imagen models. +# 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) 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..abfbf6572d4 --- /dev/null +++ b/firebase-vertexai/api.txt @@ -0,0 +1,722 @@ +// 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); + method @com.google.firebase.vertexai.type.PublicPreviewAPI public com.google.firebase.vertexai.ImagenModel imagenModel(String modelName); + method @com.google.firebase.vertexai.type.PublicPreviewAPI public com.google.firebase.vertexai.ImagenModel imagenModel(String modelName, com.google.firebase.vertexai.type.ImagenGenerationConfig? generationConfig = null); + method @com.google.firebase.vertexai.type.PublicPreviewAPI public com.google.firebase.vertexai.ImagenModel imagenModel(String modelName, com.google.firebase.vertexai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.vertexai.type.ImagenSafetySettings? safetySettings = null); + method @com.google.firebase.vertexai.type.PublicPreviewAPI public com.google.firebase.vertexai.ImagenModel imagenModel(String modelName, com.google.firebase.vertexai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.vertexai.type.ImagenSafetySettings? safetySettings = null, com.google.firebase.vertexai.type.RequestOptions requestOptions = com.google.firebase.vertexai.type.RequestOptions()); + 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()); + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenModel { + method public suspend Object? generateImages(String prompt, kotlin.coroutines.Continuation>); + } + +} + +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); + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public abstract class ImagenModelFutures { + method public static final com.google.firebase.vertexai.java.ImagenModelFutures from(com.google.firebase.vertexai.ImagenModel model); + method public abstract com.google.common.util.concurrent.ListenableFuture> generateImages(String prompt); + method public abstract com.google.firebase.vertexai.ImagenModel getImageModel(); + field public static final com.google.firebase.vertexai.java.ImagenModelFutures.Companion Companion; + } + + public static final class ImagenModelFutures.Companion { + method public com.google.firebase.vertexai.java.ImagenModelFutures from(com.google.firebase.vertexai.ImagenModel 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 ContentBlockedException extends com.google.firebase.vertexai.type.FirebaseVertexAIException { + } + + 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; + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenAspectRatio { + field public static final com.google.firebase.vertexai.type.ImagenAspectRatio.Companion Companion; + field public static final com.google.firebase.vertexai.type.ImagenAspectRatio LANDSCAPE_16x9; + field public static final com.google.firebase.vertexai.type.ImagenAspectRatio LANDSCAPE_4x3; + field public static final com.google.firebase.vertexai.type.ImagenAspectRatio PORTRAIT_3x4; + field public static final com.google.firebase.vertexai.type.ImagenAspectRatio PORTRAIT_9x16; + field public static final com.google.firebase.vertexai.type.ImagenAspectRatio SQUARE_1x1; + } + + public static final class ImagenAspectRatio.Companion { + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenGenerationConfig { + ctor public ImagenGenerationConfig(String? negativePrompt = null, Integer? numberOfImages = 1, com.google.firebase.vertexai.type.ImagenAspectRatio? aspectRatio = null, com.google.firebase.vertexai.type.ImagenImageFormat? imageFormat = null, Boolean? addWatermark = null); + method public Boolean? getAddWatermark(); + method public com.google.firebase.vertexai.type.ImagenAspectRatio? getAspectRatio(); + method public com.google.firebase.vertexai.type.ImagenImageFormat? getImageFormat(); + method public String? getNegativePrompt(); + method public Integer? getNumberOfImages(); + property public final Boolean? addWatermark; + property public final com.google.firebase.vertexai.type.ImagenAspectRatio? aspectRatio; + property public final com.google.firebase.vertexai.type.ImagenImageFormat? imageFormat; + property public final String? negativePrompt; + property public final Integer? numberOfImages; + field public static final com.google.firebase.vertexai.type.ImagenGenerationConfig.Companion Companion; + } + + public static final class ImagenGenerationConfig.Builder { + ctor public ImagenGenerationConfig.Builder(); + method public com.google.firebase.vertexai.type.ImagenGenerationConfig build(); + field public Boolean? addWatermark; + field public com.google.firebase.vertexai.type.ImagenAspectRatio? aspectRatio; + field public com.google.firebase.vertexai.type.ImagenImageFormat? imageFormat; + field public String? negativePrompt; + field public Integer? numberOfImages; + } + + public static final class ImagenGenerationConfig.Companion { + method public com.google.firebase.vertexai.type.ImagenGenerationConfig.Builder builder(); + } + + public final class ImagenGenerationConfigKt { + method @com.google.firebase.vertexai.type.PublicPreviewAPI public static com.google.firebase.vertexai.type.ImagenGenerationConfig imagenGenerationConfig(kotlin.jvm.functions.Function1 init); + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenGenerationResponse { + method public String? getFilteredReason(); + method public java.util.List getImages(); + property public final String? filteredReason; + property public final java.util.List images; + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenImageFormat { + method public Integer? getCompressionQuality(); + method public String getMimeType(); + property public final Integer? compressionQuality; + property public final String mimeType; + field public static final com.google.firebase.vertexai.type.ImagenImageFormat.Companion Companion; + } + + public static final class ImagenImageFormat.Companion { + method public com.google.firebase.vertexai.type.ImagenImageFormat jpeg(Integer? compressionQuality = null); + method public com.google.firebase.vertexai.type.ImagenImageFormat png(); + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenInlineImage { + method public android.graphics.Bitmap asBitmap(); + method public byte[] getData(); + method public String getMimeType(); + property public final byte[] data; + property public final String mimeType; + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenPersonFilterLevel { + field public static final com.google.firebase.vertexai.type.ImagenPersonFilterLevel ALLOW_ADULT; + field public static final com.google.firebase.vertexai.type.ImagenPersonFilterLevel ALLOW_ALL; + field public static final com.google.firebase.vertexai.type.ImagenPersonFilterLevel BLOCK_ALL; + field public static final com.google.firebase.vertexai.type.ImagenPersonFilterLevel.Companion Companion; + } + + public static final class ImagenPersonFilterLevel.Companion { + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenSafetyFilterLevel { + field public static final com.google.firebase.vertexai.type.ImagenSafetyFilterLevel BLOCK_LOW_AND_ABOVE; + field public static final com.google.firebase.vertexai.type.ImagenSafetyFilterLevel BLOCK_MEDIUM_AND_ABOVE; + field public static final com.google.firebase.vertexai.type.ImagenSafetyFilterLevel BLOCK_NONE; + field public static final com.google.firebase.vertexai.type.ImagenSafetyFilterLevel BLOCK_ONLY_HIGH; + field public static final com.google.firebase.vertexai.type.ImagenSafetyFilterLevel.Companion Companion; + } + + public static final class ImagenSafetyFilterLevel.Companion { + } + + @com.google.firebase.vertexai.type.PublicPreviewAPI public final class ImagenSafetySettings { + ctor public ImagenSafetySettings(com.google.firebase.vertexai.type.ImagenSafetyFilterLevel safetyFilterLevel, com.google.firebase.vertexai.type.ImagenPersonFilterLevel personFilterLevel); + } + + 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; + } + + @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This API is part of an experimental public preview and may change in " + "backwards-incompatible ways without notice.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface PublicPreviewAPI { + } + + 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/FirebaseVertexAI.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/FirebaseVertexAI.kt index ff256482112..b89e5671992 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/FirebaseVertexAI.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/FirebaseVertexAI.kt @@ -24,7 +24,10 @@ import com.google.firebase.auth.internal.InternalAuthProvider import com.google.firebase.inject.Provider import com.google.firebase.vertexai.type.Content import com.google.firebase.vertexai.type.GenerationConfig +import com.google.firebase.vertexai.type.ImagenGenerationConfig +import com.google.firebase.vertexai.type.ImagenSafetySettings import com.google.firebase.vertexai.type.InvalidLocationException +import com.google.firebase.vertexai.type.PublicPreviewAPI import com.google.firebase.vertexai.type.RequestOptions import com.google.firebase.vertexai.type.SafetySetting import com.google.firebase.vertexai.type.Tool @@ -79,6 +82,37 @@ internal constructor( ) } + /** + * Instantiates a new [ImagenModel] given the provided parameters. + * + * @param modelName The name of the model to use, for example `"imagen-3.0-generate-001"`. + * @param generationConfig The configuration parameters to use for image generation. + * @param safetySettings The safety bounds the model will abide by during image generation. + * @param requestOptions Configuration options for sending requests to the backend. + * @return The initialized [ImagenModel] instance. + */ + @JvmOverloads + @PublicPreviewAPI + public fun imagenModel( + modelName: String, + generationConfig: ImagenGenerationConfig? = null, + safetySettings: ImagenSafetySettings? = null, + requestOptions: RequestOptions = RequestOptions(), + ): ImagenModel { + if (location.trim().isEmpty() || location.contains("/")) { + throw InvalidLocationException(location) + } + return ImagenModel( + "projects/${firebaseApp.options.projectId}/locations/${location}/publishers/google/models/${modelName}", + firebaseApp.options.apiKey, + generationConfig, + safetySettings, + requestOptions, + appCheckProvider.get(), + internalAuthProvider.get(), + ) + } + public companion object { /** The [FirebaseVertexAI] instance for the default [FirebaseApp] */ @JvmStatic 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..a49d4c279a8 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 @@ -17,15 +17,12 @@ package com.google.firebase.vertexai import android.graphics.Bitmap -import android.util.Log import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider import com.google.firebase.auth.internal.InternalAuthProvider import com.google.firebase.vertexai.common.APIController +import com.google.firebase.vertexai.common.AppCheckHeaderProvider 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 @@ -40,12 +37,9 @@ import com.google.firebase.vertexai.type.SerializationException import com.google.firebase.vertexai.type.Tool import com.google.firebase.vertexai.type.ToolConfig import com.google.firebase.vertexai.type.content -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map -import kotlinx.coroutines.tasks.await /** * Represents a multimodal model (like Gemini), capable of generating content based on various input @@ -59,10 +53,8 @@ internal constructor( private val tools: List? = null, private val toolConfig: ToolConfig? = null, private val systemInstruction: Content? = null, - private val controller: APIController + private val controller: APIController, ) { - - @JvmOverloads internal constructor( modelName: String, apiKey: String, @@ -86,42 +78,8 @@ internal constructor( modelName, requestOptions, "gl-kotlin/${KotlinVersion.CURRENT} fire/${BuildConfig.VERSION_NAME}", - object : HeaderProvider { - override val timeout: Duration - get() = 10.seconds - - override suspend fun generateHeaders(): Map { - val headers = mutableMapOf() - if (appCheckTokenProvider == null) { - Log.w(TAG, "AppCheck not registered, skipping") - } else { - val token = appCheckTokenProvider.getToken(false).await() - - if (token.error != null) { - Log.w(TAG, "Error obtaining AppCheck token", token.error) - } - // The Firebase App Check backend can differentiate between apps without App Check, and - // wrongly configured apps by verifying the value of the token, so it always needs to be - // included. - headers["X-Firebase-AppCheck"] = token.token - } - - if (internalAuthProvider == null) { - Log.w(TAG, "Auth not registered, skipping") - } else { - try { - val token = internalAuthProvider.getAccessToken(false).await() - - headers["Authorization"] = "Firebase ${token.token!!}" - } catch (e: Exception) { - Log.w(TAG, "Error getting Auth token ", e) - } - } - - return headers - } - } - ) + AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider), + ), ) /** @@ -249,7 +207,7 @@ internal constructor( generationConfig?.toInternal(), tools?.map { it.toInternal() }, toolConfig?.toInternal(), - systemInstruction?.copy(role = "system")?.toInternal() + systemInstruction?.copy(role = "system")?.toInternal(), ) private fun constructCountTokensRequest(vararg prompt: Content) = diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/ImagenModel.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/ImagenModel.kt new file mode 100644 index 00000000000..583ef24bcc4 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/ImagenModel.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.vertexai + +import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider +import com.google.firebase.auth.internal.InternalAuthProvider +import com.google.firebase.vertexai.common.APIController +import com.google.firebase.vertexai.common.AppCheckHeaderProvider +import com.google.firebase.vertexai.common.ContentBlockedException +import com.google.firebase.vertexai.common.GenerateImageRequest +import com.google.firebase.vertexai.type.FirebaseVertexAIException +import com.google.firebase.vertexai.type.ImagenGenerationConfig +import com.google.firebase.vertexai.type.ImagenGenerationResponse +import com.google.firebase.vertexai.type.ImagenInlineImage +import com.google.firebase.vertexai.type.ImagenSafetySettings +import com.google.firebase.vertexai.type.PublicPreviewAPI +import com.google.firebase.vertexai.type.RequestOptions + +/** + * Represents a generative model (like Imagen), capable of generating images based on various input + * types. + */ +@PublicPreviewAPI +public class ImagenModel +internal constructor( + private val modelName: String, + private val generationConfig: ImagenGenerationConfig? = null, + private val safetySettings: ImagenSafetySettings? = null, + private val controller: APIController, +) { + @JvmOverloads + internal constructor( + modelName: String, + apiKey: String, + generationConfig: ImagenGenerationConfig? = null, + safetySettings: ImagenSafetySettings? = null, + requestOptions: RequestOptions = RequestOptions(), + appCheckTokenProvider: InteropAppCheckTokenProvider? = null, + internalAuthProvider: InternalAuthProvider? = null, + ) : this( + modelName, + generationConfig, + safetySettings, + APIController( + apiKey, + modelName, + requestOptions, + "gl-kotlin/${KotlinVersion.CURRENT} fire/${BuildConfig.VERSION_NAME}", + AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider), + ), + ) + + /** + * Generates an image, returning the result directly to the caller. + * + * @param prompt The input(s) given to the model as a prompt. + */ + public suspend fun generateImages(prompt: String): ImagenGenerationResponse = + try { + controller + .generateImage(constructRequest(prompt, null, generationConfig)) + .validate() + .toPublicInline() + } catch (e: Throwable) { + throw FirebaseVertexAIException.from(e) + } + + private fun constructRequest( + prompt: String, + gcsUri: String?, + config: ImagenGenerationConfig?, + ): GenerateImageRequest { + return GenerateImageRequest( + listOf(GenerateImageRequest.ImagenPrompt(prompt)), + GenerateImageRequest.ImagenParameters( + sampleCount = config?.numberOfImages ?: 1, + includeRaiReason = true, + addWatermark = generationConfig?.addWatermark, + personGeneration = safetySettings?.personFilterLevel?.internalVal, + negativePrompt = config?.negativePrompt, + safetySetting = safetySettings?.safetyFilterLevel?.internalVal, + storageUri = gcsUri, + aspectRatio = config?.aspectRatio?.internalVal, + imageOutputOptions = generationConfig?.imageFormat?.toInternal(), + ), + ) + } + + internal companion object { + private val TAG = ImagenModel::class.java.simpleName + internal const val DEFAULT_FILTERED_ERROR = + "Unable to show generated images. All images were filtered out because they violated Vertex AI's usage guidelines. You will not be charged for blocked images. Try rephrasing the prompt. If you think this was an error, send feedback." + } +} + +@OptIn(PublicPreviewAPI::class) +private fun ImagenGenerationResponse.Internal.validate(): ImagenGenerationResponse.Internal { + if (predictions.none { it.mimeType != null }) { + throw ContentBlockedException( + message = predictions.first { it.raiFilteredReason != null }.raiFilteredReason + ?: ImagenModel.DEFAULT_FILTERED_ERROR + ) + } + return this +} 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..f8bfe0bc24f 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,16 @@ 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.ImagenGenerationResponse +import com.google.firebase.vertexai.type.PublicPreviewAPI 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 @@ -56,12 +60,15 @@ import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json +@OptIn(ExperimentalSerializationApi::class) internal val JSON = Json { ignoreUnknownKeys = true prettyPrint = false isLenient = true + explicitNulls = false } /** @@ -76,6 +83,7 @@ internal val JSON = Json { * @property apiClient The value to pass in the `x-goog-api-client` header. * @property headerProvider A provider that generates extra headers to include in all HTTP requests. */ +@OptIn(PublicPreviewAPI::class) internal class APIController internal constructor( private val key: String, @@ -106,7 +114,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 +122,30 @@ internal constructor( applyHeaderProvider() } .also { validateResponse(it) } - .body() + .body() .validate() } catch (e: Throwable) { throw FirebaseCommonAIException.from(e) } - fun generateContentStream(request: GenerateContentRequest): Flow = + suspend fun generateImage(request: GenerateImageRequest): ImagenGenerationResponse.Internal = + try { + client + .post("${requestOptions.endpoint}/${requestOptions.apiVersion}/$model:predict") { + applyCommonConfiguration(request) + applyHeaderProvider() + } + .also { validateResponse(it) } + .body() + } catch (e: Throwable) { + throw FirebaseCommonAIException.from(e) + } + + fun generateContentStream( + request: GenerateContentRequest + ): Flow = client - .postStream( + .postStream( "${requestOptions.endpoint}/${requestOptions.apiVersion}/$model:streamGenerateContent?alt=sse" ) { applyCommonConfiguration(request) @@ -130,7 +153,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") { @@ -147,6 +170,7 @@ internal constructor( when (request) { is GenerateContentRequest -> setBody(request) is CountTokensRequest -> setBody(request) + is GenerateImageRequest -> setBody(request) } contentType(ContentType.Application.Json) header("x-goog-api-key", key) @@ -254,6 +278,9 @@ private suspend fun validateResponse(response: HttpResponse) { if (message.contains("quota")) { throw QuotaExceededException(message) } + if (message.contains("The prompt could not be submitted")) { + throw PromptBlockedException(message) + } getServiceDisabledErrorDetailsOrNull(error)?.let { val errorMessage = if (it.metadata?.get("service") == "firebasevertexai.googleapis.com") { @@ -275,19 +302,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/AppCheckHeaderProvider.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/AppCheckHeaderProvider.kt new file mode 100644 index 00000000000..fb3b52ad46f --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/AppCheckHeaderProvider.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.vertexai.common + +import android.util.Log +import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider +import com.google.firebase.auth.internal.InternalAuthProvider +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.tasks.await + +internal class AppCheckHeaderProvider( + private val logTag: String, + private val appCheckTokenProvider: InteropAppCheckTokenProvider? = null, + private val internalAuthProvider: InternalAuthProvider? = null, +) : HeaderProvider { + override val timeout: Duration + get() = 10.seconds + + override suspend fun generateHeaders(): Map { + val headers = mutableMapOf() + if (appCheckTokenProvider == null) { + Log.w(logTag, "AppCheck not registered, skipping") + } else { + val token = appCheckTokenProvider.getToken(false).await() + + if (token.error != null) { + Log.w(logTag, "Error obtaining AppCheck token", token.error) + } + // The Firebase App Check backend can differentiate between apps without App Check, and + // wrongly configured apps by verifying the value of the token, so it always needs to be + // included. + headers["X-Firebase-AppCheck"] = token.token + } + + if (internalAuthProvider == null) { + Log.w(logTag, "Auth not registered, skipping") + } else { + try { + val token = internalAuthProvider.getAccessToken(false).await() + + headers["Authorization"] = "Firebase ${token.token!!}" + } catch (e: Exception) { + Log.w(logTag, "Error getting Auth token ", e) + } + } + + return headers + } +} 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..ad982f2daff 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 @@ -65,14 +66,18 @@ internal class InvalidAPIKeyException(message: String, cause: Throwable? = null) * * @property response the full server response for the request. */ -internal class PromptBlockedException( - val response: GenerateContentResponse, - cause: Throwable? = null +internal class PromptBlockedException +internal constructor( + val response: GenerateContentResponse.Internal?, + cause: Throwable? = null, + message: String? = null, ) : FirebaseCommonAIException( - "Prompt was blocked: ${response.promptFeedback?.blockReason?.name}", + "Prompt was blocked: ${response?.promptFeedback?.blockReason?.name?: message}", cause, - ) + ) { + internal constructor(message: String, cause: Throwable? = null) : this(null, cause, message) +} /** * The user's location (region) is not supported by the API. @@ -98,7 +103,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 +130,21 @@ 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 class ContentBlockedException(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..8696a090fc2 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,35 +16,37 @@ 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.ImagenImageFormat +import com.google.firebase.vertexai.type.PublicPreviewAPI +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 -internal sealed interface Request +internal 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) = @@ -65,3 +67,25 @@ internal data class CountTokensRequest( ) } } + +@Serializable +internal data class GenerateImageRequest( + val instances: List, + val parameters: ImagenParameters, +) : Request { + @Serializable internal data class ImagenPrompt(val prompt: String) + + @OptIn(PublicPreviewAPI::class) + @Serializable + internal data class ImagenParameters( + val sampleCount: Int, + val includeRaiReason: Boolean, + val storageUri: String?, + val negativePrompt: String?, + val aspectRatio: String?, + val safetySetting: String?, + val personGeneration: String?, + val addWatermark: Boolean?, + val imageOutputOptions: ImagenImageFormat.Internal?, + ) +} 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/internal/util/kotlin.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/kotlin.kt deleted file mode 100644 index b4d68d2f14c..00000000000 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/kotlin.kt +++ /dev/null @@ -1,41 +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 java.lang.reflect.Field - -/** - * Removes the last character from the [StringBuilder]. - * - * If the StringBuilder is empty, calling this function will throw an [IndexOutOfBoundsException]. - * - * @return The [StringBuilder] used to make the call, for optional chaining. - * @throws IndexOutOfBoundsException if the StringBuilder is empty. - */ -internal fun StringBuilder.removeLast(): StringBuilder = - if (isEmpty()) throw IndexOutOfBoundsException("StringBuilder is empty.") - else deleteCharAt(length - 1) - -/** - * A variant of [getAnnotation][Field.getAnnotation] that provides implicit Kotlin support. - * - * Syntax sugar for: - * ``` - * getAnnotation(T::class.java) - * ``` - */ -internal inline fun Field.getAnnotation() = getAnnotation(T::class.java) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/java/ImagenModelFutures.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/java/ImagenModelFutures.kt new file mode 100644 index 00000000000..97b043312c4 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/java/ImagenModelFutures.kt @@ -0,0 +1,59 @@ +/* + * 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.java + +import androidx.concurrent.futures.SuspendToFutureAdapter +import com.google.common.util.concurrent.ListenableFuture +import com.google.firebase.vertexai.ImagenModel +import com.google.firebase.vertexai.type.ImagenGenerationResponse +import com.google.firebase.vertexai.type.ImagenInlineImage +import com.google.firebase.vertexai.type.PublicPreviewAPI + +/** + * Wrapper class providing Java compatible methods for [ImagenModel]. + * + * @see [ImagenModel] + */ +@PublicPreviewAPI +public abstract class ImagenModelFutures internal constructor() { + /** + * Generates an image, returning the result directly to the caller. + * + * @param prompt The main text prompt from which the image is generated. + */ + public abstract fun generateImages( + prompt: String, + ): ListenableFuture> + + /** Returns the [ImagenModel] object wrapped by this object. */ + public abstract fun getImageModel(): ImagenModel + + private class FuturesImpl(private val model: ImagenModel) : ImagenModelFutures() { + override fun generateImages( + prompt: String, + ): ListenableFuture> = + SuspendToFutureAdapter.launchFuture { model.generateImages(prompt) } + + override fun getImageModel(): ImagenModel = model + } + + public companion object { + + /** @return a [ImagenModelFutures] created around the provided [ImagenModel] */ + @JvmStatic public fun from(model: ImagenModel): ImagenModelFutures = FuturesImpl(model) + } +} 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..4890cd7ada3 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. */ @@ -45,7 +44,7 @@ internal constructor(message: String, cause: Throwable? = null) : RuntimeExcepti is com.google.firebase.vertexai.common.InvalidAPIKeyException -> InvalidAPIKeyException(cause.message ?: "") is com.google.firebase.vertexai.common.PromptBlockedException -> - PromptBlockedException(cause.response.toPublic(), cause.cause) + PromptBlockedException(cause.response?.toPublic(), cause.cause) is com.google.firebase.vertexai.common.UnsupportedUserLocationException -> UnsupportedUserLocationException(cause.cause) is com.google.firebase.vertexai.common.InvalidStateException -> @@ -58,6 +57,8 @@ internal constructor(message: String, cause: Throwable? = null) : RuntimeExcepti ServiceDisabledException(cause.message ?: "", cause.cause) is com.google.firebase.vertexai.common.UnknownException -> UnknownException(cause.message ?: "", cause.cause) + is com.google.firebase.vertexai.common.ContentBlockedException -> + ContentBlockedException(cause.message ?: "", cause.cause) else -> UnknownException(cause.message ?: "", cause) } is TimeoutCancellationException -> @@ -88,13 +89,22 @@ internal constructor(message: String, cause: Throwable? = null) : * * @property response The full server response. */ -// TODO(rlazo): Add secondary constructor to pass through the message? public class PromptBlockedException -internal constructor(public val response: GenerateContentResponse, cause: Throwable? = null) : +internal constructor( + public val response: GenerateContentResponse?, + cause: Throwable? = null, + message: String? = null, +) : FirebaseVertexAIException( - "Prompt was blocked: ${response.promptFeedback?.blockReason?.name}", + "Prompt was blocked: ${response?.promptFeedback?.blockReason?.name?: message}", cause, - ) + ) { + internal constructor(message: String, cause: Throwable? = null) : this(null, cause, message) +} + +public class ContentBlockedException +internal constructor(message: String, cause: Throwable? = null) : + FirebaseVertexAIException(message, cause) /** * The user's location (region) is not supported by the API. 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/ImagenAspectRatio.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenAspectRatio.kt new file mode 100644 index 00000000000..e605a6e987e --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenAspectRatio.kt @@ -0,0 +1,34 @@ +/* + * 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 + +/** Represents the aspect ratio that the generated image should conform to. */ +@PublicPreviewAPI +public class ImagenAspectRatio private constructor(internal val internalVal: String) { + public companion object { + /** A square image, useful for icons, profile pictures, etc. */ + @JvmField public val SQUARE_1x1: ImagenAspectRatio = ImagenAspectRatio("1:1") + /** A portrait image in 3:4, the aspect ratio of older TVs. */ + @JvmField public val PORTRAIT_3x4: ImagenAspectRatio = ImagenAspectRatio("3:4") + /** A landscape image in 4:3, the aspect ratio of older TVs. */ + @JvmField public val LANDSCAPE_4x3: ImagenAspectRatio = ImagenAspectRatio("4:3") + /** A portrait image in 9:16, the aspect ratio of modern monitors and phone screens. */ + @JvmField public val PORTRAIT_9x16: ImagenAspectRatio = ImagenAspectRatio("9:16") + /** A landscape image in 16:9, the aspect ratio of modern monitors and phone screens. */ + @JvmField public val LANDSCAPE_16x9: ImagenAspectRatio = ImagenAspectRatio("16:9") + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGCSImage.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGCSImage.kt new file mode 100644 index 00000000000..380bfa3c30b --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGCSImage.kt @@ -0,0 +1,27 @@ +/* + * 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 + +/** + * Represents an Imagen-generated image that is contained in Google Cloud Storage. + * + * @param gcsUri Contains the `gs://` URI for the image. + * @param mimeType Contains the MIME type of the image (for example, `"image/png"`). + */ +@PublicPreviewAPI +internal class ImagenGCSImage +internal constructor(public val gcsUri: String, public val mimeType: String) {} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGenerationConfig.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGenerationConfig.kt new file mode 100644 index 00000000000..bbf795f4d40 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGenerationConfig.kt @@ -0,0 +1,99 @@ +/* + * 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 + +/** + * Contains extra settings to configure image generation. + * + * @param negativePrompt This string contains things that should be explicitly excluded from + * generated images. + * @param numberOfImages How many images should be generated. + * @param aspectRatio The aspect ratio of the generated images. + * @param imageFormat The file format/compression of the generated images. + * @param addWatermark Adds an invisible watermark to mark the image as AI generated. + */ +@PublicPreviewAPI +public class ImagenGenerationConfig( + public val negativePrompt: String? = null, + public val numberOfImages: Int? = 1, + public val aspectRatio: ImagenAspectRatio? = null, + public val imageFormat: ImagenImageFormat? = null, + public val addWatermark: Boolean? = null, +) { + /** + * Builder for creating a [ImagenGenerationConfig]. + * + * This is mainly intended for Java interop. For Kotlin, use [imagenGenerationConfig] for a more + * idiomatic experience. + * + * @property negativePrompt See [ImagenGenerationConfig.negativePrompt]. + * @property numberOfImages See [ImagenGenerationConfig.numberOfImages]. + * @property aspectRatio See [ImagenGenerationConfig.aspectRatio]. + * @property imageFormat See [ImagenGenerationConfig.imageFormat] + * @property addWatermark See [ImagenGenerationConfig.addWatermark] + * @see [imagenGenerationConfig] + */ + public class Builder { + @JvmField public var negativePrompt: String? = null + @JvmField public var numberOfImages: Int? = 1 + @JvmField public var aspectRatio: ImagenAspectRatio? = null + @JvmField public var imageFormat: ImagenImageFormat? = null + @JvmField public var addWatermark: Boolean? = null + + /** + * Alternative casing for [ImagenGenerationConfig.Builder]: + * ``` + * val config = GenerationConfig.builder() + * ``` + */ + public fun build(): ImagenGenerationConfig = + ImagenGenerationConfig( + negativePrompt = negativePrompt, + numberOfImages = numberOfImages, + aspectRatio = aspectRatio, + imageFormat = imageFormat, + addWatermark = addWatermark, + ) + } + + public companion object { + public fun builder(): Builder = Builder() + } +} + +/** + * Helper method to construct a [ImagenGenerationConfig] in a DSL-like manner. + * + * Example Usage: + * ``` + * imagenGenerationConfig { + * negativePrompt = "People, black and white, painting" + * numberOfImages = 1 + * aspectRatio = ImagenAspecRatio.SQUARE_1x1 + * imageFormat = ImagenImageFormat.png() + * addWatermark = false + * } + * ``` + */ +@PublicPreviewAPI +public fun imagenGenerationConfig( + init: ImagenGenerationConfig.Builder.() -> Unit +): ImagenGenerationConfig { + val builder = ImagenGenerationConfig.builder() + builder.init() + return builder.build() +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGenerationResponse.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGenerationResponse.kt new file mode 100644 index 00000000000..a1a80360848 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenGenerationResponse.kt @@ -0,0 +1,59 @@ +/* + * 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 a response from a call to [ImagenModel#generateImages] + * + * @param images contains the generated images + * @param filteredReason if fewer images were generated than were requested, this field will contain + * the reason they were filtered out. + */ +@PublicPreviewAPI +public class ImagenGenerationResponse +internal constructor(public val images: List, public val filteredReason: String?) { + + @Serializable + internal data class Internal(val predictions: List) { + internal fun toPublicGCS() = + ImagenGenerationResponse( + images = predictions.filter { it.mimeType != null }.map { it.toPublicGCS() }, + null, + ) + + internal fun toPublicInline() = + ImagenGenerationResponse( + images = predictions.filter { it.mimeType != null }.map { it.toPublicInline() }, + null, + ) + } + + @Serializable + internal data class ImagenImageResponse( + val bytesBase64Encoded: String? = null, + val gcsUri: String? = null, + val mimeType: String? = null, + val raiFilteredReason: String? = null, + ) { + internal fun toPublicInline() = + ImagenInlineImage(bytesBase64Encoded!!.toByteArray(), mimeType!!) + + internal fun toPublicGCS() = ImagenGCSImage(gcsUri!!, mimeType!!) + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenImageFormat.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenImageFormat.kt new file mode 100644 index 00000000000..41c85e98a7a --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenImageFormat.kt @@ -0,0 +1,53 @@ +/* + * 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 the format an image should be returned in. + * + * @param mimeType A string (like `"image/jpeg"`) specifying the encoding MIME type of the image. + * @param compressionQuality an int (1-100) representing the quality of the image; a lower number + * means the image is permitted to be lower quality to reduce size. This parameter is not relevant + * for every MIME type. + */ +@PublicPreviewAPI +public class ImagenImageFormat +private constructor(public val mimeType: String, public val compressionQuality: Int?) { + + internal fun toInternal() = Internal(mimeType, compressionQuality) + + @Serializable internal data class Internal(val mimeType: String, val compressionQuality: Int?) + + public companion object { + /** + * An [ImagenImageFormat] representing a JPEG image. + * + * @param compressionQuality an int (1-100) representing the quality of the image; a lower + * number means the image is permitted to be lower quality to reduce size. + */ + public fun jpeg(compressionQuality: Int? = null): ImagenImageFormat { + return ImagenImageFormat("image/jpeg", compressionQuality) + } + + /** An [ImagenImageFormat] representing a PNG image */ + public fun png(): ImagenImageFormat { + return ImagenImageFormat("image/png", null) + } + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenInlineImage.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenInlineImage.kt new file mode 100644 index 00000000000..03e93abf8e7 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenInlineImage.kt @@ -0,0 +1,40 @@ +/* + * 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 android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 + +/** + * Represents an Imagen-generated image that is contained inline + * + * @param data Contains the raw bytes of the image + * @param mimeType Contains the MIME type of the image (for example, `"image/png"`) + */ +@PublicPreviewAPI +public class ImagenInlineImage +internal constructor(public val data: ByteArray, public val mimeType: String) { + + /** + * Returns the image as an Android OS native [Bitmap] so that it can be saved or sent to the UI. + */ + public fun asBitmap(): Bitmap { + val data = Base64.decode(data, Base64.NO_WRAP) + return BitmapFactory.decodeByteArray(data, 0, data.size) + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenPersonFilterLevel.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenPersonFilterLevel.kt new file mode 100644 index 00000000000..14031c86766 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenPersonFilterLevel.kt @@ -0,0 +1,31 @@ +/* + * 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.type + +/** A filter used to prevent images from containing depictions of children or people. */ +@PublicPreviewAPI +public class ImagenPersonFilterLevel private constructor(internal val internalVal: String) { + public companion object { + /** No filters applied. */ + @JvmField public val ALLOW_ALL: ImagenPersonFilterLevel = ImagenPersonFilterLevel("allow_all") + /** Filters out any images containing depictions of children. */ + @JvmField + public val ALLOW_ADULT: ImagenPersonFilterLevel = ImagenPersonFilterLevel("allow_adult") + /** Filters out any images containing depictions of people. */ + @JvmField public val BLOCK_ALL: ImagenPersonFilterLevel = ImagenPersonFilterLevel("dont_allow") + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenSafetyFilterLevel.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenSafetyFilterLevel.kt new file mode 100644 index 00000000000..205538ebc0a --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenSafetyFilterLevel.kt @@ -0,0 +1,40 @@ +/* + * 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 + +/** Used for safety filtering. */ +@PublicPreviewAPI +public class ImagenSafetyFilterLevel private constructor(internal val internalVal: String) { + public companion object { + /** Strongest filtering level, most strict blocking. */ + @JvmField + public val BLOCK_LOW_AND_ABOVE: ImagenSafetyFilterLevel = + ImagenSafetyFilterLevel("block_low_and_above") + /** Block some problematic prompts and responses. */ + @JvmField + public val BLOCK_MEDIUM_AND_ABOVE: ImagenSafetyFilterLevel = + ImagenSafetyFilterLevel("block_medium_and_above") + /** + * Reduces the number of requests blocked due to safety filters. May increase objectionable + * content generated by the Imagen model. + */ + @JvmField + public val BLOCK_ONLY_HIGH: ImagenSafetyFilterLevel = ImagenSafetyFilterLevel("block_only_high") + /** Turns off all optional safety filters. */ + @JvmField public val BLOCK_NONE: ImagenSafetyFilterLevel = ImagenSafetyFilterLevel("block_none") + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenSafetySettings.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenSafetySettings.kt new file mode 100644 index 00000000000..d5a00b557bd --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/ImagenSafetySettings.kt @@ -0,0 +1,29 @@ +/* + * 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 + +/** + * A configuration for filtering unsafe content or images containing people. + * + * @param safetyFilterLevel Used to filter unsafe content. + * @param personFilterLevel Used to filter images containing people. + */ +@PublicPreviewAPI +public class ImagenSafetySettings( + internal val safetyFilterLevel: ImagenSafetyFilterLevel, + internal val personFilterLevel: ImagenPersonFilterLevel, +) {} 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-functions/ktx/src/test/kotlin/com/google/firebase/functions/TestVisibilityUtil.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/PublicPreviewAPI.kt similarity index 61% rename from firebase-functions/ktx/src/test/kotlin/com/google/firebase/functions/TestVisibilityUtil.kt rename to firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/PublicPreviewAPI.kt index 9a8be4b4f9c..50f9880f3be 100644 --- a/firebase-functions/ktx/src/test/kotlin/com/google/firebase/functions/TestVisibilityUtil.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/PublicPreviewAPI.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google LLC + * 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. @@ -14,9 +14,13 @@ * limitations under the License. */ -package com.google.firebase.functions +package com.google.firebase.vertexai.type -/** - * Returns true if the {@link HttpsCallableReference} is configured to use FAC limited-use tokens. - */ -fun HttpsCallableReference.usesLimitedUseFacTokens() = options.getLimitedUseAppCheckTokens() +@Retention(AnnotationRetention.BINARY) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, + message = + "This API is part of an experimental public preview and may change in " + + "backwards-incompatible ways without notice.", +) +public annotation class PublicPreviewAPI() 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/StreamingSnapshotTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/StreamingSnapshotTests.kt index 9a6beb057a1..ce53bcf9e33 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/StreamingSnapshotTests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/StreamingSnapshotTests.kt @@ -119,7 +119,7 @@ internal class StreamingSnapshotTests { withTimeout(testTimeout) { val exception = shouldThrow { responses.collect() } - exception.response.promptFeedback?.blockReason shouldBe BlockReason.SAFETY + exception.response?.promptFeedback?.blockReason shouldBe BlockReason.SAFETY } } @@ -130,8 +130,8 @@ internal class StreamingSnapshotTests { withTimeout(testTimeout) { val exception = shouldThrow { responses.collect() } - exception.response.promptFeedback?.blockReason shouldBe BlockReason.SAFETY - exception.response.promptFeedback?.blockReasonMessage shouldBe "Reasons" + exception.response?.promptFeedback?.blockReason shouldBe BlockReason.SAFETY + exception.response?.promptFeedback?.blockReasonMessage shouldBe "Reasons" } } 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..1724b3788cb 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,8 @@ package com.google.firebase.vertexai import com.google.firebase.vertexai.type.BlockReason +import com.google.firebase.vertexai.type.ContentBlockedException +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 @@ -24,6 +26,7 @@ import com.google.firebase.vertexai.type.HarmProbability import com.google.firebase.vertexai.type.HarmSeverity import com.google.firebase.vertexai.type.InvalidAPIKeyException import com.google.firebase.vertexai.type.PromptBlockedException +import com.google.firebase.vertexai.type.PublicPreviewAPI import com.google.firebase.vertexai.type.ResponseStoppedException import com.google.firebase.vertexai.type.SerializationException import com.google.firebase.vertexai.type.ServerException @@ -33,6 +36,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 +46,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 @@ -50,6 +55,7 @@ import kotlinx.serialization.json.jsonPrimitive import org.json.JSONArray import org.junit.Test +@OptIn(PublicPreviewAPI::class) internal class UnarySnapshotTests { private val testTimeout = 5.seconds @@ -67,15 +73,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 + } + } } } @@ -110,7 +128,7 @@ internal class UnarySnapshotTests { withTimeout(testTimeout) { shouldThrow { model.generateContent("prompt") } should { - it.response.promptFeedback?.blockReason shouldBe BlockReason.UNKNOWN + it.response?.promptFeedback?.blockReason shouldBe BlockReason.UNKNOWN } } } @@ -165,7 +183,7 @@ internal class UnarySnapshotTests { withTimeout(testTimeout) { shouldThrow { model.generateContent("prompt") } should { - it.response.promptFeedback?.blockReason shouldBe BlockReason.SAFETY + it.response?.promptFeedback?.blockReason shouldBe BlockReason.SAFETY } } } @@ -176,8 +194,8 @@ internal class UnarySnapshotTests { withTimeout(testTimeout) { shouldThrow { model.generateContent("prompt") } should { - it.response.promptFeedback?.blockReason shouldBe BlockReason.SAFETY - it.response.promptFeedback?.blockReasonMessage shouldContain "Reasons" + it.response?.promptFeedback?.blockReason shouldBe BlockReason.SAFETY + it.response?.promptFeedback?.blockReasonMessage shouldContain "Reasons" } } } @@ -200,7 +218,7 @@ internal class UnarySnapshotTests { fun `user location error`() = goldenUnaryFile( "unary-failure-unsupported-user-location.json", - HttpStatusCode.PreconditionFailed + HttpStatusCode.PreconditionFailed, ) { withTimeout(testTimeout) { shouldThrow { model.generateContent("prompt") } @@ -213,6 +231,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 +256,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 +292,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 +482,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 + } } } @@ -473,4 +518,23 @@ internal class UnarySnapshotTests { goldenUnaryFile("unary-failure-model-not-found.json", HttpStatusCode.NotFound) { withTimeout(testTimeout) { shouldThrow { model.countTokens("prompt") } } } + + @Test + fun `generateImages should throw when all images filtered`() = + goldenUnaryFile("unary-failure-generate-images-all-filtered.json") { + withTimeout(testTimeout) { + shouldThrow { imagenModel.generateImages("prompt") } + } + } + + @Test + fun `generateImages should throw when prompt blocked`() = + goldenUnaryFile( + "unary-failure-generate-images-prompt-blocked.json", + HttpStatusCode.BadRequest, + ) { + withTimeout(testTimeout) { + shouldThrow { imagenModel.generateImages("prompt") } + } + } } 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..6d14025b28c 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..c316a9ece81 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/src/test/java/com/google/firebase/vertexai/util/tests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/util/tests.kt index 29b7923e35b..9428aea67ef 100644 --- a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/util/tests.kt +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/util/tests.kt @@ -14,10 +14,14 @@ * limitations under the License. */ +@file:OptIn(PublicPreviewAPI::class) + package com.google.firebase.vertexai.util import com.google.firebase.vertexai.GenerativeModel +import com.google.firebase.vertexai.ImagenModel import com.google.firebase.vertexai.common.APIController +import com.google.firebase.vertexai.type.PublicPreviewAPI import com.google.firebase.vertexai.type.RequestOptions import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.nulls.shouldNotBeNull @@ -57,7 +61,11 @@ internal suspend fun ByteChannel.send(bytes: ByteArray) { * @see commonTest * @see send */ -internal data class CommonTestScope(val channel: ByteChannel, val model: GenerativeModel) +internal data class CommonTestScope( + val channel: ByteChannel, + val model: GenerativeModel, + val imagenModel: ImagenModel, +) /** A test that runs under a [CommonTestScope]. */ internal typealias CommonTest = suspend CommonTestScope.() -> Unit @@ -104,7 +112,8 @@ internal fun commonTest( null, ) val model = GenerativeModel("cool-model-name", controller = apiController) - CommonTestScope(channel, model).block() + val imagenModel = ImagenModel("cooler-model-name", controller = apiController) + CommonTestScope(channel, model, imagenModel).block() } /** 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