diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 52c45abc18b..603719e8142 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -56,18 +56,9 @@ jobs: distribution: temurin cache: gradle - - name: Pull genai-common - if: matrix.module == ':firebase-vertexai' - run: | - git clone https://github.com/google-gemini/generative-ai-android.git - cd generative-ai-android - ./gradlew :common:updateVersion common:publishToMavenLocal - cd .. - - name: Clone mock responses if: matrix.module == ':firebase-vertexai' - run: | - firebase-vertexai/update_responses.sh + run: firebase-vertexai/update_responses.sh - name: Add google-services.json env: @@ -119,6 +110,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..df4159cd285 100644 --- a/.github/workflows/create_releases.yml +++ b/.github/workflows/create_releases.yml @@ -15,6 +15,9 @@ on: jobs: create-branches: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: @@ -25,6 +28,9 @@ jobs: create-pull-request: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - uses: actions/checkout@v4.1.1 with: @@ -40,7 +46,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..797f112fd61 --- /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: Verify "Gradle connectedCheck" step was successful + if: steps.connectedCheck.outcome != 'success' + run: | + echo 'Failing because the outcome of the "Gradle connectedCheck" step ("${{ steps.connectedCheck.outcome }}") was not successful' + 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 new file mode 100644 index 00000000000..c401f296b71 --- /dev/null +++ b/.github/workflows/dataconnect_demo_app.yml @@ -0,0 +1,169 @@ +name: Data Connect Demo App + +on: + workflow_dispatch: + inputs: + nodeVersion: + firebaseToolsVersion: + javaVersion: + gradleInfoLog: + type: boolean + pull_request: + paths: + - firebase-dataconnect/demo/** + - .github/workflows/dataconnect_demo_app.yml + schedule: + - cron: '0 11 * * *' # Run nightly at 11am UTC (3am Pacific, 6am Eastern) + +env: + FDC_NODE_VERSION: ${{ inputs.nodeVersion || '20' }} + FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '13.28.0' }} + FDC_JAVA_VERSION: ${{ inputs.javaVersion || '17' }} + FDC_FIREBASE_TOOLS_DIR: ${{ github.workspace }}/firebase-tools + FDC_FIREBASE_COMMAND: ${{ github.workspace }}/firebase-tools/node_modules/.bin/firebase + +defaults: + run: + shell: bash + working-directory: firebase-dataconnect/demo + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test: + continue-on-error: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + sparse-checkout: firebase-dataconnect/demo + + - name: Create Cache Key Files + run: | + echo "gmagjr2b9d" >github_actions_demo_test_cache_key.txt + echo "${{ env.FDC_FIREBASE_TOOLS_VERSION }}" >github_actions_demo_assemble_firebase_tools_version.txt + + - uses: actions/setup-node@v3 + with: + node-version: ${{ env.FDC_NODE_VERSION }} + cache: 'npm' + cache-dependency-path: | + firebase-dataconnect/demo/github_actions_demo_test_cache_key.txt + firebase-dataconnect/demo/github_actions_demo_assemble_firebase_tools_version.txt + + - name: cache package-lock.json + id: package_json_lock + uses: actions/cache@v4 + with: + path: ${{ env.FDC_FIREBASE_TOOLS_DIR }}/package*.json + key: firebase_tools_package_json-${{ env.FDC_FIREBASE_TOOLS_VERSION }} + + - name: install firebase-tools from scratch + if: steps.package_json_lock.outputs.cache-hit != 'true' + 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: install firebase-tools from package-lock.json + if: steps.package_json_lock.outputs.cache-hit == 'true' + run: | + cd ${{ env.FDC_FIREBASE_TOOLS_DIR }} + npm ci --fund=false --audit=false + + - uses: actions/setup-java@v4 + with: + java-version: ${{ env.FDC_JAVA_VERSION }} + distribution: temurin + cache: gradle + cache-dependency-path: | + firebase-dataconnect/demo/build.gradle.kts + firebase-dataconnect/demo/gradle.properties + firebase-dataconnect/demo/gradle/wrapper/gradle-wrapper.properties + firebase-dataconnect/demo/github_actions_demo_test_cache_key.txt + + - 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 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: | + set -x + ./gradlew \ + --no-daemon \ + ${{ (inputs.gradleInfoLog && '--info') || '' }} \ + --profile \ + -PdataConnect.minimalApp.firebaseCommand=${{ env.FDC_FIREBASE_COMMAND }} \ + assemble test + + - uses: actions/upload-artifact@v4 + with: + name: apks + path: firebase-dataconnect/demo/build/**/*.apk + if-no-files-found: warn + compression-level: 0 + + - uses: actions/upload-artifact@v4 + with: + name: gradle_build_reports + path: firebase-dataconnect/demo/build/reports/ + if-no-files-found: warn + compression-level: 9 + + spotlessCheck: + continue-on-error: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + sparse-checkout: firebase-dataconnect/demo + + - name: Create Cache Key Files + run: echo "h99ee4egfd" >github_actions_demo_spotless_cache_key.txt + + - uses: actions/setup-java@v4 + with: + java-version: ${{ env.FDC_JAVA_VERSION }} + distribution: temurin + cache: gradle + cache-dependency-path: | + firebase-dataconnect/demo/build.gradle.kts + firebase-dataconnect/demo/gradle.properties + firebase-dataconnect/demo/gradle/wrapper/gradle-wrapper.properties + firebase-dataconnect/demo/github_actions_demo_spotless_cache_key.txt + + - name: tool versions + continue-on-error: true + run: | + set +e -v + which java + java -version + which javac + javac -version + ./gradlew --version + + - name: ./gradlew spotlessCheck + run: | + set -x + ./gradlew \ + --no-daemon \ + ${{ (inputs.gradleInfoLog && '--info') || '' }} \ + spotlessCheck 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/metalava-semver-check.yml b/.github/workflows/metalava-semver-check.yml new file mode 100644 index 00000000000..df68a691234 --- /dev/null +++ b/.github/workflows/metalava-semver-check.yml @@ -0,0 +1,34 @@ +name: Metalava SemVer Check + +on: + pull_request: + +jobs: + semver-check: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Checkout main + uses: actions/checkout@v4.1.1 + with: + ref: ${{ github.base_ref }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4.1.0 + with: + java-version: 17 + distribution: temurin + cache: gradle + + - name: Copy previous api.txt files + run: ./gradlew copyApiTxtFile + + - name: Checkout PR + uses: actions/checkout@v4.1.1 + with: + ref: ${{ github.head_ref }} + clean: false + + - name: Run Metalava SemVer check + run: ./gradlew metalavaSemver 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 6db8b2c4b30..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@v36.0.10 - 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-testing/firebase-appcheck-debug-testing.gradle b/appcheck/firebase-appcheck-debug-testing/firebase-appcheck-debug-testing.gradle index 126df994df8..03624b20da0 100644 --- a/appcheck/firebase-appcheck-debug-testing/firebase-appcheck-debug-testing.gradle +++ b/appcheck/firebase-appcheck-debug-testing/firebase-appcheck-debug-testing.gradle @@ -50,30 +50,29 @@ android { } dependencies { - javadocClasspath 'com.google.auto.value:auto-value-annotations:1.6.6' + javadocClasspath libs.autovalue.annotations api project(':appcheck:firebase-appcheck') api project(':appcheck:firebase-appcheck-debug') api 'com.google.firebase:firebase-appcheck-interop:17.0.0' api 'com.google.firebase:firebase-common:21.0.0' - api 'com.google.firebase:firebase-common-ktx:21.0.0' api 'com.google.firebase:firebase-components:18.0.0' implementation libs.androidx.test.core - implementation 'com.google.android.gms:play-services-base:18.0.1' - implementation 'com.google.android.gms:play-services-tasks:18.0.1' + implementation libs.playservices.base + implementation libs.playservices.tasks testImplementation project(':appcheck:firebase-appcheck-playintegrity') testImplementation libs.androidx.test.core testImplementation libs.truth - testImplementation 'junit:junit:4.13-beta-2' - testImplementation 'org.mockito:mockito-core:2.25.0' + testImplementation libs.junit + testImplementation libs.mockito.core testImplementation libs.robolectric androidTestImplementation project(':firebase-storage') - androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation libs.androidx.test.runner androidTestImplementation libs.androidx.test.junit androidTestImplementation libs.truth - androidTestImplementation 'junit:junit:4.13-beta-2' - androidTestImplementation 'org.mockito:mockito-core:2.25.0' + androidTestImplementation libs.junit + androidTestImplementation libs.mockito.core } 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-debug/firebase-appcheck-debug.gradle b/appcheck/firebase-appcheck-debug/firebase-appcheck-debug.gradle index 6d59bd434dc..44a14355d03 100644 --- a/appcheck/firebase-appcheck-debug/firebase-appcheck-debug.gradle +++ b/appcheck/firebase-appcheck-debug/firebase-appcheck-debug.gradle @@ -47,25 +47,25 @@ android { } dependencies { - javadocClasspath 'com.google.auto.value:auto-value-annotations:1.6.6' + javadocClasspath libs.autovalue.annotations api project(':appcheck:firebase-appcheck') api 'com.google.firebase:firebase-annotations:16.2.0' api 'com.google.firebase:firebase-common:21.0.0' - api 'com.google.firebase:firebase-common-ktx:21.0.0' api 'com.google.firebase:firebase-components:18.0.0' + implementation platform(libs.kotlin.bom) - implementation 'com.google.android.gms:play-services-base:18.0.1' - implementation 'com.google.android.gms:play-services-tasks:18.0.1' + implementation libs.playservices.base + implementation libs.playservices.tasks testImplementation(project(":integ-testing")){ exclude group: 'com.google.firebase', module: 'firebase-common' exclude group: 'com.google.firebase', module: 'firebase-components' } testImplementation libs.androidx.test.core - testImplementation 'androidx.test:rules:1.2.0' + testImplementation libs.androidx.test.rules testImplementation libs.truth - testImplementation 'junit:junit:4.13-beta-2' - testImplementation 'org.mockito:mockito-core:2.25.0' + testImplementation libs.junit + testImplementation libs.mockito.core testImplementation libs.robolectric } 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-interop/firebase-appcheck-interop.gradle b/appcheck/firebase-appcheck-interop/firebase-appcheck-interop.gradle index 1f3fdec19df..5a86d4963bc 100644 --- a/appcheck/firebase-appcheck-interop/firebase-appcheck-interop.gradle +++ b/appcheck/firebase-appcheck-interop/firebase-appcheck-interop.gradle @@ -45,13 +45,13 @@ android { } dependencies { - implementation 'com.google.android.gms:play-services-base:18.0.1' - implementation 'com.google.android.gms:play-services-tasks:18.0.1' + implementation libs.playservices.base + implementation libs.playservices.tasks testImplementation libs.androidx.test.core - testImplementation 'androidx.test:rules:1.2.0' + testImplementation libs.androidx.test.rules testImplementation libs.truth - testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.25.0' + testImplementation libs.junit + testImplementation libs.mockito.core testImplementation libs.robolectric } 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-playintegrity/firebase-appcheck-playintegrity.gradle b/appcheck/firebase-appcheck-playintegrity/firebase-appcheck-playintegrity.gradle index 1ceec608bb7..975401af098 100644 --- a/appcheck/firebase-appcheck-playintegrity/firebase-appcheck-playintegrity.gradle +++ b/appcheck/firebase-appcheck-playintegrity/firebase-appcheck-playintegrity.gradle @@ -47,7 +47,7 @@ android { } dependencies { - javadocClasspath 'com.google.auto.value:auto-value-annotations:1.6.6' + javadocClasspath libs.autovalue.annotations api project(':appcheck:firebase-appcheck') api 'com.google.firebase:firebase-annotations:16.2.0' @@ -55,9 +55,9 @@ dependencies { api 'com.google.firebase:firebase-common-ktx:21.0.0' api 'com.google.firebase:firebase-components:18.0.0' - implementation 'com.google.android.gms:play-services-base:18.0.1' - implementation 'com.google.android.gms:play-services-tasks:18.0.1' - implementation 'com.google.android.play:integrity:1.2.0' + implementation libs.playservices.base + implementation libs.playservices.tasks + implementation libs.integrity testImplementation(project(":integ-testing")){ exclude group: 'com.google.firebase', module: 'firebase-common' @@ -66,6 +66,6 @@ dependencies { testImplementation libs.androidx.test.core testImplementation libs.truth testImplementation libs.junit - testImplementation 'org.mockito:mockito-core:3.4.6' + testImplementation libs.mockito.core testImplementation libs.robolectric } 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/firebase-appcheck.gradle b/appcheck/firebase-appcheck/firebase-appcheck.gradle index 7490398d00e..8bb131495f3 100644 --- a/appcheck/firebase-appcheck/firebase-appcheck.gradle +++ b/appcheck/firebase-appcheck/firebase-appcheck.gradle @@ -48,16 +48,15 @@ android { } dependencies { - javadocClasspath 'com.google.auto.value:auto-value-annotations:1.6.6' + javadocClasspath libs.autovalue.annotations api libs.playservices.tasks api 'com.google.firebase:firebase-annotations:16.2.0' api "com.google.firebase:firebase-appcheck-interop:17.1.0" api("com.google.firebase:firebase-common:21.0.0") - api("com.google.firebase:firebase-common-ktx:21.0.0") api("com.google.firebase:firebase-components:18.0.0") - implementation 'androidx.annotation:annotation:1.1.0' + implementation libs.androidx.annotation implementation libs.playservices.base implementation libs.kotlin.stdlib @@ -66,12 +65,12 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-components' } testImplementation libs.androidx.test.core - testImplementation 'androidx.test:rules:1.2.0' + testImplementation libs.androidx.test.rules testImplementation libs.truth - testImplementation 'junit:junit:4.12' - testImplementation 'junit:junit:4.13-beta-2' + testImplementation libs.junit + testImplementation libs.junit testImplementation libs.mockito.core - testImplementation 'org.mockito:mockito-inline:5.2.0' + testImplementation libs.mockito.mockito.inline testImplementation libs.robolectric androidTestImplementation project(':appcheck:firebase-appcheck') @@ -79,12 +78,12 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-common' exclude group: 'com.google.firebase', module: 'firebase-components' } - androidTestImplementation "androidx.annotation:annotation:1.0.0" + androidTestImplementation libs.androidx.annotation androidTestImplementation libs.androidx.test.core - androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation libs.androidx.test.runner androidTestImplementation libs.androidx.test.junit androidTestImplementation libs.truth - androidTestImplementation 'junit:junit:4.12' - androidTestImplementation 'org.mockito:mockito-core:2.25.0' - androidTestImplementation 'org.mockito:mockito-inline:2.25.0' + androidTestImplementation libs.junit + androidTestImplementation libs.mockito.core + androidTestImplementation libs.mockito.mockito.inline } 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/appcheck/firebase-appcheck/ktx/ktx.gradle b/appcheck/firebase-appcheck/ktx/ktx.gradle index 17bed8fdaee..ed498a84e28 100644 --- a/appcheck/firebase-appcheck/ktx/ktx.gradle +++ b/appcheck/firebase-appcheck/ktx/ktx.gradle @@ -61,13 +61,13 @@ dependencies { implementation("com.google.firebase:firebase-components:18.0.0") testImplementation libs.truth - testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.25.0' + testImplementation libs.junit + testImplementation libs.mockito.core testImplementation libs.robolectric androidTestImplementation libs.androidx.test.core - androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation libs.androidx.test.runner androidTestImplementation 'com.google.firebase:firebase-appcheck-interop:17.1.0' androidTestImplementation libs.truth - androidTestImplementation 'junit:junit:4.12' + androidTestImplementation libs.junit } diff --git a/build.gradle b/build.gradle deleted file mode 100644 index b53d1461e8e..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 'com.google.protobuf:protobuf-gradle-plugin:0.9.4' - classpath 'net.ltgt.gradle:gradle-errorprone-plugin:3.1.0' - classpath 'com.google.gms:google-services:4.3.15' - classpath 'com.google.firebase:firebase-appdistribution-gradle:5.0.0' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' - 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/README.md b/ci/README.md index d1546d1ddbb..a4b4eb4799b 100644 --- a/ci/README.md +++ b/ci/README.md @@ -4,7 +4,7 @@ This directory contains tooling used to run Continuous Integration tasks. ## Prerequisites -- Requires python3.5+ and setuptools to be installed. +- Requires python3.9+ and setuptools to be installed. ## Setup @@ -22,3 +22,26 @@ This directory contains tooling used to run Continuous Integration tasks. ``` fireci --help ``` + +## Uninstall + +If you run into any issues and need to re-install, or uninstall the package, you can do so +by uninstalling the `fireci` package. + +```shell +pip3 uninstall fireci -y +``` + +## Debug + +By default, if you're not running `fireci` within the context of CI, the minimum log level is set +to `INFO`. + +To manually set the level to `DEBUG`, you can use the `--debug` flag. + +```shell +fireci --debug clean +``` + +> ![NOTE] +> The `--debug` flag must come _before_ the command. 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/ci/fireci/fireci/ci_utils.py b/ci/fireci/fireci/ci_utils.py index 12ac98b93f6..1b9e0477e75 100644 --- a/ci/fireci/fireci/ci_utils.py +++ b/ci/fireci/fireci/ci_utils.py @@ -16,6 +16,8 @@ import os import subprocess +from typing import List, Tuple, Union + _logger = logging.getLogger('fireci.ci_utils') @@ -61,3 +63,28 @@ def gcloud_identity_token(): """Returns an identity token with the current gcloud service account.""" result = subprocess.run(['gcloud', 'auth', 'print-identity-token'], stdout=subprocess.PIPE, check=True) return result.stdout.decode('utf-8').strip() + +def get_projects(file_path: str = "subprojects.cfg") -> List[str]: + """Parses the specified file for a list of projects in the repo.""" + with open(file_path, 'r') as file: + stripped_lines = [line.strip() for line in file] + return [line for line in stripped_lines if line and not line.startswith('#')] + +def counts(arr: List[Union[bool, int]]) -> Tuple[int, int]: + """Given an array of booleans and ints, returns a tuple mapping of [true, false]. + Positive int values add to the `true` count while values less than one add to `false`. + """ + true_count = 0 + false_count = 0 + for value in arr: + if isinstance(value, bool): + if value: + true_count += 1 + else: + false_count += 1 + elif value >= 1: + true_count += value + else: + false_count += abs(value) if value < 0 else 1 + + return true_count, false_count diff --git a/ci/fireci/fireci/dir_utils.py b/ci/fireci/fireci/dir_utils.py index c5aea659d06..bb0dbb0fb44 100644 --- a/ci/fireci/fireci/dir_utils.py +++ b/ci/fireci/fireci/dir_utils.py @@ -15,6 +15,9 @@ import contextlib import logging import os +import pathlib +import shutil +import glob _logger = logging.getLogger('fireci.dir_utils') @@ -30,3 +33,27 @@ def chdir(directory): finally: _logger.debug(f'Restoring directory to: {original_dir} ...') os.chdir(original_dir) + +def rmdir(path: str) -> bool: + """Recursively deletes a directory, and returns a boolean indicating if the dir was deleted.""" + dir = pathlib.Path(path) + if not dir.exists(): + _logger.debug(f"Directory already deleted: {dir}") + return False + + _logger.debug(f"Deleting directory: {dir}") + shutil.rmtree(dir) + return True + +def rmglob(pattern: str) -> int: + """Deletes all files that match a given pattern, and returns the amount of (root) files deleted""" + files = glob.glob(os.path.expanduser(pattern)) + for file in files: + path = pathlib.Path(file) + if path.is_dir(): + rmdir(file) + else: + _logger.debug(f"Deleting file: {path}") + os.remove(path) + + return len(files) diff --git a/ci/fireci/fireci/internal.py b/ci/fireci/fireci/internal.py index 0950d770fc2..7078528c512 100644 --- a/ci/fireci/fireci/internal.py +++ b/ci/fireci/fireci/internal.py @@ -58,6 +58,11 @@ class _CommonOptions: @click.group() +@click.option( + '--debug/--no-debug', + help='Set the min loglevel to debug.', + default=False +) @click.option( '--artifact-target-dir', default='_artifacts', @@ -83,7 +88,7 @@ def main(options, **kwargs): setattr(options, k, v) -def ci_command(name=None, cls=click.Command, group=main): +def ci_command(name=None, cls=click.Command, group=main, epilog=None): """Decorator to use for CI commands. The differences from the standard @click.command are: @@ -94,15 +99,19 @@ def ci_command(name=None, cls=click.Command, group=main): :param name: Optional name of the task. Defaults to the function name that is decorated with this decorator. :param cls: Specifies whether the func is a command or a command group. Defaults to `click.Command`. :param group: Specifies the group the command belongs to. Defaults to the `main` command group. + :param epilog: Specifies epilog text to show at the end of the help text. """ def ci_command(f): actual_name = f.__name__ if name is None else name - @click.command(name=actual_name, cls=cls, help=f.__doc__) + @click.command(name=actual_name, cls=cls, help=f.__doc__, epilog=epilog) @_pass_options @click.pass_context def new_func(ctx, options, *args, **kwargs): + if options.debug: + logging.getLogger('fireci').setLevel(logging.DEBUG) + with _artifact_handler( options.artifact_target_dir, options.artifact_patterns, diff --git a/ci/fireci/fireci/main.py b/ci/fireci/fireci/main.py index 9348f69b02c..957cca3c3c8 100644 --- a/ci/fireci/fireci/main.py +++ b/ci/fireci/fireci/main.py @@ -20,14 +20,17 @@ from .internal import main # Unnecessary on CI as GitHub Actions provides them already. -asctime_place_holder = '' if os.getenv('CI') else '%(asctime)s ' +is_ci = os.getenv('CI') +asctime_place_holder = '' if is_ci else '%(asctime)s ' log_format = f'[%(levelname).1s] {asctime_place_holder}%(name)s: %(message)s' logging.basicConfig( datefmt='%Y-%m-%d %H:%M:%S %z %Z', format=log_format, level=logging.INFO, ) -logging.getLogger('fireci').setLevel(logging.DEBUG) + +level = logging.DEBUG if is_ci else logging.INFO +logging.getLogger('fireci').setLevel(level) plugins.discover() diff --git a/ci/fireci/fireciplugins/clean.py b/ci/fireci/fireciplugins/clean.py new file mode 100644 index 00000000000..9f2cd6af9a5 --- /dev/null +++ b/ci/fireci/fireciplugins/clean.py @@ -0,0 +1,118 @@ +# 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 click +import logging + +from fireci import ci_command +from fireci import ci_utils +from fireci import dir_utils +from typing import Tuple, List, Callable, Union +from termcolor import colored + +log = logging.getLogger('fireci.clean') + +@click.argument("projects", + nargs=-1, + type=click.Path(), + required=False +) +@click.option('--gradle/--no-gradle', default=False, help="Delete the local .gradle caches.") +@click.option('--build/--no-build', default=True, help="Delete the local build caches.") +@click.option('--transforms/--no-transforms', default=False, help="Delete the system-wide transforms cache.") +@click.option('--build-cache/--no-build-cache', default=False, help="Delete the system-wide build cache.") + +@click.option('--deep/--no-deep', default=False, help="Delete all of the system-wide files for gradle.") +@click.option('--cache/--no-cache', default=False, help="Delete all of the system-wide caches for gradle.") +@ci_command(epilog=""" + Clean a subset of projects: + + \b + $ fireci clean firebase-common + $ fireci clean firebase-common firebase-vertexai + + Clean all projects: + + $ fireci clean +""") +def clean(projects, gradle, build, transforms, build_cache, deep, cache): + """ + Delete files cached by gradle. + + Alternative to the standard `gradlew clean`, which runs outside the scope of gradle, + and provides deeper cache cleaning capabilities. + """ + if not projects: + log.debug("No projects specified, so we're defaulting to all projects.") + projects = ci_utils.get_projects() + + cache = cache or deep + gradle = gradle or cache + + cleaners = [] + + if build: + cleaners.append(delete_build) + if gradle: + cleaners.append(delete_gradle) + + results = [call_and_sum(projects, cleaner) for cleaner in cleaners] + local_count = tuple(map(sum, zip(*results))) + + cleaners = [] + + if deep: + cleaners.append(delete_deep) + elif cache: + cleaners.append(delete_cache) + else: + if transforms: + cleaners.append(delete_transforms) + if build_cache: + cleaners.append(delete_build_cache) + + results = [cleaner() for cleaner in cleaners] + system_count = ci_utils.counts(results) + + [deleted, skipped] = tuple(a + b for a, b in zip(local_count, system_count)) + + log.info(f""" + Clean results: + + {colored("Deleted:", None, attrs=["bold"])} {colored(deleted, "red")} + {colored("Already deleted:", None, attrs=["bold"])} {colored(skipped, "grey")} + """) + + +def call_and_sum(variables: List[str], func: Callable[[str], Union[bool, int]]) -> Tuple[int, int]: + results = list(map(lambda var: func(var), variables)) + return ci_utils.counts(results) + +def delete_build(dir: str) -> bool: + return dir_utils.rmdir(f"{dir}/build") + +def delete_gradle(dir: str) -> bool: + return dir_utils.rmdir(f"{dir}/.gradle") + +def delete_transforms() -> int: + return dir_utils.rmglob("~/.gradle/caches/transforms-*") + +def delete_build_cache() -> int: + return dir_utils.rmglob("~/.gradle/caches/build-cache-*") + +def delete_deep() -> bool: + return dir_utils.rmdir("~/.gradle") + +def delete_cache() -> bool: + return dir_utils.rmdir("~/.gradle/caches") diff --git a/ci/fireci/setup.cfg b/ci/fireci/setup.cfg index 466898d3cb6..7b49519871c 100644 --- a/ci/fireci/setup.cfg +++ b/ci/fireci/setup.cfg @@ -4,17 +4,18 @@ version = 0.1 [options] install_requires = - protobuf==3.19 - click==8.1.3 - google-cloud-storage==2.5.0 - mypy==0.991 - numpy==1.23.1 - pandas==1.5.1 - PyGithub==1.55 - pystache==0.6.0 - requests==2.23.0 - seaborn==0.12.1 - PyYAML==6.0.0 + protobuf==3.20.3 + click==8.1.7 + google-cloud-storage==2.18.2 + mypy==1.6.0 + numpy==1.24.4 + pandas==1.5.3 + PyGithub==1.58.2 + pystache==0.6.0 + requests==2.31.0 + seaborn==0.12.2 + PyYAML==6.0.1 + termcolor==2.4.0 [options.extras_require] test = 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-decoders-json/firebase-decoders-json.gradle b/encoders/firebase-decoders-json/firebase-decoders-json.gradle index 935106c2a97..60906181a28 100644 --- a/encoders/firebase-decoders-json/firebase-decoders-json.gradle +++ b/encoders/firebase-decoders-json/firebase-decoders-json.gradle @@ -45,13 +45,13 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.1.0' + implementation libs.androidx.annotation - testImplementation 'androidx.test:runner:1.2.0' + testImplementation libs.androidx.test.runner testImplementation libs.androidx.test.junit testImplementation libs.truth - testImplementation 'junit:junit:4.13-rc-1' - testImplementation 'org.mockito:mockito-core:2.25.0' + testImplementation libs.junit + testImplementation libs.mockito.core testImplementation libs.robolectric } 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-json/firebase-encoders-json.gradle b/encoders/firebase-encoders-json/firebase-encoders-json.gradle index 8b3f7ca9964..d054982b2dd 100644 --- a/encoders/firebase-encoders-json/firebase-encoders-json.gradle +++ b/encoders/firebase-encoders-json/firebase-encoders-json.gradle @@ -51,13 +51,13 @@ android { dependencies { api 'com.google.firebase:firebase-encoders:17.0.0' - implementation 'androidx.annotation:annotation:1.1.0' + implementation libs.androidx.annotation - testImplementation 'androidx.test:runner:1.3.0' + testImplementation libs.androidx.test.runner testImplementation libs.androidx.test.junit - testImplementation "com.google.truth:truth:1.0.1" - testImplementation 'junit:junit:4.13' - testImplementation 'org.mockito:mockito-core:3.3.3' + testImplementation libs.truth + testImplementation libs.junit + testImplementation libs.mockito.core testImplementation libs.robolectric } 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-reflective/firebase-encoders-reflective.gradle b/encoders/firebase-encoders-reflective/firebase-encoders-reflective.gradle index 96049927882..7a3f8355819 100644 --- a/encoders/firebase-encoders-reflective/firebase-encoders-reflective.gradle +++ b/encoders/firebase-encoders-reflective/firebase-encoders-reflective.gradle @@ -48,12 +48,12 @@ dependencies { api 'com.google.firebase:firebase-encoders:17.0.0' api 'com.google.firebase:firebase-encoders-json:18.0.0' - implementation 'androidx.annotation:annotation:1.1.0' + implementation libs.androidx.annotation - testImplementation 'androidx.test:runner:1.3.0' + testImplementation libs.androidx.test.runner testImplementation libs.androidx.test.junit - testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'junit:junit:4.13' - testImplementation 'org.mockito:mockito-core:3.3.3' + testImplementation libs.truth + testImplementation libs.junit + testImplementation libs.mockito.core testImplementation libs.robolectric } 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/encoders/firebase-encoders/firebase-encoders.gradle b/encoders/firebase-encoders/firebase-encoders.gradle index 96661eb1d08..da7594861c7 100644 --- a/encoders/firebase-encoders/firebase-encoders.gradle +++ b/encoders/firebase-encoders/firebase-encoders.gradle @@ -30,10 +30,10 @@ java { dependencies { - implementation 'androidx.annotation:annotation:1.1.0' + implementation libs.androidx.annotation testImplementation libs.truth - testImplementation 'junit:junit:4.13' + testImplementation libs.junit } tasks.withType(JavaCompile) { diff --git a/encoders/protoc-gen-firebase-encoders/tests/tests.gradle b/encoders/protoc-gen-firebase-encoders/tests/tests.gradle index eb5d531edf8..39fb6730679 100644 --- a/encoders/protoc-gen-firebase-encoders/tests/tests.gradle +++ b/encoders/protoc-gen-firebase-encoders/tests/tests.gradle @@ -52,6 +52,6 @@ dependencies { testImplementation project(":encoders:firebase-encoders") testImplementation project(":encoders:firebase-encoders-proto") testImplementation libs.protobuf.java - testImplementation "com.google.truth:truth:1.0.1" - testImplementation 'junit:junit:4.13.1' + testImplementation libs.truth + testImplementation libs.junit } 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-abt/firebase-abt.gradle b/firebase-abt/firebase-abt.gradle index d33a5a971ea..3743c3b9546 100644 --- a/firebase-abt/firebase-abt.gradle +++ b/firebase-abt/firebase-abt.gradle @@ -55,18 +55,18 @@ dependencies { api 'com.google.firebase:firebase-common:21.0.0' api 'com.google.firebase:firebase-components:18.0.0' - implementation 'com.google.android.gms:play-services-basement:18.1.0' + implementation libs.playservices.basement implementation ('com.google.firebase:firebase-measurement-connector:18.0.0') { exclude group: "com.google.firebase", module: "firebase-common" } implementation(libs.kotlin.stdlib.jdk8) - testImplementation 'androidx.test:runner:1.2.0' - testImplementation 'androidx.test.espresso:espresso-core:3.2.0' - testImplementation 'com.google.truth:truth:0.44' - testImplementation 'io.grpc:grpc-testing:1.12.0' - testImplementation 'junit:junit:4.13-beta-2' - testImplementation 'org.mockito:mockito-core:2.25.0' + testImplementation libs.androidx.test.runner + testImplementation libs.androidx.espresso.core + testImplementation libs.truth + testImplementation libs.grpc.testing + testImplementation libs.junit + testImplementation libs.mockito.core testImplementation libs.robolectric } 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/firebase-appdistribution-api.gradle b/firebase-appdistribution-api/firebase-appdistribution-api.gradle index 97687554f40..ed9f7d9f63c 100644 --- a/firebase-appdistribution-api/firebase-appdistribution-api.gradle +++ b/firebase-appdistribution-api/firebase-appdistribution-api.gradle @@ -52,29 +52,29 @@ android { } dependencies { - api 'com.google.android.gms:play-services-tasks:18.0.1' + api libs.playservices.tasks api("com.google.firebase:firebase-common:21.0.0") api("com.google.firebase:firebase-common-ktx:21.0.0") api("com.google.firebase:firebase-components:18.0.0") - implementation 'androidx.annotation:annotation:1.1.0' + implementation libs.androidx.annotation implementation libs.kotlin.stdlib - compileOnly 'com.google.auto.value:auto-value-annotations:1.6.5' + compileOnly libs.autovalue.annotations - annotationProcessor 'com.google.auto.value:auto-value:1.6.5' + annotationProcessor libs.autovalue testImplementation project(':firebase-appdistribution-api') testImplementation libs.androidx.test.core testImplementation libs.truth testImplementation libs.junit - testImplementation 'org.mockito:mockito-core:2.25.0' - testImplementation 'org.mockito:mockito-inline:3.4.0' + testImplementation libs.mockito.core + testImplementation libs.mockito.mockito.inline testImplementation libs.robolectric androidTestImplementation libs.androidx.test.core - androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation libs.androidx.test.runner androidTestImplementation libs.truth - androidTestImplementation 'junit:junit:4.12' - androidTestImplementation "org.mockito:mockito-android:3.4.0" + androidTestImplementation libs.junit + androidTestImplementation libs.mockito.android } 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/CHANGELOG.md b/firebase-appdistribution/CHANGELOG.md index 6aa5cbfad70..3bfc9628fd5 100644 --- a/firebase-appdistribution/CHANGELOG.md +++ b/firebase-appdistribution/CHANGELOG.md @@ -1,5 +1,5 @@ # Unreleased - +* [fixed] Added custom tab support for more browsers [#6692] # 16.0.0-beta14 * [changed] Internal improvements to testing on Android 14 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-appdistribution/firebase-appdistribution.gradle b/firebase-appdistribution/firebase-appdistribution.gradle index 298eb3db079..55a5a09884e 100644 --- a/firebase-appdistribution/firebase-appdistribution.gradle +++ b/firebase-appdistribution/firebase-appdistribution.gradle @@ -73,14 +73,14 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-components' } - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation "androidx.browser:browser:1.3.0" - implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation 'com.google.android.gms:play-services-tasks:18.0.1' + implementation libs.androidx.appcompat + implementation libs.androidx.browser + implementation libs.androidx.constraintlayout + implementation libs.playservices.tasks implementation libs.javax.inject implementation libs.kotlin.stdlib.jdk8 - compileOnly 'com.google.auto.value:auto-value-annotations:1.6.5' + compileOnly libs.autovalue.annotations runtimeOnly('com.google.firebase:firebase-installations:17.1.3') { exclude group: 'com.google.firebase', module: 'firebase-common' @@ -98,18 +98,18 @@ dependencies { testImplementation libs.androidx.test.core testImplementation libs.truth testImplementation libs.junit - testImplementation 'org.mockito:mockito-inline:5.2.0' + testImplementation libs.mockito.mockito.inline testImplementation libs.robolectric androidTestImplementation(project(":integ-testing")){ exclude group: 'com.google.firebase', module: 'firebase-common' exclude group: 'com.google.firebase', module: 'firebase-components' } - androidTestImplementation "androidx.annotation:annotation:1.0.0" - androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation libs.androidx.annotation + androidTestImplementation libs.androidx.test.runner androidTestImplementation libs.androidx.test.junit androidTestImplementation libs.truth - androidTestImplementation 'junit:junit:4.12' - androidTestImplementation 'org.mockito:mockito-core:2.25.0' - androidTestImplementation 'org.mockito:mockito-inline:2.25.0' + androidTestImplementation libs.junit + androidTestImplementation libs.mockito.core + androidTestImplementation libs.mockito.mockito.inline } diff --git a/firebase-appdistribution/src/main/AndroidManifest.xml b/firebase-appdistribution/src/main/AndroidManifest.xml index ef91581edc3..452fe856e6c 100644 --- a/firebase-appdistribution/src/main/AndroidManifest.xml +++ b/firebase-appdistribution/src/main/AndroidManifest.xml @@ -21,6 +21,12 @@ + + + + + + resolveInfos = - context.getPackageManager().queryIntentServices(customTabIntent, 0); - return resolveInfos != null && !resolveInfos.isEmpty(); + String packageName = CustomTabsClient.getPackageName(context, Collections.emptyList()); + return packageName != null; } } 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/bandwagoner/bandwagoner.gradle b/firebase-config/bandwagoner/bandwagoner.gradle index a1c18f5b098..12c0ac1d508 100644 --- a/firebase-config/bandwagoner/bandwagoner.gradle +++ b/firebase-config/bandwagoner/bandwagoner.gradle @@ -68,7 +68,7 @@ firebaseTestLab { } dependencies { - api 'com.google.auto.value:auto-value-annotations:1.6.5' + api libs.autovalue.annotations // Depend on development artifacts for Remote Config. implementation(project(":firebase-config")) { @@ -83,14 +83,14 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-common' exclude group: 'com.google.firebase', module: 'firebase-components' } - implementation 'androidx.annotation:annotation:1.1.0' - implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'androidx.core:core:1.0.2' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.test.espresso:espresso-idling-resource:3.2.0' - implementation 'com.google.android.gms:play-services-basement:18.1.0' - implementation 'com.google.android.gms:play-services-tasks:18.0.1' - implementation 'com.google.android.material:material:1.0.0' + implementation libs.androidx.annotation + implementation libs.androidx.appcompat + implementation libs.androidx.core + implementation libs.androidx.legacy.support.v4 + implementation libs.androidx.espresso.idling.resource + implementation libs.playservices.basement + implementation libs.playservices.tasks + implementation libs.material // This is required since a `project` dependency on frc does not expose the APIs of its // "implementation" dependencies. The alternative would be to make common an "api" dep of remote-config. // Released artifacts don't need these dependencies since they don't use `project` to refer @@ -104,12 +104,12 @@ dependencies { } // Support Libraries implementation 'com.google.guava:guava:28.1-android' - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4" + implementation libs.kotlinx.coroutines.core - annotationProcessor 'com.google.auto.value:auto-value:1.6.2' + annotationProcessor libs.autovalue - androidTestImplementation 'androidx.test:rules:1.2.0' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.2.0' + androidTestImplementation libs.androidx.test.rules + androidTestImplementation libs.androidx.test.runner + androidTestImplementation libs.androidx.espresso.core + androidTestImplementation libs.androidx.espresso.idling.resource } diff --git a/firebase-config/firebase-config.gradle.kts b/firebase-config/firebase-config.gradle.kts index 009c5950c09..a8024d79c77 100644 --- a/firebase-config/firebase-config.gradle.kts +++ b/firebase-config/firebase-config.gradle.kts @@ -78,13 +78,13 @@ dependencies { // Kotlin & Android implementation(libs.kotlin.stdlib) - implementation("androidx.annotation:annotation:1.1.0") - api("com.google.android.gms:play-services-tasks:18.0.1") + implementation(libs.androidx.annotation) + api(libs.playservices.tasks) // Annotations and static analysis - annotationProcessor("com.google.auto.value:auto-value:1.6.6") - javadocClasspath("com.google.auto.value:auto-value-annotations:1.6.6") - compileOnly("com.google.auto.value:auto-value-annotations:1.6.6") + annotationProcessor(libs.autovalue) + javadocClasspath(libs.autovalue.annotations) + compileOnly(libs.autovalue.annotations) compileOnly(libs.findbugs.jsr305) // Testing @@ -95,13 +95,13 @@ dependencies { testImplementation(libs.robolectric) testImplementation(libs.mockito.core) testImplementation(libs.truth) - testImplementation("org.skyscreamer:jsonassert:1.5.0") + testImplementation(libs.jsonassert) androidTestImplementation(libs.truth) androidTestImplementation(libs.junit) androidTestImplementation(libs.mockito.core) androidTestImplementation(libs.androidx.test.runner) - androidTestImplementation("org.skyscreamer:jsonassert:1.5.0") - androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1") - androidTestImplementation("com.linkedin.dexmaker:dexmaker:2.28.1") + androidTestImplementation(libs.jsonassert) + androidTestImplementation(libs.mockito.dexmaker) + androidTestImplementation(libs.dexmaker) } 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..abd09dd0330 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,31 @@ 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. See Custom + * Signal Limits. + * + * @param customSignals The custom signals to set for this instance. + *

    + *
  • New keys will add new key-value pairs in the custom signals. + *
  • Existing keys with new values will update the corresponding signals. + *
  • Setting a key's value to {@code null} will remove the associated signal. + *
+ */ + @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/CHANGELOG.md b/firebase-crashlytics-ndk/CHANGELOG.md index b5b8f7868d4..237ce272b14 100644 --- a/firebase-crashlytics-ndk/CHANGELOG.md +++ b/firebase-crashlytics-ndk/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +* [changed] Updated `firebase-crashlytics` dependency to v19.4.2 +# 19.4.1 +* [changed] Updated `firebase-crashlytics` dependency to v19.4.1 # 19.3.0 * [changed] Updated `firebase-crashlytics` dependency to v19.3.0 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/firebase-crashlytics-ndk.gradle b/firebase-crashlytics-ndk/firebase-crashlytics-ndk.gradle index 5d3bc636a9b..aafc02f489c 100644 --- a/firebase-crashlytics-ndk/firebase-crashlytics-ndk.gradle +++ b/firebase-crashlytics-ndk/firebase-crashlytics-ndk.gradle @@ -117,15 +117,15 @@ dependencies { implementation libs.playservices.basement - testImplementation 'androidx.test:runner:1.4.0' + testImplementation libs.androidx.test.runner testImplementation libs.junit - testImplementation 'org.mockito:mockito-core:3.4.3' + testImplementation libs.mockito.core testImplementation libs.robolectric androidTestImplementation libs.androidx.test.core - androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation libs.androidx.test.runner androidTestImplementation libs.protobuf.java.lite - androidTestImplementation 'com.linkedin.dexmaker:dexmaker:2.28.1' - androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.28.1' - androidTestImplementation 'org.mockito:mockito-core:3.4.3' + androidTestImplementation libs.dexmaker + androidTestImplementation libs.mockito.dexmaker + androidTestImplementation libs.mockito.core } diff --git a/firebase-crashlytics-ndk/gradle.properties b/firebase-crashlytics-ndk/gradle.properties index 345224dc4c7..a7ea562fe0f 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.2 +latestReleasedVersion=19.4.1 diff --git a/firebase-crashlytics/CHANGELOG.md b/firebase-crashlytics/CHANGELOG.md index 5b57a9581e7..95edf7e8500 100644 --- a/firebase-crashlytics/CHANGELOG.md +++ b/firebase-crashlytics/CHANGELOG.md @@ -1,6 +1,31 @@ # Unreleased +* [fixed] avoid android.os.strictmode.UnbufferedIoViolation on SessionReportingCoordinator [#6565] +* [changed] Internal changes to read version control info more efficiently [#6754] +* [fixed] Fixed NoSuchMethodError when getting process info on Android 13 on some devices [#6720] +* [changed] Updated `firebase-sessions` dependency to v2.1.0 + * [changed] Add warning for known issue [b/328687152](https://issuetracker.google.com/328687152) [#6755] + * [changed] Updated datastore dependency to v1.1.3 to fix [CVE-2024-7254](https://github.com/advisories/GHSA-735f-pc8j-v9w8) [#6688] +# 19.4.1 +* [changed] Updated `firebase-sessions` dependency to v2.0.9 + + +## Kotlin +The Kotlin extensions library transitively includes the updated +`firebase-crashlytics` library. The Kotlin extensions library has no additional +updates. + +# 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] @@ -314,10 +339,10 @@ updates. # 18.2.10 * [fixed] Fixed a bug that could prevent unhandled exceptions from being - propogated to the default handler when the network is unavailable. + propagated to the default handler when the network is unavailable. * [changed] Internal changes to support on-demand fatal crash reporting for Flutter apps. -* [fixed] Fixed a bug that prevented [crashlytics] from initalizing on some +* [fixed] Fixed a bug that prevented [crashlytics] from initializing on some devices in some cases. (#3269) diff --git a/firebase-crashlytics/api.txt b/firebase-crashlytics/api.txt index 3fb22e31b6e..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,49 +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 void log(@NonNull String); - method public void recordException(@NonNull Throwable); - method public void sendUnsentReports(); + method public static com.google.firebase.crashlytics.FirebaseCrashlytics getInstance(); method public boolean isCrashlyticsCollectionEnabled(); + 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 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 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); } } @@ -56,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/firebase-crashlytics.gradle b/firebase-crashlytics/firebase-crashlytics.gradle index be48261c760..63b9da33040 100644 --- a/firebase-crashlytics/firebase-crashlytics.gradle +++ b/firebase-crashlytics/firebase-crashlytics.gradle @@ -106,9 +106,9 @@ dependencies { androidTestImplementation("com.google.firebase:firebase-encoders-json:18.0.1") androidTestImplementation(libs.protobuf.java) androidTestImplementation(libs.truth) - androidTestImplementation("com.linkedin.dexmaker:dexmaker:2.28.3") + androidTestImplementation(libs.dexmaker) androidTestImplementation(libs.mockito.dexmaker) - androidTestImplementation("org.mockito:mockito-core:4.7.0") + androidTestImplementation(libs.mockito.core) androidTestImplementation(libs.androidx.test.junit) androidTestImplementation(libs.androidx.test.runner) androidTestImplementation(libs.truth) diff --git a/firebase-crashlytics/gradle.properties b/firebase-crashlytics/gradle.properties index 345224dc4c7..a7ea562fe0f 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.2 +latestReleasedVersion=19.4.1 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/androidTest/java/com/google/firebase/crashlytics/CrashlyticsTests.kt b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/CrashlyticsTests.kt index 9ac371797b3..64f8ba8bfa6 100644 --- a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/CrashlyticsTests.kt +++ b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/CrashlyticsTests.kt @@ -62,21 +62,36 @@ class CrashlyticsTests { @Test fun keyValueBuilder() { val keyValueBuilder = KeyValueBuilder() - keyValueBuilder.key("hello", "world") - keyValueBuilder.key("hello2", 23) - keyValueBuilder.key("hello3", 0.1) + keyValueBuilder.key("string", "world") + keyValueBuilder.key("int", Int.MAX_VALUE) + keyValueBuilder.key("float", Float.MAX_VALUE) + keyValueBuilder.key("boolean", true) + keyValueBuilder.key("double", Double.MAX_VALUE) + keyValueBuilder.key("long", Long.MAX_VALUE) val result: Map = keyValueBuilder.build().keysAndValues - assertThat(result).containsExactly("hello", "world", "hello2", "23", "hello3", "0.1") + val expectedKeys = + mapOf( + "string" to "world", + "int" to "${Int.MAX_VALUE}", + "float" to "${Float.MAX_VALUE}", + "boolean" to "${true}", + "double" to "${Double.MAX_VALUE}", + "long" to "${Long.MAX_VALUE}" + ) + assertThat(result).isEqualTo(expectedKeys) } @Test fun keyValueBuilder_withCrashlyticsInstance() { @Suppress("DEPRECATION") val keyValueBuilder = KeyValueBuilder(Firebase.crashlytics) - keyValueBuilder.key("hello", "world") - keyValueBuilder.key("hello2", 23) - keyValueBuilder.key("hello3", 0.1) + keyValueBuilder.key("string", "world") + keyValueBuilder.key("int", Int.MAX_VALUE) + keyValueBuilder.key("float", Float.MAX_VALUE) + keyValueBuilder.key("boolean", true) + keyValueBuilder.key("double", Double.MAX_VALUE) + keyValueBuilder.key("long", Long.MAX_VALUE) val result: Map = keyValueBuilder.build().keysAndValues diff --git a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java index 82cd14bcf74..9260a7f7950 100644 --- a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java +++ b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java @@ -44,6 +44,7 @@ import com.google.firebase.crashlytics.internal.NativeSessionFileProvider; import com.google.firebase.crashlytics.internal.analytics.AnalyticsEventLogger; import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorkers; +import com.google.firebase.crashlytics.internal.metadata.EventMetadata; import com.google.firebase.crashlytics.internal.metadata.LogFileManager; import com.google.firebase.crashlytics.internal.metadata.UserMetadata; import com.google.firebase.crashlytics.internal.model.CrashlyticsReport; @@ -57,6 +58,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.TreeSet; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -216,14 +218,14 @@ public void testWriteNonFatal_callsSessionReportingCoordinatorPersistNonFatal() when(mockSessionReportingCoordinator.listSortedOpenSessionIds()) .thenReturn(new TreeSet<>(Collections.singleton(sessionId))); - controller.writeNonFatalException(thread, nonFatal); + controller.writeNonFatalException(thread, nonFatal, Map.of()); crashlyticsWorkers.common.submit(() -> controller.doCloseSessions(testSettingsProvider)); crashlyticsWorkers.common.await(); crashlyticsWorkers.diskWrite.await(); verify(mockSessionReportingCoordinator) - .persistNonFatalEvent(eq(nonFatal), eq(thread), eq(sessionId), anyLong()); + .persistNonFatalEvent(eq(nonFatal), eq(thread), any(EventMetadata.class)); } @SdkSuppress(minSdkVersion = 30) // ApplicationExitInfo @@ -377,7 +379,7 @@ public void testLoggedExceptionsAfterCrashOk() { testSettingsProvider, Thread.currentThread(), new RuntimeException()); // This should not throw. - controller.writeNonFatalException(Thread.currentThread(), new RuntimeException()); + controller.writeNonFatalException(Thread.currentThread(), new RuntimeException(), Map.of()); } /** diff --git a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreTest.java b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreTest.java index 6bd0924f3d0..56ac7f825c5 100644 --- a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreTest.java +++ b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreTest.java @@ -159,6 +159,143 @@ public void testCustomAttributes() throws Exception { assertEquals(longValue, metadata.getCustomKeys().get(key1)); } + @Test + public void testCustomAttributes_retrievedWithEmptyEventKeys() throws Exception { + UserMetadata metadata = crashlyticsCore.getController().getUserMetadata(); + + assertTrue(metadata.getCustomKeys(Map.of()).isEmpty()); + + final String id = "id012345"; + crashlyticsCore.setUserId(id); + crashlyticsWorkers.common.await(); + assertEquals(id, metadata.getUserId()); + + final StringBuffer idBuffer = new StringBuffer(id); + while (idBuffer.length() < UserMetadata.MAX_ATTRIBUTE_SIZE) { + idBuffer.append("0"); + } + final String longId = idBuffer.toString(); + final String superLongId = longId + "more chars"; + + crashlyticsCore.setUserId(superLongId); + crashlyticsWorkers.common.await(); + assertEquals(longId, metadata.getUserId()); + + final String key1 = "key1"; + final String value1 = "value1"; + crashlyticsCore.setCustomKey(key1, value1); + crashlyticsWorkers.common.await(); + assertEquals(value1, metadata.getCustomKeys(Map.of()).get(key1)); + + // Adding an existing key with the same value should return false + assertFalse(metadata.setCustomKey(key1, value1)); + assertTrue(metadata.setCustomKey(key1, "someOtherValue")); + assertTrue(metadata.setCustomKey(key1, value1)); + assertFalse(metadata.setCustomKey(key1, value1)); + + final String longValue = longId.replaceAll("0", "x"); + final String superLongValue = longValue + "some more chars"; + + // test truncation of custom keys and attributes + crashlyticsCore.setCustomKey(superLongId, superLongValue); + crashlyticsWorkers.common.await(); + assertNull(metadata.getCustomKeys(Map.of()).get(superLongId)); + assertEquals(longValue, metadata.getCustomKeys().get(longId)); + + // test the max number of attributes. We've already set 2. + for (int i = 2; i < UserMetadata.MAX_ATTRIBUTES; ++i) { + final String key = "key" + i; + final String value = "value" + i; + crashlyticsCore.setCustomKey(key, value); + crashlyticsWorkers.common.await(); + assertEquals(value, metadata.getCustomKeys(Map.of()).get(key)); + } + // should be full now, extra key, value pairs will be dropped. + final String key = "new key"; + crashlyticsCore.setCustomKey(key, "some value"); + crashlyticsWorkers.common.await(); + assertFalse(metadata.getCustomKeys(Map.of()).containsKey(key)); + + // should be able to update existing keys + crashlyticsCore.setCustomKey(key1, longValue); + crashlyticsWorkers.common.await(); + assertEquals(longValue, metadata.getCustomKeys(Map.of()).get(key1)); + + // when we set a key to null, it should still exist with an empty value + crashlyticsCore.setCustomKey(key1, null); + crashlyticsWorkers.common.await(); + assertEquals("", metadata.getCustomKeys(Map.of()).get(key1)); + + // keys and values are trimmed. + crashlyticsCore.setCustomKey(" " + key1 + " ", " " + longValue + " "); + crashlyticsWorkers.common.await(); + assertTrue(metadata.getCustomKeys(Map.of()).containsKey(key1)); + assertEquals(longValue, metadata.getCustomKeys(Map.of()).get(key1)); + } + + @Test + public void testCustomKeysMergedWithEventKeys() throws Exception { + UserMetadata metadata = crashlyticsCore.getController().getUserMetadata(); + + Map keysAndValues = new HashMap<>(); + keysAndValues.put("customKey1", "value"); + keysAndValues.put("customKey2", "value"); + keysAndValues.put("customKey3", "value"); + + crashlyticsCore.setCustomKeys(keysAndValues); + crashlyticsWorkers.common.await(); + + Map eventKeysAndValues = new HashMap<>(); + eventKeysAndValues.put("eventKey1", "eventValue"); + eventKeysAndValues.put("eventKey2", "eventValue"); + + // Tests reading custom keys with event keys. + assertEquals(keysAndValues.size(), metadata.getCustomKeys().size()); + assertEquals(keysAndValues.size(), metadata.getCustomKeys(Map.of()).size()); + assertEquals( + keysAndValues.size() + eventKeysAndValues.size(), + metadata.getCustomKeys(eventKeysAndValues).size()); + + // Tests event keys don't add to custom keys in future reads. + assertEquals(keysAndValues.size(), metadata.getCustomKeys().size()); + assertEquals(keysAndValues.size(), metadata.getCustomKeys(Map.of()).size()); + + // Tests additional event keys. + eventKeysAndValues.put("eventKey3", "eventValue"); + eventKeysAndValues.put("eventKey4", "eventValue"); + assertEquals( + keysAndValues.size() + eventKeysAndValues.size(), + metadata.getCustomKeys(eventKeysAndValues).size()); + + // Tests overriding custom key with event keys. + keysAndValues.put("eventKey1", "value"); + crashlyticsCore.setCustomKeys(keysAndValues); + crashlyticsWorkers.common.await(); + + assertEquals("value", metadata.getCustomKeys().get("eventKey1")); + assertEquals("value", metadata.getCustomKeys(Map.of()).get("eventKey1")); + assertEquals("eventValue", metadata.getCustomKeys(eventKeysAndValues).get("eventKey1")); + + // Test the event key behavior when the count of custom keys is max. + for (int i = keysAndValues.size(); i < UserMetadata.MAX_ATTRIBUTES; ++i) { + final String key = "key" + i; + final String value = "value" + i; + crashlyticsCore.setCustomKey(key, value); + crashlyticsWorkers.common.await(); + assertEquals(value, metadata.getCustomKeys().get(key)); + } + + assertEquals(UserMetadata.MAX_ATTRIBUTES, metadata.getCustomKeys().size()); + + // Tests event keys override global custom keys when the key exists. + assertEquals("value", metadata.getCustomKeys().get("eventKey1")); + assertEquals("value", metadata.getCustomKeys(Map.of()).get("eventKey1")); + assertEquals("eventValue", metadata.getCustomKeys(eventKeysAndValues).get("eventKey1")); + + // Test when event keys *don't* override global custom keys. + assertNull(metadata.getCustomKeys(eventKeysAndValues).get("eventKey2")); + } + @Test public void testBulkCustomKeys() throws Exception { final double DELTA = 1e-15; diff --git a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinatorTest.java b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinatorTest.java index f1015447441..0f5cf09233b 100644 --- a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinatorTest.java +++ b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinatorTest.java @@ -34,12 +34,16 @@ import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Tasks; import com.google.firebase.concurrent.TestOnlyExecutors; +import com.google.firebase.crashlytics.internal.CrashlyticsTestCase; import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorkers; +import com.google.firebase.crashlytics.internal.metadata.EventMetadata; import com.google.firebase.crashlytics.internal.metadata.LogFileManager; +import com.google.firebase.crashlytics.internal.metadata.RolloutAssignment; import com.google.firebase.crashlytics.internal.metadata.UserMetadata; import com.google.firebase.crashlytics.internal.model.CrashlyticsReport; import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.CustomAttribute; import com.google.firebase.crashlytics.internal.persistence.CrashlyticsReportPersistence; +import com.google.firebase.crashlytics.internal.persistence.FileStore; import com.google.firebase.crashlytics.internal.send.DataTransportCrashlyticsReportSender; import java.io.File; import java.util.ArrayList; @@ -54,13 +58,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -public class SessionReportingCoordinatorTest { +public class SessionReportingCoordinatorTest extends CrashlyticsTestCase { + + private static final String TEST_SESSION_ID = "testSessionId"; @Mock private CrashlyticsReportDataCapture dataCapture; @Mock private CrashlyticsReportPersistence reportPersistence; @Mock private DataTransportCrashlyticsReportSender reportSender; @Mock private LogFileManager logFileManager; - @Mock private UserMetadata reportMetadata; @Mock private IdManager idManager; @Mock private CrashlyticsReport mockReport; @Mock private CrashlyticsReport.Session.Event mockEvent; @@ -70,6 +75,7 @@ public class SessionReportingCoordinatorTest { @Mock private Exception mockException; @Mock private Thread mockThread; + private UserMetadata reportMetadata; private SessionReportingCoordinator reportingCoordinator; private AutoCloseable mocks; @@ -80,6 +86,9 @@ public class SessionReportingCoordinatorTest { public void setUp() { mocks = MockitoAnnotations.openMocks(this); + FileStore testFileStore = new FileStore(getContext()); + reportMetadata = new UserMetadata(TEST_SESSION_ID, testFileStore, crashlyticsWorkers); + reportingCoordinator = new SessionReportingCoordinator( dataCapture, @@ -138,7 +147,8 @@ public void testNonFatalEvent_persistsNormalPriorityEventWithoutAllThreadsForSes mockEventInteractions(); reportingCoordinator.onBeginSession(sessionId, timestamp); - reportingCoordinator.persistNonFatalEvent(mockException, mockThread, sessionId, timestamp); + reportingCoordinator.persistNonFatalEvent( + mockException, mockThread, new EventMetadata(sessionId, timestamp)); crashlyticsWorkers.diskWrite.await(); @@ -163,7 +173,8 @@ public void testNonFatalEvent_addsLogsToEvent() throws Exception { when(logFileManager.getLogString()).thenReturn(testLog); reportingCoordinator.onBeginSession(sessionId, timestamp); - reportingCoordinator.persistNonFatalEvent(mockException, mockThread, sessionId, timestamp); + reportingCoordinator.persistNonFatalEvent( + mockException, mockThread, new EventMetadata(sessionId, timestamp)); crashlyticsWorkers.diskWrite.await(); @@ -184,7 +195,8 @@ public void testNonFatalEvent_addsNoLogsToEventWhenNoneAvailable() throws Except when(logFileManager.getLogString()).thenReturn(null); reportingCoordinator.onBeginSession(sessionId, timestamp); - reportingCoordinator.persistNonFatalEvent(mockException, mockThread, sessionId, timestamp); + reportingCoordinator.persistNonFatalEvent( + mockException, mockThread, new EventMetadata(sessionId, timestamp)); crashlyticsWorkers.diskWrite.await(); @@ -257,11 +269,12 @@ public void testNonFatalEvent_addsSortedKeysToEvent() throws Exception { expectedCustomAttributes.add(customAttribute1); expectedCustomAttributes.add(customAttribute2); - when(reportMetadata.getCustomKeys()).thenReturn(attributes); - when(reportMetadata.getInternalKeys()).thenReturn(attributes); + addCustomKeysToUserMetadata(attributes); + addInternalKeysToUserMetadata(attributes); reportingCoordinator.onBeginSession(sessionId, timestamp); - reportingCoordinator.persistNonFatalEvent(mockException, mockThread, sessionId, timestamp); + reportingCoordinator.persistNonFatalEvent( + mockException, mockThread, new EventMetadata(sessionId, timestamp)); crashlyticsWorkers.diskWrite.await(); @@ -281,12 +294,9 @@ public void testNonFatalEvent_addsNoKeysToEventWhenNoneAvailable() throws Except final String sessionId = "testSessionId"; - final Map attributes = Collections.emptyMap(); - - when(reportMetadata.getCustomKeys()).thenReturn(attributes); - reportingCoordinator.onBeginSession(sessionId, timestamp); - reportingCoordinator.persistNonFatalEvent(mockException, mockThread, sessionId, timestamp); + reportingCoordinator.persistNonFatalEvent( + mockException, mockThread, new EventMetadata(sessionId, timestamp)); crashlyticsWorkers.diskWrite.await(); @@ -297,33 +307,117 @@ public void testNonFatalEvent_addsNoKeysToEventWhenNoneAvailable() throws Except verify(logFileManager, never()).clearLog(); } + @Test + public void testNonFatalEvent_addsUserInfoKeysToEventWhenAvailable() throws Exception { + final long timestamp = System.currentTimeMillis(); + + mockEventInteractions(); + + final String sessionId = "testSessionId"; + + final String testKey1 = "testKey1"; + final String testValue1 = "testValue1"; + + final Map userInfo = new HashMap<>(); + userInfo.put(testKey1, testValue1); + + final CustomAttribute customAttribute1 = + CustomAttribute.builder().setKey(testKey1).setValue(testValue1).build(); + + final List expectedCustomAttributes = new ArrayList<>(); + expectedCustomAttributes.add(customAttribute1); + + reportingCoordinator.onBeginSession(sessionId, timestamp); + reportingCoordinator.persistNonFatalEvent( + mockException, mockThread, new EventMetadata(sessionId, timestamp, userInfo)); + + crashlyticsWorkers.diskWrite.await(); + + verify(mockEventAppBuilder).setCustomAttributes(expectedCustomAttributes); + verify(mockEventAppBuilder).build(); + verify(mockEventBuilder).setApp(mockEventApp); + verify(mockEventBuilder).build(); + verify(logFileManager, never()).clearLog(); + } + + @Test + public void testNonFatalEvent_mergesUserInfoKeysWithCustomKeys() throws Exception { + final long timestamp = System.currentTimeMillis(); + + mockEventInteractions(); + + final String sessionId = "testSessionId"; + + final String testKey1 = "testKey1"; + final String testValue1 = "testValue1"; + + final String testKey2 = "testKey2"; + final String testValue2 = "testValue2"; + + final Map attributes = new HashMap<>(); + attributes.put(testKey1, testValue1); + attributes.put(testKey2, testValue2); + + addCustomKeysToUserMetadata(attributes); + + final String testValue1UserInfo = "testValue1"; + final String testKey3 = "testKey3"; + final String testValue3 = "testValue3"; + + final Map userInfo = new HashMap<>(); + userInfo.put(testKey1, testValue1UserInfo); + userInfo.put(testKey3, testValue3); + + final CustomAttribute customAttribute1 = + CustomAttribute.builder().setKey(testKey1).setValue(testValue1UserInfo).build(); + final CustomAttribute customAttribute2 = + CustomAttribute.builder().setKey(testKey2).setValue(testValue2).build(); + final CustomAttribute customAttribute3 = + CustomAttribute.builder().setKey(testKey3).setValue(testValue3).build(); + + final List expectedCustomAttributes = + List.of(customAttribute1, customAttribute2, customAttribute3); + + reportingCoordinator.onBeginSession(sessionId, timestamp); + reportingCoordinator.persistNonFatalEvent( + mockException, mockThread, new EventMetadata(sessionId, timestamp, userInfo)); + + crashlyticsWorkers.diskWrite.await(); + + verify(mockEventAppBuilder).setCustomAttributes(expectedCustomAttributes); + verify(mockEventAppBuilder).build(); + verify(mockEventBuilder).setApp(mockEventApp); + verify(mockEventBuilder).build(); + verify(logFileManager, never()).clearLog(); + } + @Test public void testNonFatalEvent_addRolloutsEvent() throws Exception { long timestamp = System.currentTimeMillis(); String sessionId = "testSessionId"; mockEventInteractions(); - List rolloutsState = - new ArrayList(); - rolloutsState.add(mockRolloutAssignment()); - when(reportMetadata.getRolloutsState()).thenReturn(rolloutsState); + List rolloutsState = new ArrayList<>(); + rolloutsState.add(fakeRolloutAssignment()); + reportMetadata.updateRolloutsState(rolloutsState); + crashlyticsWorkers.diskWrite.await(); reportingCoordinator.onBeginSession(sessionId, timestamp); - reportingCoordinator.persistNonFatalEvent(mockException, mockThread, sessionId, timestamp); + reportingCoordinator.persistNonFatalEvent( + mockException, mockThread, new EventMetadata(sessionId, timestamp)); crashlyticsWorkers.diskWrite.await(); verify(mockEventAppBuilder, never()).setCustomAttributes(anyList()); verify(mockEventAppBuilder, never()).build(); verify(mockEventBuilder, never()).setApp(mockEventApp); - verify(reportMetadata).getRolloutsState(); // first build for custom keys // second build for rollouts verify(mockEventBuilder, times(2)).build(); } @Test - public void testFatalEvent_addsSortedCustomKeysToEvent() { + public void testFatalEvent_addsSortedCustomKeysToEvent() throws Exception { final long timestamp = System.currentTimeMillis(); mockEventInteractions(); @@ -348,7 +442,7 @@ public void testFatalEvent_addsSortedCustomKeysToEvent() { expectedCustomAttributes.add(customAttribute1); expectedCustomAttributes.add(customAttribute2); - when(reportMetadata.getCustomKeys()).thenReturn(attributes); + addCustomKeysToUserMetadata(attributes); reportingCoordinator.onBeginSession(sessionId, timestamp); reportingCoordinator.persistFatalEvent(mockException, mockThread, sessionId, timestamp); @@ -361,7 +455,7 @@ public void testFatalEvent_addsSortedCustomKeysToEvent() { } @Test - public void testFatalEvent_addsSortedInternalKeysToEvent() { + public void testFatalEvent_addsSortedInternalKeysToEvent() throws Exception { final long timestamp = System.currentTimeMillis(); mockEventInteractions(); @@ -386,7 +480,7 @@ public void testFatalEvent_addsSortedInternalKeysToEvent() { expectedCustomAttributes.add(customAttribute1); expectedCustomAttributes.add(customAttribute2); - when(reportMetadata.getInternalKeys()).thenReturn(attributes); + addInternalKeysToUserMetadata(attributes); reportingCoordinator.onBeginSession(sessionId, timestamp); reportingCoordinator.persistFatalEvent(mockException, mockThread, sessionId, timestamp); @@ -406,10 +500,6 @@ public void testFatalEvent_addsNoKeysToEventWhenNoneAvailable() { final String sessionId = "testSessionId"; - final Map attributes = Collections.emptyMap(); - - when(reportMetadata.getCustomKeys()).thenReturn(attributes); - reportingCoordinator.onBeginSession(sessionId, timestamp); reportingCoordinator.persistFatalEvent(mockException, mockThread, sessionId, timestamp); @@ -421,15 +511,16 @@ public void testFatalEvent_addsNoKeysToEventWhenNoneAvailable() { } @Test - public void testFatalEvent_addRolloutsToEvent() { + public void testFatalEvent_addRolloutsToEvent() throws Exception { long timestamp = System.currentTimeMillis(); String sessionId = "testSessionId"; mockEventInteractions(); - List rolloutsState = - new ArrayList(); - rolloutsState.add(mockRolloutAssignment()); - when(reportMetadata.getRolloutsState()).thenReturn(rolloutsState); + List rolloutsState = new ArrayList<>(); + rolloutsState.add(fakeRolloutAssignment()); + + reportMetadata.updateRolloutsState(rolloutsState); + crashlyticsWorkers.diskWrite.await(); reportingCoordinator.onBeginSession(sessionId, timestamp); reportingCoordinator.persistFatalEvent(mockException, mockThread, sessionId, timestamp); @@ -437,7 +528,6 @@ public void testFatalEvent_addRolloutsToEvent() { verify(mockEventAppBuilder, never()).setCustomAttributes(anyList()); verify(mockEventAppBuilder, never()).build(); verify(mockEventBuilder, never()).setApp(mockEventApp); - verify(reportMetadata).getRolloutsState(); // first build for custom keys // second build for rollouts verify(mockEventBuilder, times(2)).build(); @@ -522,6 +612,21 @@ public void testRemoveAllReports_deletesPersistedReports() { verify(reportPersistence).deleteAllReports(); } + private void addCustomKeysToUserMetadata(Map customKeys) throws Exception { + reportMetadata.setCustomKeys(customKeys); + for (Map.Entry entry : customKeys.entrySet()) { + reportMetadata.setInternalKey(entry.getKey(), entry.getValue()); + } + crashlyticsWorkers.diskWrite.await(); + } + + private void addInternalKeysToUserMetadata(Map internalKeys) throws Exception { + for (Map.Entry entry : internalKeys.entrySet()) { + reportMetadata.setInternalKey(entry.getKey(), entry.getValue()); + } + crashlyticsWorkers.diskWrite.await(); + } + private void mockEventInteractions() { when(mockEvent.toBuilder()).thenReturn(mockEventBuilder); when(mockEventBuilder.build()).thenReturn(mockEvent); @@ -556,16 +661,7 @@ private static CrashlyticsReportWithSessionId mockReportWithSessionId(String ses mockReport(sessionId), sessionId, new File("fake")); } - private static CrashlyticsReport.Session.Event.RolloutAssignment mockRolloutAssignment() { - return CrashlyticsReport.Session.Event.RolloutAssignment.builder() - .setTemplateVersion(2) - .setParameterKey("my_feature") - .setParameterValue("false") - .setRolloutVariant( - CrashlyticsReport.Session.Event.RolloutAssignment.RolloutVariant.builder() - .setRolloutId("rollout_1") - .setVariantId("enabled") - .build()) - .build(); + private static RolloutAssignment fakeRolloutAssignment() { + return RolloutAssignment.create("rollout_1", "my_feature", "false", "enabled", 2); } } diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.java index 1eb2e1a367b..46f992aaf12 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.java @@ -43,6 +43,7 @@ import com.google.firebase.remoteconfig.interop.FirebaseRemoteConfigInterop; import com.google.firebase.sessions.api.FirebaseSessionsDependencies; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; /** @@ -208,7 +209,31 @@ public void recordException(@NonNull Throwable throwable) { Logger.getLogger().w("A null value was passed to recordException. Ignoring."); return; } - core.logException(throwable); + + core.logException(throwable, Map.of()); + } + + /** + * Records a non-fatal report to send to Crashlytics. + * + *

Combined with app level custom keys, the event is restricted to a maximum of 64 key/value + * pairs. New keys beyond that limit are ignored. Keys or values that exceed 1024 characters are + * truncated. + * + *

The values of event keys override the values of app level custom keys if they're identical. + * + * @param throwable a {@link Throwable} to be recorded as a non-fatal event. + * @param keysAndValues A dictionary of keys and the values to associate with the non fatal + * exception, in addition to the app level custom keys. + */ + public void recordException( + @NonNull Throwable throwable, @NonNull CustomKeysAndValues keysAndValues) { + if (throwable == null) { // Users could call this with null despite the annotation. + Logger.getLogger().w("A null value was passed to recordException. Ignoring."); + return; + } + + core.logException(throwable, keysAndValues.keysAndValues); } /** diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.kt b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.kt index 3df07752caf..676d9877669 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.kt +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.kt @@ -30,6 +30,10 @@ val Firebase.crashlytics: FirebaseCrashlytics fun FirebaseCrashlytics.setCustomKeys(init: KeyValueBuilder.() -> Unit) = setCustomKeys(KeyValueBuilder().apply(init).build()) +/** Records a non-fatal report to send to Crashlytics with additional custom keys */ +fun FirebaseCrashlytics.recordException(throwable: Throwable, init: KeyValueBuilder.() -> Unit) = + recordException(throwable, KeyValueBuilder().apply(init).build()) + /** @suppress */ @Keep internal class FirebaseCrashlyticsKtxRegistrar : ComponentRegistrar { 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 6d8835f4934..74d3793e215 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 @@ -16,7 +16,7 @@ package com.google.firebase.crashlytics -/** Helper class to enable convenient syntax in [setCustomKeys] */ +/** Helper class to enable convenient syntax in [setCustomKeys] and [recordException] */ class KeyValueBuilder private constructor( private val crashlytics: FirebaseCrashlytics?, diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/ProcessDetailsProvider.kt b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/ProcessDetailsProvider.kt index 49fd2fafd18..172ebaaf477 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/ProcessDetailsProvider.kt +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/ProcessDetailsProvider.kt @@ -29,6 +29,8 @@ import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session. * @hide */ internal object ProcessDetailsProvider { + // TODO(mrober): Merge this with [com.google.firebase.sessions.ProcessDetailsProvider]. + /** Gets the details for all of this app's running processes. */ fun getAppProcessDetails(context: Context): List { val appUid = context.applicationInfo.uid @@ -70,7 +72,7 @@ internal object ProcessDetailsProvider { processName: String, pid: Int = 0, importance: Int = 0, - isDefaultProcess: Boolean = false + isDefaultProcess: Boolean = false, ) = ProcessDetails.builder() .setProcessName(processName) @@ -81,7 +83,7 @@ internal object ProcessDetailsProvider { /** Gets the app's current process name. If the API is not available, returns an empty string. */ private fun getProcessName(): String = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) { Process.myProcessName() } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { Application.getProcessName() ?: "" diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java index b29863f66c5..a116cf55542 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java @@ -69,6 +69,8 @@ public class CommonUtils { "com.google.firebase.crashlytics.build_ids_arch"; static final String BUILD_IDS_BUILD_ID_RESOURCE_NAME = "com.google.firebase.crashlytics.build_ids_build_id"; + static final String VERSION_CONTROL_INFO_RESOURCE_NAME = + "com.google.firebase.crashlytics.version_control_info"; // TODO: Maybe move this method into a more appropriate class. public static SharedPreferences getSharedPrefs(Context context) { @@ -525,6 +527,15 @@ public static List getBuildIdInfo(Context context) { return buildIdInfoList; } + @Nullable + public static String getVersionControlInfo(Context context) { + int id = getResourcesIdentifier(context, VERSION_CONTROL_INFO_RESOURCE_NAME, "string"); + if (id == 0) { + return null; + } + return context.getResources().getString(id); + } + public static void closeQuietly(Closeable closeable) { if (closeable != null) { try { diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java index 7726d9e6924..da28e8708db 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java @@ -35,6 +35,7 @@ import com.google.firebase.crashlytics.internal.analytics.AnalyticsEventLogger; import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsTasks; import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorkers; +import com.google.firebase.crashlytics.internal.metadata.EventMetadata; import com.google.firebase.crashlytics.internal.metadata.LogFileManager; import com.google.firebase.crashlytics.internal.metadata.UserMetadata; import com.google.firebase.crashlytics.internal.model.CrashlyticsReport; @@ -47,6 +48,7 @@ import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -76,6 +78,8 @@ class CrashlyticsController { private static final String VERSION_CONTROL_INFO_FILE = "version-control-info.textproto"; private static final String META_INF_FOLDER = "META-INF/"; + private static final Charset UTF_8 = Charset.forName("UTF-8"); + private final Context context; private final DataCollectionArbiter dataCollectionArbiter; private final CrashlyticsFileMarker crashMarker; @@ -405,7 +409,10 @@ void writeToLog(final long timestamp, final String msg) { } /** Log a caught exception - write out Throwable as event section of protobuf */ - void writeNonFatalException(@NonNull final Thread thread, @NonNull final Throwable ex) { + void writeNonFatalException( + @NonNull final Thread thread, + @NonNull final Throwable ex, + @NonNull Map eventKeys) { // Capture and close over the current time, so that we get the exact call time, // rather than the time at which the task executes. final long timestampMillis = System.currentTimeMillis(); @@ -417,7 +424,9 @@ void writeNonFatalException(@NonNull final Thread thread, @NonNull final Throwab Logger.getLogger().w("Tried to write a non-fatal exception while no session was open."); return; } - reportingCoordinator.persistNonFatalEvent(ex, thread, currentSessionId, timestampSeconds); + EventMetadata eventMetadata = + new EventMetadata(currentSessionId, timestampSeconds, eventKeys); + reportingCoordinator.persistNonFatalEvent(ex, thread, eventMetadata); } } @@ -622,13 +631,23 @@ void saveVersionControlInfo() { } String getVersionControlInfo() throws IOException { - InputStream is = getResourceAsStream(META_INF_FOLDER + VERSION_CONTROL_INFO_FILE); - if (is == null) { - return null; + // Attempt to read from an Android string resource + String versionControlInfo = CommonUtils.getVersionControlInfo(context); + if (versionControlInfo != null) { + Logger.getLogger().d("Read version control info from string resource"); + return Base64.encodeToString(versionControlInfo.getBytes(UTF_8), 0); + } + + // Fallback to reading the file + try (InputStream is = getResourceAsStream(META_INF_FOLDER + VERSION_CONTROL_INFO_FILE)) { + if (is != null) { + Logger.getLogger().d("Read version control info from file"); + return Base64.encodeToString(readResource(is), 0); + } } - Logger.getLogger().d("Read version control info"); - return Base64.encodeToString(readResource(is), 0); + Logger.getLogger().i("No version control information found"); + return null; } private InputStream getResourceAsStream(String resource) { @@ -638,13 +657,7 @@ private InputStream getResourceAsStream(String resource) { return null; } - InputStream is = classLoader.getResourceAsStream(resource); - if (is == null) { - Logger.getLogger().i("No version control information found"); - return null; - } - - return is; + return classLoader.getResourceAsStream(resource); } private static byte[] readResource(InputStream is) throws IOException { diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCore.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCore.java index d967447590e..f1f250e52b9 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCore.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCore.java @@ -313,9 +313,9 @@ public static String getVersion() { * Throwable was thrown. The Throwable will always be processed on a background thread, so it is * safe to invoke this method from the main thread. */ - public void logException(@NonNull Throwable throwable) { + public void logException(@NonNull Throwable throwable, @NonNull Map eventKeys) { crashlyticsWorkers.common.submit( - () -> controller.writeNonFatalException(Thread.currentThread(), throwable)); + () -> controller.writeNonFatalException(Thread.currentThread(), throwable, eventKeys)); } /** diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java index 29e2301591a..50533be05b1 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java @@ -25,6 +25,7 @@ import com.google.android.gms.tasks.Tasks; import com.google.firebase.crashlytics.internal.Logger; import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorkers; +import com.google.firebase.crashlytics.internal.metadata.EventMetadata; import com.google.firebase.crashlytics.internal.metadata.LogFileManager; import com.google.firebase.crashlytics.internal.metadata.UserMetadata; import com.google.firebase.crashlytics.internal.model.CrashlyticsReport; @@ -35,6 +36,7 @@ import com.google.firebase.crashlytics.internal.send.DataTransportCrashlyticsReportSender; import com.google.firebase.crashlytics.internal.settings.SettingsProvider; import com.google.firebase.crashlytics.internal.stacktrace.StackTraceTrimmingStrategy; +import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -124,13 +126,14 @@ public void onBeginSession(@NonNull String sessionId, long timestampSeconds) { public void persistFatalEvent( @NonNull Throwable event, @NonNull Thread thread, @NonNull String sessionId, long timestamp) { Logger.getLogger().v("Persisting fatal event for session " + sessionId); - persistEvent(event, thread, sessionId, EVENT_TYPE_CRASH, timestamp, true); + EventMetadata eventMetadata = new EventMetadata(sessionId, timestamp); + persistEvent(event, thread, EVENT_TYPE_CRASH, eventMetadata, true); } public void persistNonFatalEvent( - @NonNull Throwable event, @NonNull Thread thread, @NonNull String sessionId, long timestamp) { - Logger.getLogger().v("Persisting non-fatal event for session " + sessionId); - persistEvent(event, thread, sessionId, EVENT_TYPE_LOGGED, timestamp, false); + @NonNull Throwable event, @NonNull Thread thread, @NonNull EventMetadata eventMetadata) { + Logger.getLogger().v("Persisting non-fatal event for session " + eventMetadata.getSessionId()); + persistEvent(event, thread, EVENT_TYPE_LOGGED, eventMetadata, false); } @RequiresApi(api = Build.VERSION_CODES.R) @@ -253,23 +256,20 @@ private CrashlyticsReportWithSessionId ensureHasFid(CrashlyticsReportWithSession } private CrashlyticsReport.Session.Event addMetaDataToEvent( - CrashlyticsReport.Session.Event capturedEvent) { + CrashlyticsReport.Session.Event capturedEvent, Map eventCustomKeys) { CrashlyticsReport.Session.Event eventWithLogsAndCustomKeys = - addLogsAndCustomKeysToEvent(capturedEvent, logFileManager, reportMetadata); + addLogsCustomKeysAndEventKeysToEvent( + capturedEvent, logFileManager, reportMetadata, eventCustomKeys); CrashlyticsReport.Session.Event eventWithRollouts = addRolloutsStateToEvent(eventWithLogsAndCustomKeys, reportMetadata); return eventWithRollouts; } - private CrashlyticsReport.Session.Event addLogsAndCustomKeysToEvent( - CrashlyticsReport.Session.Event capturedEvent) { - return addLogsAndCustomKeysToEvent(capturedEvent, logFileManager, reportMetadata); - } - - private CrashlyticsReport.Session.Event addLogsAndCustomKeysToEvent( + private CrashlyticsReport.Session.Event addLogsCustomKeysAndEventKeysToEvent( CrashlyticsReport.Session.Event capturedEvent, LogFileManager logFileManager, - UserMetadata reportMetadata) { + UserMetadata reportMetadata, + Map eventKeys) { final CrashlyticsReport.Session.Event.Builder eventBuilder = capturedEvent.toBuilder(); final String content = logFileManager.getLogString(); @@ -284,7 +284,7 @@ private CrashlyticsReport.Session.Event addLogsAndCustomKeysToEvent( // logFileManager.clearLog(); // Clear log to prepare for next event. final List sortedCustomAttributes = - getSortedCustomAttributes(reportMetadata.getCustomKeys()); + getSortedCustomAttributes(reportMetadata.getCustomKeys(eventKeys)); final List sortedInternalKeys = getSortedCustomAttributes(reportMetadata.getInternalKeys()); @@ -299,6 +299,14 @@ private CrashlyticsReport.Session.Event addLogsAndCustomKeysToEvent( return eventBuilder.build(); } + private CrashlyticsReport.Session.Event addLogsAndCustomKeysToEvent( + CrashlyticsReport.Session.Event capturedEvent, + LogFileManager logFileManager, + UserMetadata reportMetadata) { + return addLogsCustomKeysAndEventKeysToEvent( + capturedEvent, logFileManager, reportMetadata, Map.of()); + } + private CrashlyticsReport.Session.Event addRolloutsStateToEvent( CrashlyticsReport.Session.Event capturedEvent, UserMetadata reportMetadata) { List reportRolloutAssignments = @@ -319,9 +327,8 @@ private CrashlyticsReport.Session.Event addRolloutsStateToEvent( private void persistEvent( @NonNull Throwable event, @NonNull Thread thread, - @NonNull String sessionId, @NonNull String eventType, - long timestamp, + @NonNull EventMetadata eventMetadata, boolean isFatal) { final boolean isHighPriority = eventType.equals(EVENT_TYPE_CRASH); @@ -331,23 +338,25 @@ private void persistEvent( event, thread, eventType, - timestamp, + eventMetadata.getTimestamp(), EVENT_THREAD_IMPORTANCE, MAX_CHAINED_EXCEPTION_DEPTH, isFatal); - CrashlyticsReport.Session.Event finallizedEvent = addMetaDataToEvent(capturedEvent); + CrashlyticsReport.Session.Event finallizedEvent = + addMetaDataToEvent(capturedEvent, eventMetadata.getAdditionalCustomKeys()); // Non-fatal, persistence write task we move to diskWriteWorker if (!isFatal) { crashlyticsWorkers.diskWrite.submit( () -> { Logger.getLogger().d("disk worker: log non-fatal event to persistence"); - reportPersistence.persistEvent(finallizedEvent, sessionId, isHighPriority); + reportPersistence.persistEvent( + finallizedEvent, eventMetadata.getSessionId(), isHighPriority); }); return; } - reportPersistence.persistEvent(finallizedEvent, sessionId, isHighPriority); + reportPersistence.persistEvent(finallizedEvent, eventMetadata.getSessionId(), isHighPriority); } private boolean onReportSendComplete(@NonNull Task task) { @@ -419,13 +428,15 @@ private static CrashlyticsReport.ApplicationExitInfo convertApplicationExitInfo( @VisibleForTesting @RequiresApi(api = Build.VERSION_CODES.KITKAT) public static String convertInputStreamToString(InputStream inputStream) throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byte[] bytes = new byte[DEFAULT_BUFFER_SIZE]; - int length; - while ((length = inputStream.read(bytes)) != -1) { - byteArrayOutputStream.write(bytes, 0, length); + try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + byte[] bytes = new byte[DEFAULT_BUFFER_SIZE]; + int length; + while ((length = bufferedInputStream.read(bytes)) != -1) { + byteArrayOutputStream.write(bytes, 0, length); + } + return byteArrayOutputStream.toString(StandardCharsets.UTF_8.name()); } - return byteArrayOutputStream.toString(StandardCharsets.UTF_8.name()); } /** Finds the first ANR ApplicationExitInfo within the session. */ diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/EventMetadata.kt b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/EventMetadata.kt new file mode 100644 index 00000000000..fef5899cef5 --- /dev/null +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/EventMetadata.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.crashlytics.internal.metadata + +/** + * A class that represents information to attach to a specific event. + * + * @property sessionId the sessionId to attach to the event. + * @property timestamp the timestamp to attach to the event. + * @property additionalCustomKeys a [Map] of key value pairs to attach to the event, + * in addition to the global custom keys. + */ +internal data class EventMetadata +@JvmOverloads +constructor( + val sessionId: String, + val timestamp: Long, + val additionalCustomKeys: Map = mapOf() +) diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/KeysMap.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/KeysMap.java index 620c7dca9d9..5d688b5e134 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/KeysMap.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/KeysMap.java @@ -47,11 +47,11 @@ public synchronized boolean setKey(String key, String value) { String sanitizedKey = sanitizeKey(key); // The entry can be added if we're under the size limit or we're updating an existing entry if (keys.size() < maxEntries || keys.containsKey(sanitizedKey)) { - String santitizedAttribute = sanitizeString(value, maxEntryLength); - if (CommonUtils.nullSafeEquals(keys.get(sanitizedKey), santitizedAttribute)) { + String sanitizedAttribute = sanitizeString(value, maxEntryLength); + if (CommonUtils.nullSafeEquals(keys.get(sanitizedKey), sanitizedAttribute)) { return false; } - keys.put(sanitizedKey, value == null ? "" : santitizedAttribute); + keys.put(sanitizedKey, value == null ? "" : sanitizedAttribute); return true; } Logger.getLogger() diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/UserMetadata.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/UserMetadata.java index 36ca7fda8a5..ec7403078d7 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/UserMetadata.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/UserMetadata.java @@ -17,10 +17,13 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.firebase.crashlytics.internal.Logger; import com.google.firebase.crashlytics.internal.common.CommonUtils; import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorkers; import com.google.firebase.crashlytics.internal.model.CrashlyticsReport; import com.google.firebase.crashlytics.internal.persistence.FileStore; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicMarkableReference; @@ -135,6 +138,47 @@ public void setUserId(String identifier) { crashlyticsWorkers.diskWrite.submit(this::serializeUserDataIfNeeded); } + /** + * Returns a {@link Map} containing all the custom keys to attach to the event. + * It overrides the values of app level custom keys with the values of event level custom keys if + * they're identical, and event keys or values that exceed 1024 characters are truncated. + * Combined with app level custom keys, the map is restricted to 64 key value pairs. + * + * @param eventKeys a {@link Map} representing event specific keys. + * @return a {@link Map} containing all the custom keys to attach to the event. + */ + public Map getCustomKeys(Map eventKeys) { + // In case of empty event keys, preserve existing behavior. + if (eventKeys.isEmpty()) { + return customKeys.getKeys(); + } + + // Otherwise merge the event keys with custom keys as appropriate. + Map globalKeys = customKeys.getKeys(); + HashMap result = new HashMap<>(globalKeys); + int eventKeysOverLimit = 0; + for (Map.Entry entry : eventKeys.entrySet()) { + String sanitizedKey = KeysMap.sanitizeString(entry.getKey(), MAX_ATTRIBUTE_SIZE); + if (result.size() < MAX_ATTRIBUTES || result.containsKey(sanitizedKey)) { + String sanitizedValue = KeysMap.sanitizeString(entry.getValue(), MAX_ATTRIBUTE_SIZE); + result.put(sanitizedKey, sanitizedValue); + } else { + eventKeysOverLimit++; + } + } + + if (eventKeysOverLimit > 0) { + Logger.getLogger() + .w( + "Ignored " + + eventKeysOverLimit + + " keys when adding event specific keys. Maximum allowable: " + + MAX_ATTRIBUTE_SIZE); + } + + return Collections.unmodifiableMap(result); + } + /** * @return defensive copy of the custom keys. */ 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/firebase-database.gradle.kts b/firebase-database/firebase-database.gradle.kts index e8a7a724080..038992487b8 100644 --- a/firebase-database/firebase-database.gradle.kts +++ b/firebase-database/firebase-database.gradle.kts @@ -80,8 +80,8 @@ dependencies { implementation(libs.kotlinx.coroutines.core) api(libs.playservices.tasks) - testImplementation("com.fasterxml.jackson.core:jackson-core:2.13.1") - testImplementation("com.fasterxml.jackson.core:jackson-databind:2.13.1") + testImplementation(libs.jackson.core) + testImplementation(libs.jackson.databind) testImplementation("com.firebase:firebase-token-generator:2.0.0") testImplementation(libs.androidx.test.core) testImplementation(libs.androidx.test.rules) @@ -91,10 +91,10 @@ dependencies { testImplementation(libs.robolectric) testImplementation(libs.truth) - androidTestImplementation("com.fasterxml.jackson.core:jackson-core:2.13.1") - androidTestImplementation("com.fasterxml.jackson.core:jackson-databind:2.13.1") - androidTestImplementation("org.hamcrest:hamcrest:2.2") - androidTestImplementation("org.hamcrest:hamcrest-library:2.2") + androidTestImplementation(libs.jackson.core) + androidTestImplementation(libs.jackson.databind) + androidTestImplementation(libs.hamcrest) + androidTestImplementation(libs.hamcrest.library) androidTestImplementation(libs.androidx.test.junit) androidTestImplementation(libs.androidx.test.runner) androidTestImplementation(libs.junit) 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 0bf345d2772..b21ac994950 100644 --- a/firebase-dataconnect/CHANGELOG.md +++ b/firebase-dataconnect/CHANGELOG.md @@ -1,5 +1,12 @@ # Unreleased +* [changed] Changed gRPC proto package to v1 (was v1beta). + ([#6729](https://github.com/firebase/firebase-android-sdk/pull/6729)) +# 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. + ([#6586](https://github.com/firebase/firebase-android-sdk/pull/6586)) # 16.0.0-beta03 * [changed] Requires Data Connect emulator version 1.6.1 or later for code generation. diff --git a/firebase-dataconnect/api.txt b/firebase-dataconnect/api.txt index f24ccc3c298..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,34 +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 com.google.firebase.dataconnect.LogLevel getLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion); - method public static void setLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion, @NonNull com.google.firebase.dataconnect.LogLevel); + 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 { @@ -127,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(); @@ -218,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; } } @@ -255,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); } } @@ -303,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/demo/.gitignore b/firebase-dataconnect/demo/.gitignore new file mode 100644 index 00000000000..d5ec1ef7546 --- /dev/null +++ b/firebase-dataconnect/demo/.gitignore @@ -0,0 +1,23 @@ +# 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. + +.gradle/ +.idea/ +.kotlin/ + +build/ +local.properties + +*.log +*.hprof diff --git a/firebase-dataconnect/demo/.idea/runConfigurations/spotlessApply.xml b/firebase-dataconnect/demo/.idea/runConfigurations/spotlessApply.xml new file mode 100644 index 00000000000..93b3583a85c --- /dev/null +++ b/firebase-dataconnect/demo/.idea/runConfigurations/spotlessApply.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + diff --git a/firebase-dataconnect/demo/build.gradle.kts b/firebase-dataconnect/demo/build.gradle.kts new file mode 100644 index 00000000000..add5ebb090a --- /dev/null +++ b/firebase-dataconnect/demo/build.gradle.kts @@ -0,0 +1,316 @@ +/* + * 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.android.build.api.variant.ApplicationAndroidComponentsExtension +import java.nio.charset.StandardCharsets + +plugins { + // Use whichever versions of these dependencies suit your application. + // The versions shown here were the latest versions as of March 05, 2025. + // Note, however, that the version of kotlin("plugin.serialization") _must_, + // in general, match the version of kotlin("android"). + id("com.android.application") version "8.9.0" + id("com.google.gms.google-services") version "4.4.2" + val kotlinVersion = "2.1.10" + kotlin("android") version kotlinVersion + kotlin("plugin.serialization") version kotlinVersion + + // The following code in this "plugins" block can be omitted from customer + // facing documentation as it is an implementation detail of this application. + id("com.diffplug.spotless") version "7.0.0.BETA4" +} + +dependencies { + // Use whichever versions of these dependencies suit your application. + // The versions shown here were the latest versions as of March 05, 2025. + implementation("com.google.firebase:firebase-dataconnect:16.0.0-beta04") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.8.0") + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("androidx.activity:activity-ktx:1.10.1") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7") + implementation("com.google.android.material:material:1.12.0") + + // The following code in this "dependencies" block can be omitted from customer + // facing documentation as it is an implementation detail of this application. + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") + implementation("io.kotest:kotest-property:5.9.1") + implementation("io.kotest.extensions:kotest-property-arbs:2.1.2") +} + +// The remaining code in this file can be omitted from customer facing +// documentation. It's here just to make things compile and/or configure +// optional components of the build (e.g. spotless code formatting). + +android { + namespace = "com.google.firebase.dataconnect.minimaldemo" + compileSdk = 35 + defaultConfig { + minSdk = 21 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = true + } + buildFeatures.viewBinding = true + kotlinOptions.jvmTarget = "1.8" +} + +spotless { + val ktfmtVersion = "0.53" + kotlin { + target("**/*.kt") + targetExclude("build/") + ktfmt(ktfmtVersion).googleStyle() + } + kotlinGradle { + target("**/*.gradle.kts") + targetExclude("build/") + ktfmt(ktfmtVersion).googleStyle() + } + json { + target("**/*.json") + targetExclude("build/") + simple().indentWithSpaces(2) + } + yaml { + target("**/*.yaml") + targetExclude("build/") + jackson() + .yamlFeature("INDENT_ARRAYS", true) + .yamlFeature("MINIMIZE_QUOTES", true) + .yamlFeature("WRITE_DOC_START_MARKER", false) + } + format("xml") { + target("**/*.xml") + targetExclude("build/") + trimTrailingWhitespace() + indentWithSpaces(2) + endWithNewline() + } +} + +@CacheableTask +abstract class DataConnectGenerateSourcesTask : DefaultTask() { + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val inputDirectory: Property + + @get:Input abstract val firebaseToolsVersion: Property + + @get:Input abstract val firebaseCommand: Property + + @get:Input @get:Optional abstract val nodeExecutableDirectory: Property + + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + + @get:Internal abstract val workDirectory: DirectoryProperty + + @get:Inject protected abstract val execOperations: ExecOperations + + @get:Inject protected abstract val providerFactory: ProviderFactory + + @get:Inject protected abstract val fileSystemOperations: FileSystemOperations + + @TaskAction + fun run() { + val inputDirectory: File = inputDirectory.get().dir + val firebaseToolsVersion: String = firebaseToolsVersion.get() + val firebaseCommand: String = firebaseCommand.get() + val nodeExecutableDirectory: String? = nodeExecutableDirectory.orNull + val outputDirectory: File = outputDirectory.get().asFile + val workDirectory: File = workDirectory.get().asFile + + logger.info("inputDirectory: {}", inputDirectory.absolutePath) + logger.info("firebaseToolsVersion: {}", firebaseToolsVersion) + logger.info("firebaseCommand: {}", firebaseCommand) + logger.info("nodeExecutableDirectory: {}", nodeExecutableDirectory) + logger.info("outputDirectory: {}", outputDirectory.absolutePath) + logger.info("workDirectory: {}", workDirectory.absolutePath) + + outputDirectory.deleteRecursively() + outputDirectory.mkdirs() + workDirectory.deleteRecursively() + workDirectory.mkdirs() + + val logFile = + if (logger.isInfoEnabled) { + null + } else { + File(workDirectory, "dataconnect.sdk.generate.log.txt") + } + + val execResult = + logFile?.outputStream().use { logStream -> + execOperations.runCatching { + exec { + configureFirebaseCommand( + this, + firebaseCommand = firebaseCommand, + nodeExecutableDirectory = nodeExecutableDirectory, + path = providerFactory.environmentVariable("PATH").orNull, + ) + args("--debug", "dataconnect:sdk:generate") + workingDir(inputDirectory) + isIgnoreExitValue = false + if (logStream !== null) { + standardOutput = logStream + errorOutput = logStream + } + } + } + } + + execResult.onFailure { exception -> + logFile?.readText(StandardCharsets.UTF_8)?.lines()?.forEach { line -> + logger.warn("{}", line.trimEnd()) + } + throw exception + } + } + + companion object { + + fun configureFirebaseCommand( + execSpec: ExecSpec, + firebaseCommand: String, + nodeExecutableDirectory: String?, + path: String?, + ) { + execSpec.setCommandLine(firebaseCommand) + + val newPath: String? = + if (nodeExecutableDirectory === null) { + null + } else { + if (path === null) { + nodeExecutableDirectory + } else { + nodeExecutableDirectory + File.pathSeparator + path + } + } + + if (newPath !== null) { + execSpec.environment("PATH", newPath) + } + } + } +} + +@DisableCachingByDefault( + because = "Copying files is not worth caching, just like org.gradle.api.tasks.Copy" +) +abstract class CopyDirectoryTask : DefaultTask() { + + @get:InputDirectory abstract val srcDirectory: Property + + @get:OutputDirectory abstract val destDirectory: DirectoryProperty + + @get:Inject protected abstract val fileSystemOperations: FileSystemOperations + + @TaskAction + fun run() { + val srcDirectoryTree: DirectoryTree = srcDirectory.get() + val destDirectory: File = destDirectory.get().asFile + + logger.info("srcDirectory: {}", srcDirectoryTree.dir.absolutePath) + logger.info("destDirectory: {}", destDirectory.absolutePath) + + destDirectory.deleteRecursively() + destDirectory.mkdirs() + + fileSystemOperations.copy { + from(srcDirectoryTree) + into(destDirectory) + } + } +} + +run { + val dataConnectTaskGroupName = "Firebase Data Connect Minimal App" + val projectDirectory = layout.projectDirectory + + val generateSourcesTask = + tasks.register("dataConnectGenerateSources") { + group = dataConnectTaskGroupName + description = + "Run firebase dataconnect:sdk:generate to generate the Data Connect Kotlin SDK sources" + + inputDirectory = + fileTree(layout.projectDirectory.dir("firebase")).apply { exclude("**/*.log") } + + outputDirectory = layout.buildDirectory.dir("dataConnect/generatedSources") + + firebaseCommand = + project.providers + .gradleProperty("dataConnect.minimalApp.firebaseCommand") + .orElse("firebase") + + nodeExecutableDirectory = + project.providers.gradleProperty("dataConnect.minimalApp.nodeExecutableDirectory").map { + projectDirectory.dir(it).asFile.absolutePath + } + + val path = providers.environmentVariable("PATH") + firebaseToolsVersion = + providers + .exec { + DataConnectGenerateSourcesTask.configureFirebaseCommand( + this, + firebaseCommand = firebaseCommand.get(), + nodeExecutableDirectory = nodeExecutableDirectory.orNull, + path = path.orNull, + ) + args("--version") + } + .standardOutput + .asText + .map { it.trim() } + + workDirectory = layout.buildDirectory.dir(name) + } + + val androidComponents = extensions.getByType() + androidComponents.onVariants { variant -> + val variantNameTitleCase = variant.name[0].uppercase() + variant.name.substring(1) + val copyTaskName = "dataConnectCopy${variantNameTitleCase}GeneratedSources" + val objectFactory = objects + val copyTask = + tasks.register(copyTaskName) { + group = dataConnectTaskGroupName + description = + "Copy the generated Data Connect Kotlin SDK sources into the " + + "generated code directory for the \"${variant.name}\" variant." + srcDirectory = + generateSourcesTask.flatMap { + it.outputDirectory.map { outputDirectory -> + objectFactory.fileTree().apply { + setDir(outputDirectory) + exclude("**/*.log") + } + } + } + } + + variant.sources.java!!.addGeneratedSourceDirectory(copyTask, CopyDirectoryTask::destDirectory) + } +} diff --git a/firebase-dataconnect/demo/firebase/.firebaserc b/firebase-dataconnect/demo/firebase/.firebaserc new file mode 100644 index 00000000000..77b37b80ab8 --- /dev/null +++ b/firebase-dataconnect/demo/firebase/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "fakeproject" + } +} diff --git a/firebase-dataconnect/demo/firebase/dataconnect/.gitignore b/firebase-dataconnect/demo/firebase/dataconnect/.gitignore new file mode 100644 index 00000000000..e0a389a7884 --- /dev/null +++ b/firebase-dataconnect/demo/firebase/dataconnect/.gitignore @@ -0,0 +1,15 @@ +# 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. + +/.dataconnect/ diff --git a/firebase-dataconnect/demo/firebase/dataconnect/connector/connector.yaml b/firebase-dataconnect/demo/firebase/dataconnect/connector/connector.yaml new file mode 100644 index 00000000000..8dca87919b4 --- /dev/null +++ b/firebase-dataconnect/demo/firebase/dataconnect/connector/connector.yaml @@ -0,0 +1,5 @@ +connectorId: ctry3q3tp6kzx +generate: + kotlinSdk: + outputDir: ../../../build/dataConnect/generatedSources + package: com.google.firebase.dataconnect.minimaldemo.connector diff --git a/firebase-dataconnect/demo/firebase/dataconnect/connector/operations.gql b/firebase-dataconnect/demo/firebase/dataconnect/connector/operations.gql new file mode 100644 index 00000000000..31dda056ed3 --- /dev/null +++ b/firebase-dataconnect/demo/firebase/dataconnect/connector/operations.gql @@ -0,0 +1,84 @@ +# 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. + +mutation InsertItem( + $string: String, + $int: Int, + $int64: Int64, + $float: Float, + $boolean: Boolean, + $date: Date, + $timestamp: Timestamp, + $any: Any, +) @auth(level: PUBLIC) { + key: zwda6x9zyy_insert(data: { + string: $string, + int: $int, + int64: $int64, + float: $float, + boolean: $boolean, + date: $date, + timestamp: $timestamp, + any: $any, + }) +} + +query GetItemByKey( + $key: zwda6x9zyy_Key! +) @auth(level: PUBLIC) { + item: zwda6x9zyy(key: $key) { + string + int + int64 + float + boolean + date + timestamp + any + } +} + +query GetAllItems @auth(level: PUBLIC) { + items: zwda6x9zyys { + id + string + int + int64 + float + boolean + date + timestamp + any + } +} + +mutation DeleteItemByKey( + $key: zwda6x9zyy_Key! +) @auth(level: PUBLIC) { + zwda6x9zyy_delete(key: $key) +} + +# This mutation exists only as a workaround for b/382688278 where the following +# compiler error will otherwise result when compiling the generated code: +# Serializer has not been found for type 'java.util.UUID'. To use context +# serializer as fallback, explicitly annotate type or property with @Contextual +# This bug first appeared in v1.7.1 of the Data Connect Toolkit CLI, which +# started to be used in firebase-tools v13.26.0 +# (https://github.com/firebase/firebase-tools/releases/tag/v13.26.0). +# This mutation can be removed from this file once the fix is released, which will +# likely be version 1.7.4 of the Data Connect Toolkit CLI (v13.28.1 of firebase- +# tools). +mutation WorkaroundForBug382688278( $id: UUID!) { + zwda6x9zyy_insert(data: { id: $id }) +} diff --git a/firebase-dataconnect/demo/firebase/dataconnect/dataconnect.yaml b/firebase-dataconnect/demo/firebase/dataconnect/dataconnect.yaml new file mode 100644 index 00000000000..3a718496328 --- /dev/null +++ b/firebase-dataconnect/demo/firebase/dataconnect/dataconnect.yaml @@ -0,0 +1,9 @@ +specVersion: v1 +serviceId: srv3ar8skbsza +location: us-central1 +schema: + source: ./schema + datasource: + postgresql: null +connectorDirs: + - ./connector diff --git a/firebase-dataconnect/demo/firebase/dataconnect/schema/schema.gql b/firebase-dataconnect/demo/firebase/dataconnect/schema/schema.gql new file mode 100644 index 00000000000..5b9c93cef13 --- /dev/null +++ b/firebase-dataconnect/demo/firebase/dataconnect/schema/schema.gql @@ -0,0 +1,24 @@ +# 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. + +type zwda6x9zyy @table { + string: String + int: Int + int64: Int64 + float: Float + boolean: Boolean + date: Date + timestamp: Timestamp + any: Any +} diff --git a/firebase-dataconnect/demo/firebase/firebase.json b/firebase-dataconnect/demo/firebase/firebase.json new file mode 100644 index 00000000000..5994e591654 --- /dev/null +++ b/firebase-dataconnect/demo/firebase/firebase.json @@ -0,0 +1,8 @@ +{ + "dataconnect": {"source": "dataconnect"}, + "emulators": { + "singleProjectMode": true, + "dataconnect": {"port": 9399}, + "ui": {"enabled": true} + } +} diff --git a/firebase-dataconnect/demo/google-services.json b/firebase-dataconnect/demo/google-services.json new file mode 100644 index 00000000000..34bccc8818b --- /dev/null +++ b/firebase-dataconnect/demo/google-services.json @@ -0,0 +1,14 @@ +{ + "client": [{ + "client_info": { + "android_client_info": {"package_name": "com.google.firebase.dataconnect.minimaldemo"}, + "mobilesdk_app_id": "1:12345678901:android:1234567890abcdef123456" + }, + "api_key": [{"current_key": "AIzayDNSXIbFmlXbIE6mCzDLQAqITYefhixbX4A"}] + }], + "configuration_version": "1", + "project_info": { + "project_number": "12345678901", + "project_id": "fakeproject" + } +} diff --git a/firebase-dataconnect/demo/gradle.properties b/firebase-dataconnect/demo/gradle.properties new file mode 100644 index 00000000000..12a9a7c6e9c --- /dev/null +++ b/firebase-dataconnect/demo/gradle.properties @@ -0,0 +1,7 @@ +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true + +android.useAndroidX=true + +org.gradle.jvmargs=-Xmx2g diff --git a/firebase-dataconnect/demo/gradle/wrapper/gradle-wrapper.jar b/firebase-dataconnect/demo/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..a4b76b9530d Binary files /dev/null and b/firebase-dataconnect/demo/gradle/wrapper/gradle-wrapper.jar differ diff --git a/firebase-dataconnect/demo/gradle/wrapper/gradle-wrapper.properties b/firebase-dataconnect/demo/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..e2847c82004 --- /dev/null +++ b/firebase-dataconnect/demo/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/firebase-dataconnect/demo/gradlew b/firebase-dataconnect/demo/gradlew new file mode 100755 index 00000000000..f5feea6d6b1 --- /dev/null +++ b/firebase-dataconnect/demo/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/firebase-dataconnect/demo/gradlew.bat b/firebase-dataconnect/demo/gradlew.bat new file mode 100644 index 00000000000..9b42019c791 --- /dev/null +++ b/firebase-dataconnect/demo/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/firebase-dataconnect/demo/settings.gradle.kts b/firebase-dataconnect/demo/settings.gradle.kts new file mode 100644 index 00000000000..72011613a80 --- /dev/null +++ b/firebase-dataconnect/demo/settings.gradle.kts @@ -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. + */ + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} diff --git a/firebase-dataconnect/demo/src/main/AndroidManifest.xml b/firebase-dataconnect/demo/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..084754492ca --- /dev/null +++ b/firebase-dataconnect/demo/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/ListItemsActivity.kt b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/ListItemsActivity.kt new file mode 100644 index 00000000000..72c6ef546e2 --- /dev/null +++ b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/ListItemsActivity.kt @@ -0,0 +1,117 @@ +/* + * 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.dataconnect.minimaldemo + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.firebase.dataconnect.minimaldemo.connector.GetAllItemsQuery +import com.google.firebase.dataconnect.minimaldemo.databinding.ActivityListItemsBinding +import com.google.firebase.dataconnect.minimaldemo.databinding.ListItemBinding +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class ListItemsActivity : AppCompatActivity() { + + private lateinit var myApplication: MyApplication + private lateinit var viewBinding: ActivityListItemsBinding + private val viewModel: ListItemsViewModel by viewModels { ListItemsViewModel.Factory } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + myApplication = application as MyApplication + + viewBinding = ActivityListItemsBinding.inflate(layoutInflater) + viewBinding.recyclerView.also { + val linearLayoutManager = LinearLayoutManager(this) + it.layoutManager = linearLayoutManager + val dividerItemDecoration = DividerItemDecoration(this, linearLayoutManager.layoutDirection) + it.addItemDecoration(dividerItemDecoration) + } + setContentView(viewBinding.root) + + lifecycleScope.launch { + if (viewModel.loadingState == ListItemsViewModel.LoadingState.NotStarted) { + viewModel.getItems() + } + viewModel.stateSequenceNumber.flowWithLifecycle(lifecycle).collectLatest { + onViewModelStateChange() + } + } + } + + private fun onViewModelStateChange() { + val items = viewModel.result?.getOrNull() + val exception = viewModel.result?.exceptionOrNull() + val loadingState = viewModel.loadingState + + if (loadingState == ListItemsViewModel.LoadingState.InProgress) { + viewBinding.statusText.text = "Loading Items..." + viewBinding.statusText.visibility = View.VISIBLE + viewBinding.recyclerView.visibility = View.GONE + viewBinding.recyclerView.adapter = null + } else if (items !== null) { + viewBinding.statusText.text = null + viewBinding.statusText.visibility = View.GONE + viewBinding.recyclerView.visibility = View.VISIBLE + val oldAdapter = viewBinding.recyclerView.adapter as? RecyclerViewAdapterImpl + if (oldAdapter === null || oldAdapter.items !== items) { + viewBinding.recyclerView.adapter = RecyclerViewAdapterImpl(items) + } + } else if (exception !== null) { + viewBinding.statusText.text = "Loading items FAILED: $exception" + viewBinding.statusText.visibility = View.VISIBLE + viewBinding.recyclerView.visibility = View.GONE + viewBinding.recyclerView.adapter = null + } else { + viewBinding.statusText.text = null + viewBinding.statusText.visibility = View.GONE + viewBinding.recyclerView.visibility = View.GONE + } + } + + private class RecyclerViewAdapterImpl(val items: List) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewViewHolderImpl { + val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return RecyclerViewViewHolderImpl(binding) + } + + override fun getItemCount() = items.size + + override fun onBindViewHolder(holder: RecyclerViewViewHolderImpl, position: Int) { + holder.bindTo(items[position]) + } + } + + private class RecyclerViewViewHolderImpl(private val binding: ListItemBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bindTo(item: GetAllItemsQuery.Data.ItemsItem) { + binding.id.text = item.id.toString() + binding.name.text = item.string + } + } +} diff --git a/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/ListItemsViewModel.kt b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/ListItemsViewModel.kt new file mode 100644 index 00000000000..c08ed3aa037 --- /dev/null +++ b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/ListItemsViewModel.kt @@ -0,0 +1,117 @@ +/* + * 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.dataconnect.minimaldemo + +import android.util.Log +import androidx.annotation.MainThread +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.google.firebase.dataconnect.minimaldemo.connector.GetAllItemsQuery +import com.google.firebase.dataconnect.minimaldemo.connector.execute +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class ListItemsViewModel(private val app: MyApplication) : ViewModel() { + + // Threading Note: _state and the variables below it may ONLY be accessed (read from and/or + // written to) by the main thread; otherwise a race condition and undefined behavior will result. + private val _stateSequenceNumber = MutableStateFlow(111999L) + val stateSequenceNumber: StateFlow = _stateSequenceNumber.asStateFlow() + + var result: Result>? = null + private set + + private var job: Job? = null + val loadingState: LoadingState = + job.let { + if (it === null) { + LoadingState.NotStarted + } else if (it.isCancelled || it.isCompleted) { + LoadingState.Completed + } else { + LoadingState.InProgress + } + } + + enum class LoadingState { + NotStarted, + InProgress, + Completed, + } + + @OptIn(ExperimentalCoroutinesApi::class) + @MainThread + fun getItems() { + // If there is already a "get items" operation in progress, then just return and let the + // in-progress operation finish. + if (loadingState == LoadingState.InProgress) { + return + } + + // Start a new coroutine to perform the "get items" operation. + val job: Deferred> = + viewModelScope.async { app.getConnector().getAllItems.execute().data.items } + + this.result = null + this.job = job + _stateSequenceNumber.value++ + + // Update the internal state once the "get items" operation has completed. + job.invokeOnCompletion { exception -> + // Don't log CancellationException, as documented by invokeOnCompletion(). + if (exception is CancellationException) { + return@invokeOnCompletion + } + + val result = + if (exception !== null) { + Log.w(TAG, "WARNING: Getting all items FAILED: $exception", exception) + Result.failure(exception) + } else { + val items = job.getCompleted() + Log.i(TAG, "Retrieved all items ${items.size} items") + Result.success(items) + } + + viewModelScope.launch { + if (this@ListItemsViewModel.job === job) { + this@ListItemsViewModel.result = result + this@ListItemsViewModel.job = null + _stateSequenceNumber.value++ + } + } + } + } + + companion object { + private const val TAG = "ListItemsViewModel" + + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { ListItemsViewModel(this[APPLICATION_KEY] as MyApplication) } + } + } +} diff --git a/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MainActivity.kt b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MainActivity.kt new file mode 100644 index 00000000000..a855bd8dbcf --- /dev/null +++ b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MainActivity.kt @@ -0,0 +1,172 @@ +/* + * 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.dataconnect.minimaldemo + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.widget.CompoundButton.OnCheckedChangeListener +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import com.google.firebase.dataconnect.minimaldemo.MainViewModel.OperationState +import com.google.firebase.dataconnect.minimaldemo.databinding.ActivityMainBinding +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class MainActivity : AppCompatActivity() { + + private lateinit var myApplication: MyApplication + private lateinit var viewBinding: ActivityMainBinding + private val viewModel: MainViewModel by viewModels { MainViewModel.Factory } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + myApplication = application as MyApplication + + viewBinding = ActivityMainBinding.inflate(layoutInflater) + setContentView(viewBinding.root) + + viewBinding.insertItemButton.setOnClickListener { viewModel.insertItem() } + viewBinding.getItemButton.setOnClickListener { viewModel.getItem() } + viewBinding.deleteItemButton.setOnClickListener { viewModel.deleteItem() } + viewBinding.useEmulatorCheckBox.setOnCheckedChangeListener(useEmulatorOnCheckedChangeListener) + viewBinding.debugLoggingCheckBox.setOnCheckedChangeListener(debugLoggingOnCheckedChangeListener) + + lifecycleScope.launch { + viewModel.stateSequenceNumber.flowWithLifecycle(lifecycle).collectLatest { + onViewModelStateChange() + } + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean = + when (item.itemId) { + R.id.action_list -> { + startActivity(Intent(this, ListItemsActivity::class.java)) + true + } + else -> super.onOptionsItemSelected(item) + } + + override fun onResume() { + super.onResume() + lifecycleScope.launch { + viewBinding.useEmulatorCheckBox.isChecked = myApplication.getUseDataConnectEmulator() + viewBinding.debugLoggingCheckBox.isChecked = myApplication.getDataConnectDebugLoggingEnabled() + } + } + + private fun onViewModelStateChange() { + viewBinding.progressText.text = viewModel.progressText + viewBinding.insertItemButton.isEnabled = !viewModel.isInsertOperationInProgress + viewBinding.getItemButton.isEnabled = + viewModel.isGetOperationRunnable && !viewModel.isGetOperationInProgress + viewBinding.deleteItemButton.isEnabled = + viewModel.isDeleteOperationRunnable && !viewModel.isDeleteOperationInProgress + } + + private val debugLoggingOnCheckedChangeListener = OnCheckedChangeListener { _, isChecked -> + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + return@OnCheckedChangeListener + } + myApplication.coroutineScope.launch { + myApplication.setDataConnectDebugLoggingEnabled(isChecked) + } + } + + private val useEmulatorOnCheckedChangeListener = OnCheckedChangeListener { _, isChecked -> + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + return@OnCheckedChangeListener + } + myApplication.coroutineScope.launch { myApplication.setUseDataConnectEmulator(isChecked) } + } + + companion object { + + private val MainViewModel.isInsertOperationInProgress: Boolean + get() = insertState is OperationState.InProgress + + private val MainViewModel.isGetOperationInProgress: Boolean + get() = getState is OperationState.InProgress + + private val MainViewModel.isDeleteOperationInProgress: Boolean + get() = deleteState is OperationState.InProgress + + private val MainViewModel.isGetOperationRunnable: Boolean + get() = lastInsertedKey !== null + + private val MainViewModel.isDeleteOperationRunnable: Boolean + get() = lastInsertedKey !== null + + private val MainViewModel.progressText: String? + get() { + // Save properties to local variables to enable Kotlin's type narrowing in the "if" blocks + // below. + val insertState = insertState + val getState = getState + val deleteState = deleteState + val state = + listOfNotNull(insertState, getState, deleteState).maxByOrNull { it.sequenceNumber } + + return if (state === null) { + null + } else if (state === insertState) { + when (insertState) { + is OperationState.InProgress -> + "Inserting item: ${insertState.variables.toDisplayString()}" + is OperationState.Completed -> + insertState.result.fold( + onSuccess = { + "Inserted item with id=${it.id}:\n${insertState.variables.toDisplayString()}" + }, + onFailure = { "Inserting item ${insertState.variables} FAILED: $it" }, + ) + } + } else if (state === getState) { + when (getState) { + is OperationState.InProgress -> "Retrieving item with ID ${getState.variables.id}..." + is OperationState.Completed -> + getState.result.fold( + onSuccess = { + "Retrieved item with ID ${getState.variables.id}:\n${it?.toDisplayString()}" + }, + onFailure = { "Retrieving item with ID ${getState.variables.id} FAILED: $it" }, + ) + } + } else if (state === deleteState) { + when (deleteState) { + is OperationState.InProgress -> "Deleting item with ID ${deleteState.variables.id}..." + is OperationState.Completed -> + deleteState.result.fold( + onSuccess = { "Deleted item with ID ${deleteState.variables.id}" }, + onFailure = { "Deleting item with ID ${deleteState.variables.id} FAILED: $it" }, + ) + } + } else { + throw RuntimeException("internal error: unknown state: $state (error code vp4rjptx6r)") + } + } + } +} diff --git a/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MainViewModel.kt b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MainViewModel.kt new file mode 100644 index 00000000000..1769a9154b8 --- /dev/null +++ b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MainViewModel.kt @@ -0,0 +1,224 @@ +/* + * 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.dataconnect.minimaldemo + +import android.util.Log +import androidx.annotation.MainThread +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.google.firebase.dataconnect.minimaldemo.connector.GetItemByKeyQuery +import com.google.firebase.dataconnect.minimaldemo.connector.InsertItemMutation +import com.google.firebase.dataconnect.minimaldemo.connector.Zwda6x9zyyKey +import com.google.firebase.dataconnect.minimaldemo.connector.execute +import io.kotest.property.Arb +import io.kotest.property.RandomSource +import io.kotest.property.arbitrary.next +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class MainViewModel(private val app: MyApplication) : ViewModel() { + + private val rs = RandomSource.default() + + // Threading Note: _state and the variables below it may ONLY be accessed (read from and/or + // written to) by the main thread; otherwise a race condition and undefined behavior will result. + private val _stateSequenceNumber = MutableStateFlow(111999L) + val stateSequenceNumber: StateFlow = _stateSequenceNumber.asStateFlow() + + var insertState: OperationState? = null + private set + + var getState: OperationState? = null + private set + + var deleteState: OperationState? = null + private set + + var lastInsertedKey: Zwda6x9zyyKey? = null + private set + + @OptIn(ExperimentalCoroutinesApi::class) + @MainThread + fun insertItem() { + val arb = Arb.insertItemVariables() + val variables = if (rs.random.nextFloat() < 0.333f) arb.edgecase(rs)!! else arb.next(rs) + + // If there is already an "insert" in progress, then just return and let the in-progress + // operation finish. + if (insertState is OperationState.InProgress) { + return + } + + // Start a new coroutine to perform the "insert" operation. + Log.i(TAG, "Inserting item: $variables") + val job: Deferred = + viewModelScope.async { app.getConnector().insertItem.ref(variables).execute().data.key } + val inProgressOperationState = + OperationState.InProgress(_stateSequenceNumber.value, variables, job) + insertState = inProgressOperationState + _stateSequenceNumber.value++ + + // Update the internal state once the "insert" operation has completed. + job.invokeOnCompletion { exception -> + // Don't log CancellationException, as documented by invokeOnCompletion(). + if (exception is CancellationException) { + return@invokeOnCompletion + } + + val result = + if (exception !== null) { + Log.w(TAG, "WARNING: Inserting item FAILED: $exception (variables=$variables)", exception) + Result.failure(exception) + } else { + val key = job.getCompleted() + Log.i(TAG, "Inserted item with key: $key (variables=${variables})") + Result.success(key) + } + + viewModelScope.launch { + if (insertState === inProgressOperationState) { + insertState = OperationState.Completed(_stateSequenceNumber.value, variables, result) + result.onSuccess { lastInsertedKey = it } + _stateSequenceNumber.value++ + } + } + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + fun getItem() { + // If there is no previous successful "insert" operation, then we don't know any ID's to get, + // so just do nothing. + val key: Zwda6x9zyyKey = lastInsertedKey ?: return + + // If there is already a "get" in progress, then just return and let the in-progress operation + // finish. + if (getState is OperationState.InProgress) { + return + } + + // Start a new coroutine to perform the "get" operation. + Log.i(TAG, "Retrieving item with key: $key") + val job: Deferred = + viewModelScope.async { app.getConnector().getItemByKey.execute(key).data.item } + val inProgressOperationState = OperationState.InProgress(_stateSequenceNumber.value, key, job) + getState = inProgressOperationState + _stateSequenceNumber.value++ + + // Update the internal state once the "get" operation has completed. + job.invokeOnCompletion { exception -> + // Don't log CancellationException, as documented by invokeOnCompletion(). + if (exception is CancellationException) { + return@invokeOnCompletion + } + + val result = + if (exception !== null) { + Log.w(TAG, "WARNING: Retrieving item with key=$key FAILED: $exception", exception) + Result.failure(exception) + } else { + val item = job.getCompleted() + Log.i(TAG, "Retrieved item with key: $key (item=${item})") + Result.success(item) + } + + viewModelScope.launch { + if (getState === inProgressOperationState) { + getState = OperationState.Completed(_stateSequenceNumber.value, key, result) + _stateSequenceNumber.value++ + } + } + } + } + + fun deleteItem() { + // If there is no previous successful "insert" operation, then we don't know any ID's to delete, + // so just do nothing. + val key: Zwda6x9zyyKey = lastInsertedKey ?: return + + // If there is already a "delete" in progress, then just return and let the in-progress + // operation finish. + if (deleteState is OperationState.InProgress) { + return + } + + // Start a new coroutine to perform the "delete" operation. + Log.i(TAG, "Deleting item with key: $key") + val job: Deferred = + viewModelScope.async { app.getConnector().deleteItemByKey.execute(key) } + val inProgressOperationState = OperationState.InProgress(_stateSequenceNumber.value, key, job) + deleteState = inProgressOperationState + _stateSequenceNumber.value++ + + // Update the internal state once the "delete" operation has completed. + job.invokeOnCompletion { exception -> + // Don't log CancellationException, as documented by invokeOnCompletion(). + if (exception is CancellationException) { + return@invokeOnCompletion + } + + val result = + if (exception !== null) { + Log.w(TAG, "WARNING: Deleting item with key=$key FAILED: $exception", exception) + Result.failure(exception) + } else { + Log.i(TAG, "Deleted item with key: $key") + Result.success(Unit) + } + + viewModelScope.launch { + if (deleteState === inProgressOperationState) { + deleteState = OperationState.Completed(_stateSequenceNumber.value, key, result) + _stateSequenceNumber.value++ + } + } + } + } + + sealed interface OperationState { + val sequenceNumber: Long + + data class InProgress( + override val sequenceNumber: Long, + val variables: Variables, + val job: Deferred, + ) : OperationState + + data class Completed( + override val sequenceNumber: Long, + val variables: Variables, + val result: Result, + ) : OperationState + } + + companion object { + private const val TAG = "MainViewModel" + + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { MainViewModel(this[APPLICATION_KEY] as MyApplication) } + } + } +} diff --git a/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MyApplication.kt b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MyApplication.kt new file mode 100644 index 00000000000..1b6360efb58 --- /dev/null +++ b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/MyApplication.kt @@ -0,0 +1,153 @@ +/* + * 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.dataconnect.minimaldemo + +import android.app.Application +import android.content.SharedPreferences +import android.util.Log +import com.google.firebase.dataconnect.FirebaseDataConnect +import com.google.firebase.dataconnect.LogLevel +import com.google.firebase.dataconnect.logLevel +import com.google.firebase.dataconnect.minimaldemo.connector.Ctry3q3tp6kzxConnector +import com.google.firebase.dataconnect.minimaldemo.connector.instance +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext + +class MyApplication : Application() { + + /** + * A [CoroutineScope] whose lifetime matches that of this [Application] object. + * + * Namely, the scope will be cancelled when [onTerminate] is called. + * + * This scope's [Job] is a [SupervisorJob], and, therefore, uncaught exceptions will _not_ + * terminate the application. + */ + val coroutineScope = + CoroutineScope( + SupervisorJob() + + CoroutineName("MyApplication@${System.identityHashCode(this@MyApplication)}") + + CoroutineExceptionHandler { context, throwable -> + val coroutineName = context[CoroutineName]?.name + Log.w( + TAG, + "WARNING: ignoring uncaught exception thrown from coroutine " + + "named \"$coroutineName\": $throwable " + + "(error code 8xrn9vvddd)", + throwable, + ) + } + ) + + private val initialLogLevel = FirebaseDataConnect.logLevel.value + private val connectorMutex = Mutex() + private var connector: Ctry3q3tp6kzxConnector? = null + + override fun onCreate() { + super.onCreate() + + coroutineScope.launch { + if (getDataConnectDebugLoggingEnabled()) { + FirebaseDataConnect.logLevel.value = LogLevel.DEBUG + } + } + } + + suspend fun getConnector(): Ctry3q3tp6kzxConnector { + connectorMutex.withLock { + val oldConnector = connector + if (oldConnector !== null) { + return oldConnector + } + + val newConnector = Ctry3q3tp6kzxConnector.instance + + if (getUseDataConnectEmulator()) { + newConnector.dataConnect.useEmulator() + } + + connector = newConnector + return newConnector + } + } + + private suspend fun getSharedPreferences(): SharedPreferences = + withContext(Dispatchers.IO) { + getSharedPreferences("MyApplicationSharedPreferences", MODE_PRIVATE) + } + + suspend fun getDataConnectDebugLoggingEnabled(): Boolean = + getSharedPreferences().all[SharedPrefsKeys.IS_DATA_CONNECT_LOGGING_ENABLED] as? Boolean ?: false + + suspend fun setDataConnectDebugLoggingEnabled(enabled: Boolean) { + FirebaseDataConnect.logLevel.value = if (enabled) LogLevel.DEBUG else initialLogLevel + editSharedPreferences { putBoolean(SharedPrefsKeys.IS_DATA_CONNECT_LOGGING_ENABLED, enabled) } + } + + suspend fun getUseDataConnectEmulator(): Boolean = + getSharedPreferences().all[SharedPrefsKeys.IS_USE_DATA_CONNECT_EMULATOR] as? Boolean ?: true + + suspend fun setUseDataConnectEmulator(enabled: Boolean) { + val requiresRestart = getUseDataConnectEmulator() != enabled + editSharedPreferences { putBoolean(SharedPrefsKeys.IS_USE_DATA_CONNECT_EMULATOR, enabled) } + + if (requiresRestart) { + connectorMutex.withLock { + val oldConnector = connector + connector = null + oldConnector?.dataConnect?.close() + } + } + } + + private suspend fun editSharedPreferences(block: SharedPreferences.Editor.() -> Unit) { + val prefs = getSharedPreferences() + withContext(Dispatchers.IO) { + val editor = prefs.edit() + block(editor) + if (!editor.commit()) { + Log.w( + TAG, + "WARNING: failed to save changes to SharedPreferences; " + + "ignoring the failure (error code wzy99s7jmy)", + ) + } + } + } + + override fun onTerminate() { + coroutineScope.cancel("MyApplication.onTerminate() called") + super.onTerminate() + } + + private object SharedPrefsKeys { + const val IS_DATA_CONNECT_LOGGING_ENABLED = "isDataConnectDebugLoggingEnabled" + const val IS_USE_DATA_CONNECT_EMULATOR = "useDataConnectEmulator" + } + + companion object { + private const val TAG = "MyApplication" + } +} diff --git a/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/arbs.kt b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/arbs.kt new file mode 100644 index 00000000000..a77016f9659 --- /dev/null +++ b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/arbs.kt @@ -0,0 +1,190 @@ +/* + * 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.dataconnect.minimaldemo + +import android.annotation.SuppressLint +import com.google.firebase.Timestamp +import com.google.firebase.dataconnect.LocalDate +import com.google.firebase.dataconnect.OptionalVariable +import com.google.firebase.dataconnect.minimaldemo.connector.InsertItemMutation +import com.google.firebase.dataconnect.toJavaLocalDate +import io.kotest.property.Arb +import io.kotest.property.RandomSource +import io.kotest.property.Sample +import io.kotest.property.arbitrary.boolean +import io.kotest.property.arbitrary.double +import io.kotest.property.arbitrary.enum +import io.kotest.property.arbitrary.filterNot +import io.kotest.property.arbitrary.int +import io.kotest.property.arbitrary.long +import io.kotest.property.arbitrary.map +import io.kotest.property.arbitrary.next +import io.kotest.property.arbs.fooddrink.iceCreamFlavors +import io.kotest.property.asSample +import java.time.Instant +import java.time.LocalDateTime +import java.time.Month +import java.time.Year +import java.time.ZoneOffset +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +fun Arb.Companion.insertItemVariables(): Arb = + InsertItemMutationVariablesArb() + +private class InsertItemMutationVariablesArb( + private val string: Arb = Arb.iceCreamFlavors().map { it.value }, + private val int: Arb = Arb.int(), + private val int64: Arb = Arb.long(), + private val float: Arb = Arb.double().filterNot { it.isNaN() || it.isInfinite() }, + private val boolean: Arb = Arb.boolean(), + private val date: Arb = Arb.dataConnectLocalDate(), + private val timestamp: Arb = Arb.firebaseTimestamp(), +) : Arb() { + override fun edgecase(rs: RandomSource): InsertItemMutation.Variables = + InsertItemMutation.Variables( + string = string.optionalEdgeCase(rs), + int = int.optionalEdgeCase(rs), + int64 = int64.optionalEdgeCase(rs), + float = float.optionalEdgeCase(rs), + boolean = boolean.optionalEdgeCase(rs), + date = date.optionalEdgeCase(rs), + timestamp = timestamp.optionalEdgeCase(rs), + any = OptionalVariable.Undefined, + ) + + override fun sample(rs: RandomSource): Sample = + InsertItemMutation.Variables( + string = OptionalVariable.Value(string.next(rs)), + int = OptionalVariable.Value(int.next(rs)), + int64 = OptionalVariable.Value(int64.next(rs)), + float = OptionalVariable.Value(float.next(rs)), + boolean = OptionalVariable.Value(boolean.next(rs)), + date = OptionalVariable.Value(date.next(rs)), + timestamp = OptionalVariable.Value(timestamp.next(rs)), + any = OptionalVariable.Undefined, + ) + .asSample() +} + +fun Arb.Companion.dataConnectLocalDate(): Arb = DataConnectLocalDateArb() + +private class DataConnectLocalDateArb : Arb() { + + private val yearArb: Arb = Arb.int(MIN_YEAR..MAX_YEAR).map { Year.of(it) } + private val monthArb: Arb = Arb.enum() + private val dayArbByMonthLengthLock = ReentrantLock() + private val dayArbByMonthLength = mutableMapOf>() + + override fun edgecase(rs: RandomSource): LocalDate { + val year = yearArb.maybeEdgeCase(rs, edgeCaseProbability = 0.33f) + val month = monthArb.maybeEdgeCase(rs, edgeCaseProbability = 0.33f) + val day = Arb.dayOfMonth(year, month).maybeEdgeCase(rs, edgeCaseProbability = 0.33f) + return LocalDate(year = year.value, month = month.value, day = day) + } + + override fun sample(rs: RandomSource): Sample { + val year = yearArb.sample(rs).value + val month = monthArb.sample(rs).value + val day = Arb.dayOfMonth(year, month).sample(rs).value + return LocalDate(year = year.value, month = month.value, day = day).asSample() + } + + private fun Arb.Companion.dayOfMonth(year: Year, month: Month): Arb { + val monthLength = year.atMonth(month).lengthOfMonth() + return dayArbByMonthLengthLock.withLock { + dayArbByMonthLength.getOrPut(monthLength) { Arb.int(1..monthLength) } + } + } + + companion object { + const val MIN_YEAR = 1583 + const val MAX_YEAR = 9999 + } +} + +fun Arb.Companion.firebaseTimestamp(): Arb = FirebaseTimestampArb() + +private class FirebaseTimestampArb : Arb() { + + private val localDateArb = Arb.dataConnectLocalDate() + private val hourArb = Arb.int(1..23) + private val minuteArb = Arb.int(1..59) + private val secondArb = Arb.int(1..59) + private val nanosecondArb = Arb.int(0..999_999_999) + + override fun edgecase(rs: RandomSource) = + localDateArb + .maybeEdgeCase(rs, edgeCaseProbability = 0.2f) + .toTimestampAtTime( + hour = hourArb.maybeEdgeCase(rs, edgeCaseProbability = 0.2f), + minute = minuteArb.maybeEdgeCase(rs, edgeCaseProbability = 0.2f), + second = secondArb.maybeEdgeCase(rs, edgeCaseProbability = 0.2f), + nanosecond = nanosecondArb.maybeEdgeCase(rs, edgeCaseProbability = 0.2f), + ) + + override fun sample(rs: RandomSource) = + localDateArb + .next(rs) + .toTimestampAtTime( + hour = hourArb.next(rs), + minute = minuteArb.next(rs), + second = secondArb.next(rs), + nanosecond = nanosecondArb.next(rs), + ) + .asSample() + + companion object { + + // Suppress the spurious "Call requires API level 26" warning, which can be safely ignored + // because this application uses "desugaring" to ensure access to the java.time APIs even in + // Android API versions less than 26. + // See https://developer.android.com/studio/write/java8-support-table for details. + @SuppressLint("NewApi") + private fun LocalDate.toTimestampAtTime( + hour: Int, + minute: Int, + second: Int, + nanosecond: Int, + ): Timestamp { + val localDateTime: LocalDateTime = toJavaLocalDate().atTime(hour, minute, second, nanosecond) + val instant: Instant = localDateTime.toInstant(ZoneOffset.UTC) + return Timestamp(instant) + } + } +} + +private fun Arb.optionalEdgeCase(rs: RandomSource): OptionalVariable { + val discriminator = rs.random.nextFloat() + return if (discriminator < 0.25f) { + OptionalVariable.Undefined + } else if (discriminator < 0.50f) { + OptionalVariable.Value(null) + } else { + OptionalVariable.Value(edgecase(rs) ?: next(rs)) + } +} + +private fun Arb.maybeEdgeCase(rs: RandomSource, edgeCaseProbability: Float = 0.5f): T { + require(edgeCaseProbability >= 0.0 && edgeCaseProbability < 1.0) { + "invalid edgeCaseProbability: $edgeCaseProbability" + } + return if (rs.random.nextFloat() >= edgeCaseProbability) { + sample(rs).value + } else { + edgecase(rs) ?: sample(rs).value + } +} diff --git a/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/strings.kt b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/strings.kt new file mode 100644 index 00000000000..3ddf838ed2d --- /dev/null +++ b/firebase-dataconnect/demo/src/main/kotlin/com/google/firebase/dataconnect/minimaldemo/strings.kt @@ -0,0 +1,89 @@ +/* + * 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.dataconnect.minimaldemo + +import android.annotation.SuppressLint +import com.google.firebase.Timestamp +import com.google.firebase.dataconnect.AnyValue +import com.google.firebase.dataconnect.LocalDate +import com.google.firebase.dataconnect.OptionalVariable +import com.google.firebase.dataconnect.minimaldemo.connector.GetItemByKeyQuery +import com.google.firebase.dataconnect.minimaldemo.connector.InsertItemMutation +import com.google.firebase.dataconnect.toJavaLocalDate +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.Locale + +fun InsertItemMutation.Variables.toDisplayString(): String = + displayStringForItem( + string = string, + int = int, + int64 = int64, + float = float, + boolean = boolean, + date = date, + timestamp = timestamp, + any = any, + ) + +fun GetItemByKeyQuery.Data.Item.toDisplayString(): String = + displayStringForItem( + string = OptionalVariable.Value(string), + int = OptionalVariable.Value(int), + int64 = OptionalVariable.Value(int64), + float = OptionalVariable.Value(float), + boolean = OptionalVariable.Value(boolean), + date = OptionalVariable.Value(date), + timestamp = OptionalVariable.Value(timestamp), + any = OptionalVariable.Value(any), + ) + +fun displayStringForItem( + string: OptionalVariable, + int: OptionalVariable, + int64: OptionalVariable, + float: OptionalVariable, + boolean: OptionalVariable, + date: OptionalVariable, + timestamp: OptionalVariable, + any: OptionalVariable, +) = buildString { + append("string=").append(string).appendLine() + append("int=").append(int).appendLine() + append("int64=").append(int64).appendLine() + append("float=").append(float).appendLine() + append("boolean=").append(boolean).appendLine() + append("date=").append(date.toDisplayString { it.toDisplayString() }).appendLine() + append("timestamp=").append(timestamp.toDisplayString { it.toDisplayString() }).appendLine() + append("any=").append(any) +} + +private fun OptionalVariable.toDisplayString(stringer: (T) -> String): String = + when (this) { + is OptionalVariable.Undefined -> toString() + is OptionalVariable.Value -> value?.let(stringer) ?: "null" + } + +private fun LocalDate.toDisplayString(): String = + toJavaLocalDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)) + +@SuppressLint("NewApi") +private fun Timestamp.toDisplayString(): String = + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG) + .withLocale(Locale.getDefault()) + .withZone(ZoneId.systemDefault()) + .format(toInstant()) diff --git a/firebase-dataconnect/demo/src/main/res/layout/activity_list_items.xml b/firebase-dataconnect/demo/src/main/res/layout/activity_list_items.xml new file mode 100644 index 00000000000..1bf7af38ab6 --- /dev/null +++ b/firebase-dataconnect/demo/src/main/res/layout/activity_list_items.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + diff --git a/firebase-dataconnect/demo/src/main/res/layout/activity_main.xml b/firebase-dataconnect/demo/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000000..f37798c01ce --- /dev/null +++ b/firebase-dataconnect/demo/src/main/res/layout/activity_main.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + +