diff --git a/.github/resources/firebase.asc.gpg b/.github/resources/firebase.asc.gpg index dae950b79..4b41f18c5 100644 Binary files a/.github/resources/firebase.asc.gpg and b/.github/resources/firebase.asc.gpg differ diff --git a/.github/resources/integ-service-account.json.gpg b/.github/resources/integ-service-account.json.gpg index da547f44d..5a52805c9 100644 Binary files a/.github/resources/integ-service-account.json.gpg and b/.github/resources/integ-service-account.json.gpg differ diff --git a/.github/resources/settings.xml b/.github/resources/settings.xml index 847ea52d0..53e482738 100644 --- a/.github/resources/settings.xml +++ b/.github/resources/settings.xml @@ -7,9 +7,9 @@ - ossrh - ${env.NEXUS_OSSRH_USERNAME} - ${env.NEXUS_OSSRH_PASSWORD} + central + ${env.CENTRAL_USERNAME} + ${env.CENTRAL_TOKEN} @@ -21,7 +21,7 @@ gpg - 77A3CB7D41F06A4512BA8EA5AA4E6ADF6A05C58E + ${env.GPG_KEY_ID} ${env.GPG_PASSPHRASE} diff --git a/.github/scripts/generate_changelog.sh b/.github/scripts/generate_changelog.sh index e393f40e4..a0afa6e41 100755 --- a/.github/scripts/generate_changelog.sh +++ b/.github/scripts/generate_changelog.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Google Inc. +# 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. diff --git a/.github/scripts/package_artifacts.sh b/.github/scripts/package_artifacts.sh index 30d998c75..fbbc5555b 100755 --- a/.github/scripts/package_artifacts.sh +++ b/.github/scripts/package_artifacts.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Google Inc. +# 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. diff --git a/.github/scripts/publish_artifacts.sh b/.github/scripts/publish_artifacts.sh index cd1a5b75c..5da0f32a4 100755 --- a/.github/scripts/publish_artifacts.sh +++ b/.github/scripts/publish_artifacts.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Google Inc. +# 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. @@ -26,7 +26,7 @@ gpg --import --no-tty --batch --yes firebase.asc # 1. Compiles the source (compile phase) # 2. Packages the artifacts - src, bin, javadocs (package phase) # 3. Signs the artifacts (verify phase) -# 4. Publishes artifacts via Nexus (deploy phase) +# 4. Publishes artifacts via Central Publisher Portal (deploy phase) mvn -B clean deploy \ -Dcheckstyle.skip \ -DskipTests \ diff --git a/.github/scripts/publish_preflight_check.sh b/.github/scripts/publish_preflight_check.sh index 7a191518d..7c23eba57 100755 --- a/.github/scripts/publish_preflight_check.sh +++ b/.github/scripts/publish_preflight_check.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2020 Google Inc. +# 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. @@ -69,7 +69,7 @@ if [[ ! "${RELEASE_VERSION}" =~ ^([0-9]*)\.([0-9]*)\.([0-9]*)$ ]]; then fi echo_info "Extracted release version: ${RELEASE_VERSION}" -echo "::set-output name=version::v${RELEASE_VERSION}" +echo "version=v${RELEASE_VERSION}" >> $GITHUB_OUTPUT echo_info "" @@ -122,8 +122,8 @@ echo_info "Generating changelog" echo_info "--------------------------------------------" echo_info "" -echo_info "---< git fetch origin master --prune --unshallow >---" -git fetch origin master --prune --unshallow +echo_info "---< git fetch origin main --prune --unshallow >---" +git fetch origin main --prune --unshallow echo "" echo_info "Generating changelog from history..." @@ -132,12 +132,13 @@ readonly CHANGELOG=`${CURRENT_DIR}/generate_changelog.sh` echo "$CHANGELOG" # Parse and preformat the text to handle multi-line output. -# See https://github.community/t5/GitHub-Actions/set-output-Truncates-Multiline-Strings/td-p/37870 +# See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#example-of-a-multiline-string +# and https://github.com/github/docs/issues/21529#issue-1418590935 FILTERED_CHANGELOG=`echo "$CHANGELOG" | grep -v "\\[INFO\\]"` -FILTERED_CHANGELOG="${FILTERED_CHANGELOG//'%'/'%25'}" -FILTERED_CHANGELOG="${FILTERED_CHANGELOG//$'\n'/'%0A'}" -FILTERED_CHANGELOG="${FILTERED_CHANGELOG//$'\r'/'%0D'}" -echo "::set-output name=changelog::${FILTERED_CHANGELOG}" +FILTERED_CHANGELOG="${FILTERED_CHANGELOG//$'\''/'"'}" +echo "changelog<> $GITHUB_OUTPUT +echo -e "$FILTERED_CHANGELOG" >> $GITHUB_OUTPUT +echo "CHANGELOGEOF" >> $GITHUB_OUTPUT echo "" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb09f08c6..4c3d6ac73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# Copyright 2020 Google Inc. +# 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. @@ -24,11 +24,12 @@ jobs: java-version: [8, 11, 17] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up JDK - uses: actions/setup-java@v1 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0 with: + distribution: 'zulu' java-version: ${{ matrix.java-version }} # Does the following: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index dd3110c46..5026f2580 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,4 +1,4 @@ -# Copyright 2021 Google Inc. +# 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. @@ -29,14 +29,15 @@ jobs: steps: - name: Checkout source for staging - uses: actions/checkout@v2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: ${{ github.event.client_payload.ref || github.ref }} - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 8 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0 with: - java-version: 1.8 + distribution: 'zulu' + java-version: 8 - name: Compile, test and package run: ./.github/scripts/package_artifacts.sh @@ -47,14 +48,14 @@ jobs: # Attach the packaged artifacts to the workflow output. These can be manually # downloaded for later inspection if necessary. - name: Archive artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dist path: dist - name: Send email on failure if: failure() - uses: firebase/firebase-admin-node/.github/actions/send-email@master + uses: firebase/firebase-admin-node/.github/actions/send-email@2e2b36a84ba28679bcb7aecdacabfec0bded2d48 # Admin Node SDK v13.6.0 with: api-key: ${{ secrets.OSS_BOT_MAILGUN_KEY }} domain: ${{ secrets.OSS_BOT_MAILGUN_DOMAIN }} @@ -69,7 +70,7 @@ jobs: - name: Send email on cancelled if: cancelled() - uses: firebase/firebase-admin-node/.github/actions/send-email@master + uses: firebase/firebase-admin-node/.github/actions/send-email@2e2b36a84ba28679bcb7aecdacabfec0bded2d48 # Admin Node SDK v13.6.0 with: api-key: ${{ secrets.OSS_BOT_MAILGUN_KEY }} domain: ${{ secrets.OSS_BOT_MAILGUN_DOMAIN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b49a453fc..0be2d122c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -# Copyright 2020 Google Inc. +# 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. @@ -15,10 +15,18 @@ name: Release Candidate on: - # Only run the workflow when a PR is updated or when a developer explicitly requests - # a build by sending a 'firebase_build' event. + # Run the workflow when: + # 1. A PR is updated (staging checks). + # 2. A commit is pushed to main (release publication). + # 3. A developer explicitly requests a build via 'firebase_build' event. pull_request: - types: [opened, synchronize, closed] + types: [opened, synchronize] + + push: + branches: + - main + paths: + - 'pom.xml' repository_dispatch: types: @@ -26,28 +34,25 @@ on: jobs: stage_release: - # To publish a release, merge the release PR with the label 'release:publish'. + # To publish a release, merge a PR with the title prefix '[chore] Release ' to main + # and ensure the squashed commit message also has the prefix. # To stage a release without publishing it, send a 'firebase_build' event or apply # the 'release:stage' label to a PR. if: github.event.action == 'firebase_build' || contains(github.event.pull_request.labels.*.name, 'release:stage') || - (github.event.pull_request.merged && - contains(github.event.pull_request.labels.*.name, 'release:publish')) + (github.event_name == 'push' && startsWith(github.event.head_commit.message, '[chore] Release ')) runs-on: ubuntu-latest - # When manually triggering the build, the requester can specify a target branch or a tag - # via the 'ref' client parameter. steps: - name: Checkout source for staging - uses: actions/checkout@v2 - with: - ref: ${{ github.event.client_payload.ref || github.ref }} + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 8 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0 with: - java-version: 1.8 + distribution: 'zulu' + java-version: 8 - name: Compile, test and package run: ./.github/scripts/package_artifacts.sh @@ -58,7 +63,7 @@ jobs: # Attach the packaged artifacts to the workflow output. These can be manually # downloaded for later inspection if necessary. - name: Archive artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dist path: dist @@ -66,64 +71,49 @@ jobs: publish_release: needs: stage_release - # Check whether the release should be published. We publish only when the trigger PR is - # 1. merged - # 2. to the master branch - # 3. with the label 'release:publish', and - # 4. the title prefix '[chore] Release '. - if: github.event.pull_request.merged && - github.ref == 'master' && - contains(github.event.pull_request.labels.*.name, 'release:publish') && - startsWith(github.event.pull_request.title, '[chore] Release ') + # Check whether the release should be published. We publish only when the trigger is + # 1. a push (merge) + # 2. to the main branch + # 3. and the commit message has the title prefix '[chore] Release '. + if: github.event_name == 'push' && + github.ref == 'refs/heads/main' && + startsWith(github.event.head_commit.message, '[chore] Release ') runs-on: ubuntu-latest + environment: Release + permissions: + contents: write steps: - name: Checkout source for publish - uses: actions/checkout@v2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 8 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0 with: - java-version: 1.8 + distribution: 'zulu' + java-version: 8 - name: Publish preflight check id: preflight run: ./.github/scripts/publish_preflight_check.sh + # See: https://cli.github.com/manual/gh_release_create + - name: Create release tag + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_VER: ${{ steps.preflight.outputs.version }} + RELEASE_BODY: ${{ steps.preflight.outputs.changelog }} + run: | + gh release create "$RELEASE_VER" \ + --title "Firebase Admin Java SDK $RELEASE_VER" \ + --notes "$RELEASE_BODY" + - name: Publish to Maven Central run: ./.github/scripts/publish_artifacts.sh env: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - NEXUS_OSSRH_USERNAME: ${{ secrets.NEXUS_OSSRH_USERNAME }} - NEXUS_OSSRH_PASSWORD: ${{ secrets.NEXUS_OSSRH_PASSWORD }} - - # We pull this action from a custom fork of a contributor until - # https://github.com/actions/create-release/pull/32 is merged. Also note that v1 of - # this action does not support the "body" parameter. - - name: Create release tag - uses: fleskesvor/create-release@1a72e235c178bf2ae6c51a8ae36febc24568c5fe - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.preflight.outputs.version }} - release_name: Firebase Admin Java SDK ${{ steps.preflight.outputs.version }} - body: ${{ steps.preflight.outputs.changelog }} - draft: false - prerelease: false - - # Post to Twitter if explicitly opted-in by adding the label 'release:tweet'. - - name: Post to Twitter - if: success() && - contains(github.event.pull_request.labels.*.name, 'release:tweet') - uses: firebase/firebase-admin-node/.github/actions/send-tweet@master - with: - status: > - ${{ steps.preflight.outputs.version }} of @Firebase Admin Java SDK is available. - https://github.com/firebase/firebase-admin-java/releases/tag/${{ steps.preflight.outputs.version }} - consumer-key: ${{ secrets.TWITTER_CONSUMER_KEY }} - consumer-secret: ${{ secrets.TWITTER_CONSUMER_SECRET }} - access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }} - access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - continue-on-error: true + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..d25e006a6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,33 @@ +# AI coding assistant guidelines: Firebase Admin Java SDK + +This document defines repository-wide expectations, code styles, and Checkstyle compliance metrics that every autonomous AI coding agent or pair programming assistant must strictly follow when introducing or modifying code in this repository. + +--- + +## 🎨 Code Style & Checkstyle Compliance + +This repository enforces standard **Google Java Style** guidelines validated through automated Maven checkstyle executions (`checkstyle.xml`). Any compilation or pull request build will fail if these constraints are violated. + +### 1. Strict 100-Character Line Limit +* **Rule:** No line of code, comments, Javadoc documentation entries, or inline string literal concatenations may exceed **100 characters** under any circumstances. +* **Remediation:** + * Break long method parameters, array initializations, and logic comparisons onto separate lines. + * Wrap long `assertThrows` statements or lambda chains across multiple contiguous lines. + * Format long log messages, exception messages, or assertion string explanations using standard string block concatenation broken across separate lines. + +### 2. Import Ordering & Grouping +* **Rule:** Imports must be grouped and arranged alphabetically to avoid validation noise. +* **Remediation:** + 1. Static imports placed first, grouped, and alphabetically arranged. + 2. Non-static imports grouped alphabetically by package tier. + 3. Avoid utilizing wildcard (`*`) imports. Every import declaration must be explicit. + +### 3. Javadoc Completeness & Formatting +* **Rule:** Every public class, package-private component interface, constructor, and public method signature must include fully formed Javadoc documentation blocks. +* **Remediation:** + * Document all arguments via `@param`, explain error flows via `@throws`, and clear return constraints via `@return`. + * Wrap documentation description text explicitly so that no single javadoc documentation line passes the 100 characters limit. + +### 4. Indentation & Spacing +* **Rule:** Standard indentation uses exactly **2 spaces** per block indentation level. **4 spaces** are used explicitly for wrapped line continuation indentation. +* **Remediation:** Never utilize tab characters. Ensure block braces follow the Google K&R opening line placement convention. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3b65e101..907223c5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,7 @@ Great, we love hearing how we can improve our products! Share you idea through o ## Want to submit a pull request? Sweet, we'd love to accept your contribution! -[Open a new pull request](https://github.com/firebase/firebase-admin-java/pull/new/master) and fill +[Open a new pull request](https://github.com/firebase/firebase-admin-java/pull/new) and fill out the provided template. **If you want to implement a new feature, please open an issue with a proposal first so that we can @@ -127,27 +127,90 @@ mvn test Integration tests are also written using Junit4. They coexist with the unit tests in the `src/test` subdirectory. Integration tests follow the naming convention `*IT.java` (e.g. `DataTestIT.java`), which enables the Maven Surefire and Failsafe plugins to differentiate between the two types of -tests. Integration tests are executed against a real life Firebase project, and therefore -requires an Internet connection. - -Create a new project in the [Firebase console](https://console.firebase.google.com/) if you do -not already have one. Use a separate, dedicated project for integration tests since the test suite -makes a large number of writes to the Firebase realtime database. Download the service account -private key from the "Settings > Service Accounts" page of the project, and save it as -`integration_cert.json` at the root of the codebase. Grant your service account the `Firebase -Authentication Admin` role at -[Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin). This is -required to ensure that exported user records contain the password hashes of the user accounts. -Also obtain the web API key of the project from the "Settings > General" page, and save it as -`integration_apikey.txt` at the root of the codebase. - -Some of the integration tests require an -[Identity Platform](https://cloud.google.com/identity-platform/) project with multi-tenancy -[enabled](https://cloud.google.com/identity-platform/docs/multi-tenancy-quickstart#enabling_multi-tenancy). -An existing Firebase project can be upgraded to an Identity Platform project without losing any -functionality via the -[Identity Platform Marketplace Page](https://console.cloud.google.com/customer-identity). Note that -charges may be incurred for active users beyond the Identity Platform free tier. +tests. + +Integration tests are executed against a real life Firebase project. If you do not already +have one suitable for running the tests against, you can create a new project in the +[Firebase Console](https://console.firebase.google.com) following the setup guide below. +If you already have a Firebase project, you'll need to obtain credentials to communicate and +authorize access to your Firebase project: + +1. Service account certificate: This allows access to your Firebase project through a service account +which is required for all integration tests. This can be downloaded as a JSON file from the +**Settings > Service Accounts** tab of the Firebase console when you click the +**Generate new private key** button. Copy the file into the repo so it's available at +`integration_cert.json`. + > **Note:** Service accounts should be carefully managed and their keys should never be stored in publicly accessible source code or repositories. + + +2. Web API key: This allows for Auth sign-in needed for some Authentication and Tenant Management +integration tests. This is displayed in the **Settings > General** tab of the Firebase console +after enabling Authentication as described in the steps below. Copy it and save to a new text +file at `integration_apikey.txt`. + + +Set up your Firebase project as follows: + + +1. Enable Authentication: + 1. Go to the Firebase Console, and select **Authentication** from the **Build** menu. + 2. Click on **Get Started**. + 3. Select **Sign-in method > Add new provider > Email/Password** then enable both the + **Email/Password** and **Email link (passwordless sign-in)** options. + + +2. Enable Firestore: + 1. Go to the Firebase Console, and select **Firestore Database** from the **Build** menu. + 2. Click on the **Create database** button. You can choose to set up Firestore either in + the production mode or in the test mode. + + +3. Enable Realtime Database: + 1. Go to the Firebase Console, and select **Realtime Database** from the **Build** menu. + 2. Click on the **Create Database** button. You can choose to set up the Realtime Database + either in the locked mode or in the test mode. + + > **Note:** Integration tests are not run against the default Realtime Database reference and are + instead run against a database created at `https://{PROJECT_ID}.firebaseio.com`. + This second Realtime Database reference is created in the following steps. + + 3. In the **Data** tab click on the kebab menu (3 dots) and select **Create Database**. + 4. Enter your Project ID (Found in the **General** tab in **Account Settings**) as the + **Realtime Database reference**. Again, you can choose to set up the Realtime Database + either in the locked mode or in the test mode. + + +4. Enable Storage: + 1. Go to the Firebase Console, and select **Storage** from the **Build** menu. + 2. Click on the **Get started** button. You can choose to set up Cloud Storage + either in the production mode or in the test mode. + + +5. Enable the IAM API: + 1. Go to the [Google Cloud console](https://console.cloud.google.com) + and make sure your Firebase project is selected. + 2. Select **APIs & Services** from the main menu, and click the + **ENABLE APIS AND SERVICES** button. + 3. Search for and enable **Identity and Access Management (IAM) API** by Google Enterprise API. + + +6. Enable Tenant Management: + 1. Go to + [Google Cloud console | Identity Platform](https://console.cloud.google.com/customer-identity/) + and if it is not already enabled, click **Enable**. + 2. Then + [enable multi-tenancy](https://cloud.google.com/identity-platform/docs/multi-tenancy-quickstart#enabling_multi-tenancy) + for your project. + + +7. Ensure your service account has the **Firebase Authentication Admin** role. This is required +to ensure that exported user records contain the password hashes of the user accounts: + 1. Go to [Google Cloud console | IAM & admin](https://console.cloud.google.com/iam-admin). + 2. Find your service account in the list. If not added click the pencil icon to edit its + permissions. + 3. Click **ADD ANOTHER ROLE** and choose **Firebase Authentication Admin**. + 4. Click **SAVE**. + Now run the following command to invoke the integration test suite: diff --git a/NOTICE.txt b/NOTICE.txt index b6c5d02b2..f876712e4 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Firebase Admin Java SDK -Copyright 2019 Google Inc. +Copyright 2019 Google LLC This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/checkstyle.xml b/checkstyle.xml index 77e2dba54..c2a35946a 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,6 +1,6 @@ maven-javadoc-plugin - 3.5.0 + 3.12.0 site @@ -131,7 +124,7 @@ maven-antrun-plugin - 3.1.0 + 3.2.0 site @@ -157,7 +150,7 @@ maven-gpg-plugin - 3.0.1 + 3.2.8 sign-artifacts @@ -189,7 +182,7 @@ org.jacoco jacoco-maven-plugin - 0.8.8 + 0.8.15 pre-unit-test @@ -215,7 +208,7 @@ org.codehaus.mojo exec-maven-plugin - 3.1.0 + 3.6.3 test @@ -270,7 +263,7 @@ maven-compiler-plugin - 3.11.0 + 3.15.0 1.8 1.8 @@ -280,7 +273,7 @@ maven-surefire-plugin - 2.22.2 + 3.5.6 ${skipUTs} @@ -289,7 +282,7 @@ maven-source-plugin - 3.2.1 + 3.4.0 attach-sources @@ -301,7 +294,7 @@ maven-javadoc-plugin - 3.5.0 + 3.12.0 attach-javadocs @@ -337,7 +330,7 @@ maven-failsafe-plugin - 2.22.2 + 3.5.6 @@ -350,14 +343,14 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.11.0 true - ossrh - https://oss.sonatype.org/ - true + central + true + published @@ -367,7 +360,7 @@ maven-project-info-reports-plugin - 3.4.2 + 3.9.0 @@ -385,14 +378,7 @@ com.google.cloud libraries-bom - 26.10.0 - pom - import - - - com.google.api-client - google-api-client-bom - 2.2.0 + 26.83.0 pom import @@ -401,6 +387,10 @@ + + com.google.auth + google-auth-library-oauth2-http + com.google.api-client google-api-client @@ -417,10 +407,6 @@ com.google.api api-common - - com.google.auth - google-auth-library-oauth2-http - com.google.cloud google-cloud-storage @@ -438,7 +424,7 @@ org.slf4j slf4j-api - 2.0.6 + 2.0.18 io.netty @@ -455,6 +441,16 @@ netty-transport ${netty.version} + + org.apache.httpcomponents.client5 + httpclient5 + 5.3.1 + + + com.nimbusds + nimbus-jose-jwt + 10.9.1 + @@ -482,5 +478,11 @@ 2.1.1 test + + org.hamcrest + hamcrest + 3.0 + test + diff --git a/prepare_release.sh b/prepare_release.sh index b531557ef..c30d3dbe2 100755 --- a/prepare_release.sh +++ b/prepare_release.sh @@ -1,4 +1,4 @@ -# Copyright 2018 Google Inc. +# 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. @@ -66,14 +66,13 @@ if [[ $(git status --porcelain) ]]; then #exit 1 fi -echo "[INFO] Updating the master branch" -git checkout master -git pull origin master +echo "[INFO] Updating the main branch" +git fetch origin main TIMESTAMP=$(date +%s) RELEASE_BRANCH="release-${TIMESTAMP}" echo "[INFO] Creating new release branch: ${RELEASE_BRANCH}" -git checkout -b ${RELEASE_BRANCH} master +git checkout -b ${RELEASE_BRANCH} origin/main ################################# diff --git a/src/main/java/com/google/firebase/ErrorCode.java b/src/main/java/com/google/firebase/ErrorCode.java index 0eaa9cec2..2eda54054 100644 --- a/src/main/java/com/google/firebase/ErrorCode.java +++ b/src/main/java/com/google/firebase/ErrorCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/FirebaseApp.java b/src/main/java/com/google/firebase/FirebaseApp.java index 27727c366..726e2d1bd 100644 --- a/src/main/java/com/google/firebase/FirebaseApp.java +++ b/src/main/java/com/google/firebase/FirebaseApp.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/FirebaseAppLifecycleListener.java b/src/main/java/com/google/firebase/FirebaseAppLifecycleListener.java index 6493edb60..b347bfe95 100644 --- a/src/main/java/com/google/firebase/FirebaseAppLifecycleListener.java +++ b/src/main/java/com/google/firebase/FirebaseAppLifecycleListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/FirebaseException.java b/src/main/java/com/google/firebase/FirebaseException.java index a5bb80424..ce4325150 100644 --- a/src/main/java/com/google/firebase/FirebaseException.java +++ b/src/main/java/com/google/firebase/FirebaseException.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java index 8e7a29ccb..a994b98c2 100644 --- a/src/main/java/com/google/firebase/FirebaseOptions.java +++ b/src/main/java/com/google/firebase/FirebaseOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,6 +82,7 @@ public GoogleCredentials get() { private final HttpTransport httpTransport; private final int connectTimeout; private final int readTimeout; + private final int writeTimeout; private final JsonFactory jsonFactory; private final ThreadManager threadManager; private final FirestoreOptions firestoreOptions; @@ -112,6 +113,8 @@ private FirebaseOptions(@NonNull final FirebaseOptions.Builder builder) { this.connectTimeout = builder.connectTimeout; checkArgument(builder.readTimeout >= 0); this.readTimeout = builder.readTimeout; + checkArgument(builder.writeTimeout >= 0); + this.writeTimeout = builder.writeTimeout; this.firestoreOptions = builder.firestoreOptions; } @@ -198,8 +201,7 @@ public int getConnectTimeout() { } /** - * Returns the read timeout in milliseconds, which is applied to outgoing REST calls - * made by the SDK. + * Returns the read timeout applied to outgoing REST calls in milliseconds. * * @return Read timeout in milliseconds. 0 indicates an infinite timeout. */ @@ -207,6 +209,15 @@ public int getReadTimeout() { return readTimeout; } + /** + * Returns the write timeout applied to outgoing REST calls in milliseconds. + * + * @return Write timeout in milliseconds. 0 indicates an infinite timeout. + */ + public int getWriteTimeout() { + return writeTimeout; + } + @NonNull ThreadManager getThreadManager() { return threadManager; @@ -260,6 +271,7 @@ public static final class Builder { private ThreadManager threadManager; private int connectTimeout; private int readTimeout; + private int writeTimeout; /** * Constructs an empty builder. @@ -289,6 +301,7 @@ public Builder(FirebaseOptions options) { threadManager = options.threadManager; connectTimeout = options.connectTimeout; readTimeout = options.readTimeout; + writeTimeout = options.writeTimeout; firestoreOptions = options.firestoreOptions; } @@ -495,6 +508,19 @@ public Builder setReadTimeout(int readTimeout) { return this; } + /** + * Sets the write timeout for outgoing HTTP (REST) calls made by the SDK. This does not affect + * the {@link com.google.firebase.database.FirebaseDatabase} and + * {@link com.google.firebase.cloud.FirestoreClient} APIs. + * + * @param writeTimeout Write timeout in milliseconds. Must not be negative. + * @return This Builder instance is returned so subsequent calls can be chained. + */ + public Builder setWriteTimeout(int writeTimeout) { + this.writeTimeout = writeTimeout; + return this; + } + /** * Builds the {@link FirebaseOptions} instance from the previously set options. * diff --git a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java index 0d3008b18..3dff2764c 100644 --- a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java +++ b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/IncomingHttpResponse.java b/src/main/java/com/google/firebase/IncomingHttpResponse.java index cfeac5e70..794479040 100644 --- a/src/main/java/com/google/firebase/IncomingHttpResponse.java +++ b/src/main/java/com/google/firebase/IncomingHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/OutgoingHttpRequest.java b/src/main/java/com/google/firebase/OutgoingHttpRequest.java index 44af4bff0..fdaee7458 100644 --- a/src/main/java/com/google/firebase/OutgoingHttpRequest.java +++ b/src/main/java/com/google/firebase/OutgoingHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/ThreadManager.java b/src/main/java/com/google/firebase/ThreadManager.java index 8903101f6..29e7b42d0 100644 --- a/src/main/java/com/google/firebase/ThreadManager.java +++ b/src/main/java/com/google/firebase/ThreadManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java index d1b4a513e..44fe9b7d2 100644 --- a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java @@ -600,9 +600,10 @@ private CallableOperation getUserByProviderUi // Although we don't really advertise it, we want to also handle // non-federated idps with this call. So if we detect one of them, we'll // reroute this request appropriately. - if (providerId == "phone") { + if ("phone".equals(providerId)) { return this.getUserByPhoneNumberOp(uid); - } else if (providerId == "email") { + } + if ("email".equals(providerId)) { return this.getUserByEmailOp(uid); } diff --git a/src/main/java/com/google/firebase/auth/ActionCodeSettings.java b/src/main/java/com/google/firebase/auth/ActionCodeSettings.java index 0b102b7a3..22dfda8f2 100644 --- a/src/main/java/com/google/firebase/auth/ActionCodeSettings.java +++ b/src/main/java/com/google/firebase/auth/ActionCodeSettings.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -51,6 +51,9 @@ private ActionCodeSettings(Builder builder) { if (!Strings.isNullOrEmpty(builder.dynamicLinkDomain)) { properties.put("dynamicLinkDomain", builder.dynamicLinkDomain); } + if (!Strings.isNullOrEmpty(builder.linkDomain)) { + properties.put("linkDomain", builder.linkDomain); + } if (!Strings.isNullOrEmpty(builder.iosBundleId)) { properties.put("iOSBundleId", builder.iosBundleId); } @@ -84,6 +87,7 @@ public static final class Builder { private String url; private boolean handleCodeInApp; private String dynamicLinkDomain; + private String linkDomain; private String iosBundleId; private String androidPackageName; private String androidMinimumVersion; @@ -135,12 +139,28 @@ public Builder setHandleCodeInApp(boolean handleCodeInApp) { * * @param dynamicLinkDomain Firebase Dynamic Link domain string. * @return This builder. + * @deprecated Use {@link #setLinkDomain(String)} instead. */ + @Deprecated public Builder setDynamicLinkDomain(String dynamicLinkDomain) { this.dynamicLinkDomain = dynamicLinkDomain; return this; } + /** + * Sets the link domain to use for the current link if it is to be opened using + * {@code handleCodeInApp}, as multiple link domains can be configured per project. This + * setting provides the ability to explicitly choose one. If none is provided, the default + * Firebase Hosting domain will be used. + * + * @param linkDomain Link domain string. + * @return This builder. + */ + public Builder setLinkDomain(String linkDomain) { + this.linkDomain = linkDomain; + return this; + } + /** * Sets the bundle ID of the iOS app where the link should be handled if the * application is already installed on the device. diff --git a/src/main/java/com/google/firebase/auth/AuthErrorCode.java b/src/main/java/com/google/firebase/auth/AuthErrorCode.java index 90b5da1a2..e612d9f7e 100644 --- a/src/main/java/com/google/firebase/auth/AuthErrorCode.java +++ b/src/main/java/com/google/firebase/auth/AuthErrorCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. @@ -58,6 +58,11 @@ public enum AuthErrorCode { */ INVALID_DYNAMIC_LINK_DOMAIN, + /** + * The provided hosting link domain is not configured or authorized for the current project. + */ + INVALID_HOSTING_LINK_DOMAIN, + /** * The specified ID token is invalid. */ diff --git a/src/main/java/com/google/firebase/auth/DeleteUsersResult.java b/src/main/java/com/google/firebase/auth/DeleteUsersResult.java index e8ca7dba5..0b911e1aa 100644 --- a/src/main/java/com/google/firebase/auth/DeleteUsersResult.java +++ b/src/main/java/com/google/firebase/auth/DeleteUsersResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/EmailIdentifier.java b/src/main/java/com/google/firebase/auth/EmailIdentifier.java index 8e729c220..2786d5117 100644 --- a/src/main/java/com/google/firebase/auth/EmailIdentifier.java +++ b/src/main/java/com/google/firebase/auth/EmailIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/ErrorInfo.java b/src/main/java/com/google/firebase/auth/ErrorInfo.java index 7f7a5e346..cd150b63b 100644 --- a/src/main/java/com/google/firebase/auth/ErrorInfo.java +++ b/src/main/java/com/google/firebase/auth/ErrorInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/ExportedUserRecord.java b/src/main/java/com/google/firebase/auth/ExportedUserRecord.java index 9fdfc9c5e..341080da3 100644 --- a/src/main/java/com/google/firebase/auth/ExportedUserRecord.java +++ b/src/main/java/com/google/firebase/auth/ExportedUserRecord.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuthException.java b/src/main/java/com/google/firebase/auth/FirebaseAuthException.java index 53c980668..915abe62e 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseAuthException.java +++ b/src/main/java/com/google/firebase/auth/FirebaseAuthException.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/FirebaseToken.java b/src/main/java/com/google/firebase/auth/FirebaseToken.java index 835fa41c9..ec1827b2d 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseToken.java +++ b/src/main/java/com/google/firebase/auth/FirebaseToken.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,12 +37,12 @@ public final class FirebaseToken { this.claims = ImmutableMap.copyOf(claims); } - /** Returns the Uid for the this token. */ + /** Returns the Uid for this token. */ public String getUid() { return (String) claims.get("sub"); } - /** Returns the tenant ID for the this token. */ + /** Returns the tenant ID for this token. */ public String getTenantId() { Map firebase = (Map) claims.get("firebase"); if (firebase == null) { @@ -51,7 +51,7 @@ public String getTenantId() { return (String) firebase.get("tenant"); } - /** Returns the Issuer for the this token. */ + /** Returns the Issuer for this token. */ public String getIssuer() { return (String) claims.get("iss"); } diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java b/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java index 873dbe7ac..fb2e2d279 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java +++ b/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifier.java b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifier.java index e73f82381..334fa60b9 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifier.java +++ b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java index 9a3e1ae15..421ea2f8c 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java +++ b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java index 195527841..0647084c8 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java +++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/GetUsersResult.java b/src/main/java/com/google/firebase/auth/GetUsersResult.java index 3ceec01cb..a98b87b2e 100644 --- a/src/main/java/com/google/firebase/auth/GetUsersResult.java +++ b/src/main/java/com/google/firebase/auth/GetUsersResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/ImportUserRecord.java b/src/main/java/com/google/firebase/auth/ImportUserRecord.java index 5ce57fcc9..edde53bb5 100644 --- a/src/main/java/com/google/firebase/auth/ImportUserRecord.java +++ b/src/main/java/com/google/firebase/auth/ImportUserRecord.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/ListUsersPage.java b/src/main/java/com/google/firebase/auth/ListUsersPage.java index ba727af5a..f442bf85b 100644 --- a/src/main/java/com/google/firebase/auth/ListUsersPage.java +++ b/src/main/java/com/google/firebase/auth/ListUsersPage.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/PhoneIdentifier.java b/src/main/java/com/google/firebase/auth/PhoneIdentifier.java index bdc84fe92..f387a8abd 100644 --- a/src/main/java/com/google/firebase/auth/PhoneIdentifier.java +++ b/src/main/java/com/google/firebase/auth/PhoneIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/ProviderIdentifier.java b/src/main/java/com/google/firebase/auth/ProviderIdentifier.java index 25e00026d..cb44e0c0a 100644 --- a/src/main/java/com/google/firebase/auth/ProviderIdentifier.java +++ b/src/main/java/com/google/firebase/auth/ProviderIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/ProviderUserInfo.java b/src/main/java/com/google/firebase/auth/ProviderUserInfo.java index d9663e2d3..d37e24244 100644 --- a/src/main/java/com/google/firebase/auth/ProviderUserInfo.java +++ b/src/main/java/com/google/firebase/auth/ProviderUserInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java b/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java index 9a99fd5df..b693cab6c 100644 --- a/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java +++ b/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/SessionCookieOptions.java b/src/main/java/com/google/firebase/auth/SessionCookieOptions.java index d630780e8..6c55d242f 100644 --- a/src/main/java/com/google/firebase/auth/SessionCookieOptions.java +++ b/src/main/java/com/google/firebase/auth/SessionCookieOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/UidIdentifier.java b/src/main/java/com/google/firebase/auth/UidIdentifier.java index a4f7069d9..1819c11d3 100644 --- a/src/main/java/com/google/firebase/auth/UidIdentifier.java +++ b/src/main/java/com/google/firebase/auth/UidIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/UserIdentifier.java b/src/main/java/com/google/firebase/auth/UserIdentifier.java index 7ec9699e6..96d925703 100644 --- a/src/main/java/com/google/firebase/auth/UserIdentifier.java +++ b/src/main/java/com/google/firebase/auth/UserIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/UserImportHash.java b/src/main/java/com/google/firebase/auth/UserImportHash.java index 443c6f858..21fea1d5f 100644 --- a/src/main/java/com/google/firebase/auth/UserImportHash.java +++ b/src/main/java/com/google/firebase/auth/UserImportHash.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/UserImportOptions.java b/src/main/java/com/google/firebase/auth/UserImportOptions.java index 86accf91b..06567cfbb 100644 --- a/src/main/java/com/google/firebase/auth/UserImportOptions.java +++ b/src/main/java/com/google/firebase/auth/UserImportOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/UserImportResult.java b/src/main/java/com/google/firebase/auth/UserImportResult.java index 143b0618b..07c1b2ca2 100644 --- a/src/main/java/com/google/firebase/auth/UserImportResult.java +++ b/src/main/java/com/google/firebase/auth/UserImportResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/UserInfo.java b/src/main/java/com/google/firebase/auth/UserInfo.java index a95e5ecdc..2eb242caf 100644 --- a/src/main/java/com/google/firebase/auth/UserInfo.java +++ b/src/main/java/com/google/firebase/auth/UserInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/UserMetadata.java b/src/main/java/com/google/firebase/auth/UserMetadata.java index 85a24a0fd..577ce3ba6 100644 --- a/src/main/java/com/google/firebase/auth/UserMetadata.java +++ b/src/main/java/com/google/firebase/auth/UserMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/UserProvider.java b/src/main/java/com/google/firebase/auth/UserProvider.java index a13e0ecc1..278fd341d 100644 --- a/src/main/java/com/google/firebase/auth/UserProvider.java +++ b/src/main/java/com/google/firebase/auth/UserProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/UserRecord.java b/src/main/java/com/google/firebase/auth/UserRecord.java index ac962c52d..28c901378 100644 --- a/src/main/java/com/google/firebase/auth/UserRecord.java +++ b/src/main/java/com/google/firebase/auth/UserRecord.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -487,7 +487,7 @@ public UpdateRequest setPhoneNumber(@Nullable String phone) { // *and* by setting providersToUnlink to include 'phone', then we'll reject that. Though // it might also be reasonable to relax this restriction and just unlink it. for (String dp : deleteProviderIterable) { - if (dp == "phone") { + if ("phone".equals(dp)) { throw new IllegalArgumentException( "Both UpdateRequest.setPhoneNumber(null) and " + "UpdateRequest.setProvidersToUnlink(['phone']) were set. To unlink from a " @@ -601,7 +601,7 @@ public UpdateRequest setProvidersToUnlink(Iterable providerIds) { for (String id : providerIds) { checkArgument(!Strings.isNullOrEmpty(id), "providerIds must not be null or empty"); - if (id == "phone" && properties.containsKey("phoneNumber") + if ("phone".equals(id) && properties.containsKey("phoneNumber") && properties.get("phoneNumber") == null) { // If we've been told to unlink the phone provider both via setting phoneNumber to null // *and* by setting providersToUnlink to include 'phone', then we'll reject that. Though diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java index 8edcf11b7..22c6a6150 100644 --- a/src/main/java/com/google/firebase/auth/hash/Argon2.java +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google Inc. + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/hash/Bcrypt.java b/src/main/java/com/google/firebase/auth/hash/Bcrypt.java index 9c55d8d56..994484995 100644 --- a/src/main/java/com/google/firebase/auth/hash/Bcrypt.java +++ b/src/main/java/com/google/firebase/auth/hash/Bcrypt.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/Hmac.java b/src/main/java/com/google/firebase/auth/hash/Hmac.java index d55879389..0a2b2bd8f 100644 --- a/src/main/java/com/google/firebase/auth/hash/Hmac.java +++ b/src/main/java/com/google/firebase/auth/hash/Hmac.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/HmacMd5.java b/src/main/java/com/google/firebase/auth/hash/HmacMd5.java index b2ffdb852..7b1da7a06 100644 --- a/src/main/java/com/google/firebase/auth/hash/HmacMd5.java +++ b/src/main/java/com/google/firebase/auth/hash/HmacMd5.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/HmacSha1.java b/src/main/java/com/google/firebase/auth/hash/HmacSha1.java index 964e5e60d..16d6822fb 100644 --- a/src/main/java/com/google/firebase/auth/hash/HmacSha1.java +++ b/src/main/java/com/google/firebase/auth/hash/HmacSha1.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/HmacSha256.java b/src/main/java/com/google/firebase/auth/hash/HmacSha256.java index 92917e6f3..2d8df3bfa 100644 --- a/src/main/java/com/google/firebase/auth/hash/HmacSha256.java +++ b/src/main/java/com/google/firebase/auth/hash/HmacSha256.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/HmacSha512.java b/src/main/java/com/google/firebase/auth/hash/HmacSha512.java index b5a0e09ec..1bed76e1f 100644 --- a/src/main/java/com/google/firebase/auth/hash/HmacSha512.java +++ b/src/main/java/com/google/firebase/auth/hash/HmacSha512.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/auth/hash/Md5.java b/src/main/java/com/google/firebase/auth/hash/Md5.java index 353b07f01..3468953db 100644 --- a/src/main/java/com/google/firebase/auth/hash/Md5.java +++ b/src/main/java/com/google/firebase/auth/hash/Md5.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/Pbkdf2Sha256.java b/src/main/java/com/google/firebase/auth/hash/Pbkdf2Sha256.java index 6c3ffeff2..27db34921 100644 --- a/src/main/java/com/google/firebase/auth/hash/Pbkdf2Sha256.java +++ b/src/main/java/com/google/firebase/auth/hash/Pbkdf2Sha256.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/PbkdfSha1.java b/src/main/java/com/google/firebase/auth/hash/PbkdfSha1.java index 647a365b3..fd9e2f68d 100644 --- a/src/main/java/com/google/firebase/auth/hash/PbkdfSha1.java +++ b/src/main/java/com/google/firebase/auth/hash/PbkdfSha1.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/RepeatableHash.java b/src/main/java/com/google/firebase/auth/hash/RepeatableHash.java index bcf1a9524..bf90da8fb 100644 --- a/src/main/java/com/google/firebase/auth/hash/RepeatableHash.java +++ b/src/main/java/com/google/firebase/auth/hash/RepeatableHash.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/Scrypt.java b/src/main/java/com/google/firebase/auth/hash/Scrypt.java index 1c9612285..4d2445d8d 100644 --- a/src/main/java/com/google/firebase/auth/hash/Scrypt.java +++ b/src/main/java/com/google/firebase/auth/hash/Scrypt.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/Sha1.java b/src/main/java/com/google/firebase/auth/hash/Sha1.java index 9de01b0b8..e40112b8d 100644 --- a/src/main/java/com/google/firebase/auth/hash/Sha1.java +++ b/src/main/java/com/google/firebase/auth/hash/Sha1.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/Sha256.java b/src/main/java/com/google/firebase/auth/hash/Sha256.java index d0185195e..11100e636 100644 --- a/src/main/java/com/google/firebase/auth/hash/Sha256.java +++ b/src/main/java/com/google/firebase/auth/hash/Sha256.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/Sha512.java b/src/main/java/com/google/firebase/auth/hash/Sha512.java index f468abe1c..8c044600c 100644 --- a/src/main/java/com/google/firebase/auth/hash/Sha512.java +++ b/src/main/java/com/google/firebase/auth/hash/Sha512.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/hash/StandardScrypt.java b/src/main/java/com/google/firebase/auth/hash/StandardScrypt.java index 139fd114f..d489a9f4e 100644 --- a/src/main/java/com/google/firebase/auth/hash/StandardScrypt.java +++ b/src/main/java/com/google/firebase/auth/hash/StandardScrypt.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java index 4b9523c83..c298da4dc 100644 --- a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java +++ b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. @@ -73,6 +73,13 @@ final class AuthErrorHandler extends AbstractHttpErrorHandler{@code Firestore} + * {@code Firestore} * instance, which provides methods for updating and querying data in Firestore. * *

A Google Cloud project ID is required to access Firestore. FirestoreClient determines the @@ -60,7 +60,7 @@ private FirestoreClient(FirebaseApp app, String databaseId) { * same instance for all invocations. The Firestore instance and all references obtained from it * becomes unusable, once the default app is deleted. * - * @return A non-null {@code Firestore} + * @return A non-null {@code Firestore} * instance. */ @NonNull @@ -74,7 +74,7 @@ public static Firestore getFirestore() { * obtained from it becomes unusable, once the specified app is deleted. * * @param app A non-null {@link FirebaseApp}. - * @return A non-null {@code Firestore} + * @return A non-null {@code Firestore} * instance. */ @NonNull @@ -90,11 +90,11 @@ public static Firestore getFirestore(FirebaseApp app) { * * @param app A non-null {@link FirebaseApp}. * @param database - The name of database. - * @return A non-null {@code Firestore} + * @return A non-null {@code Firestore} * instance. */ @NonNull - static Firestore getFirestore(FirebaseApp app, String database) { + public static Firestore getFirestore(FirebaseApp app, String database) { return getInstance(app, database).firestore; } @@ -104,11 +104,11 @@ static Firestore getFirestore(FirebaseApp app, String database) { * references obtained from it becomes unusable, once the default app is deleted. * * @param database - The name of database. - * @return A non-null {@code Firestore} + * @return A non-null {@code Firestore} * instance. */ @NonNull - static Firestore getFirestore(String database) { + public static Firestore getFirestore(String database) { return getFirestore(FirebaseApp.getInstance(), database); } diff --git a/src/main/java/com/google/firebase/cloud/StorageClient.java b/src/main/java/com/google/firebase/cloud/StorageClient.java index 955abd7e2..4176b5329 100644 --- a/src/main/java/com/google/firebase/cloud/StorageClient.java +++ b/src/main/java/com/google/firebase/cloud/StorageClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,7 @@ public static synchronized StorageClient getInstance(FirebaseApp app) { * configured via {@link com.google.firebase.FirebaseOptions} when initializing the app. If * no bucket was configured via options, this method throws an exception. * - * @return a cloud storage {@code Bucket} + * @return a cloud storage {@code Bucket} * instance. * @throws IllegalArgumentException If no bucket is configured via FirebaseOptions, * or if the bucket does not exist. @@ -84,7 +84,7 @@ public Bucket bucket() { * Returns a cloud storage Bucket instance for the specified bucket name. * * @param name a non-null, non-empty bucket name. - * @return a cloud storage {@code Bucket} + * @return a cloud storage {@code Bucket} * instance. * @throws IllegalArgumentException If the bucket name is null, empty, or if the specified * bucket does not exist. diff --git a/src/main/java/com/google/firebase/database/ChildEventListener.java b/src/main/java/com/google/firebase/database/ChildEventListener.java index 65bcdfdce..21f74db54 100644 --- a/src/main/java/com/google/firebase/database/ChildEventListener.java +++ b/src/main/java/com/google/firebase/database/ChildEventListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/DataSnapshot.java b/src/main/java/com/google/firebase/database/DataSnapshot.java index f1f470505..64fa7f7b7 100644 --- a/src/main/java/com/google/firebase/database/DataSnapshot.java +++ b/src/main/java/com/google/firebase/database/DataSnapshot.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/DatabaseError.java b/src/main/java/com/google/firebase/database/DatabaseError.java index 456eb99e6..1b0deb059 100644 --- a/src/main/java/com/google/firebase/database/DatabaseError.java +++ b/src/main/java/com/google/firebase/database/DatabaseError.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/DatabaseException.java b/src/main/java/com/google/firebase/database/DatabaseException.java index c85e93878..a5aff67b8 100644 --- a/src/main/java/com/google/firebase/database/DatabaseException.java +++ b/src/main/java/com/google/firebase/database/DatabaseException.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/DatabaseReference.java b/src/main/java/com/google/firebase/database/DatabaseReference.java index 10dfe40ba..7a1bebac1 100644 --- a/src/main/java/com/google/firebase/database/DatabaseReference.java +++ b/src/main/java/com/google/firebase/database/DatabaseReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/Exclude.java b/src/main/java/com/google/firebase/database/Exclude.java index e4d90c7da..4eb65dc46 100644 --- a/src/main/java/com/google/firebase/database/Exclude.java +++ b/src/main/java/com/google/firebase/database/Exclude.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/FirebaseDatabase.java b/src/main/java/com/google/firebase/database/FirebaseDatabase.java index e59cd38fa..9dea16566 100644 --- a/src/main/java/com/google/firebase/database/FirebaseDatabase.java +++ b/src/main/java/com/google/firebase/database/FirebaseDatabase.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/GenericTypeIndicator.java b/src/main/java/com/google/firebase/database/GenericTypeIndicator.java index 3d9bb94d9..5a519d4d7 100644 --- a/src/main/java/com/google/firebase/database/GenericTypeIndicator.java +++ b/src/main/java/com/google/firebase/database/GenericTypeIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/IgnoreExtraProperties.java b/src/main/java/com/google/firebase/database/IgnoreExtraProperties.java index ff953fd88..ce5d92a66 100644 --- a/src/main/java/com/google/firebase/database/IgnoreExtraProperties.java +++ b/src/main/java/com/google/firebase/database/IgnoreExtraProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/InternalHelpers.java b/src/main/java/com/google/firebase/database/InternalHelpers.java index f5ef4e249..73a92f972 100644 --- a/src/main/java/com/google/firebase/database/InternalHelpers.java +++ b/src/main/java/com/google/firebase/database/InternalHelpers.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/MutableData.java b/src/main/java/com/google/firebase/database/MutableData.java index 7a0e7983b..862330357 100644 --- a/src/main/java/com/google/firebase/database/MutableData.java +++ b/src/main/java/com/google/firebase/database/MutableData.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/OnDisconnect.java b/src/main/java/com/google/firebase/database/OnDisconnect.java index 7b4cc0859..fc81e2d42 100644 --- a/src/main/java/com/google/firebase/database/OnDisconnect.java +++ b/src/main/java/com/google/firebase/database/OnDisconnect.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/PropertyName.java b/src/main/java/com/google/firebase/database/PropertyName.java index 70a8f1345..0b9a58cec 100644 --- a/src/main/java/com/google/firebase/database/PropertyName.java +++ b/src/main/java/com/google/firebase/database/PropertyName.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/Query.java b/src/main/java/com/google/firebase/database/Query.java index e22a4931a..9903cf525 100644 --- a/src/main/java/com/google/firebase/database/Query.java +++ b/src/main/java/com/google/firebase/database/Query.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/ServerValue.java b/src/main/java/com/google/firebase/database/ServerValue.java index de0bb6a0f..410860bc5 100644 --- a/src/main/java/com/google/firebase/database/ServerValue.java +++ b/src/main/java/com/google/firebase/database/ServerValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/ThrowOnExtraProperties.java b/src/main/java/com/google/firebase/database/ThrowOnExtraProperties.java index d0a354c4d..49e91ba78 100644 --- a/src/main/java/com/google/firebase/database/ThrowOnExtraProperties.java +++ b/src/main/java/com/google/firebase/database/ThrowOnExtraProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/Transaction.java b/src/main/java/com/google/firebase/database/Transaction.java index 525005b5c..e2733f26e 100644 --- a/src/main/java/com/google/firebase/database/Transaction.java +++ b/src/main/java/com/google/firebase/database/Transaction.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/ValueEventListener.java b/src/main/java/com/google/firebase/database/ValueEventListener.java index bb50e0e26..4b89b59a2 100644 --- a/src/main/java/com/google/firebase/database/ValueEventListener.java +++ b/src/main/java/com/google/firebase/database/ValueEventListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/annotations/NotNull.java b/src/main/java/com/google/firebase/database/annotations/NotNull.java index fe60cf688..3d79b48e3 100644 --- a/src/main/java/com/google/firebase/database/annotations/NotNull.java +++ b/src/main/java/com/google/firebase/database/annotations/NotNull.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/annotations/Nullable.java b/src/main/java/com/google/firebase/database/annotations/Nullable.java index 7c58d121c..da4e931d0 100644 --- a/src/main/java/com/google/firebase/database/annotations/Nullable.java +++ b/src/main/java/com/google/firebase/database/annotations/Nullable.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/annotations/package-info.java b/src/main/java/com/google/firebase/database/annotations/package-info.java index 23e1605a2..e23aff54e 100644 --- a/src/main/java/com/google/firebase/database/annotations/package-info.java +++ b/src/main/java/com/google/firebase/database/annotations/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/ArraySortedMap.java b/src/main/java/com/google/firebase/database/collection/ArraySortedMap.java index edc7afabe..46043e30e 100644 --- a/src/main/java/com/google/firebase/database/collection/ArraySortedMap.java +++ b/src/main/java/com/google/firebase/database/collection/ArraySortedMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/ImmutableSortedMap.java b/src/main/java/com/google/firebase/database/collection/ImmutableSortedMap.java index 525f9edc5..8beb8bb91 100644 --- a/src/main/java/com/google/firebase/database/collection/ImmutableSortedMap.java +++ b/src/main/java/com/google/firebase/database/collection/ImmutableSortedMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/ImmutableSortedMapIterator.java b/src/main/java/com/google/firebase/database/collection/ImmutableSortedMapIterator.java index 5229579d7..d901ef71f 100644 --- a/src/main/java/com/google/firebase/database/collection/ImmutableSortedMapIterator.java +++ b/src/main/java/com/google/firebase/database/collection/ImmutableSortedMapIterator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/ImmutableSortedSet.java b/src/main/java/com/google/firebase/database/collection/ImmutableSortedSet.java index f5ab4f7fb..ba6079602 100644 --- a/src/main/java/com/google/firebase/database/collection/ImmutableSortedSet.java +++ b/src/main/java/com/google/firebase/database/collection/ImmutableSortedSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/LLRBBlackValueNode.java b/src/main/java/com/google/firebase/database/collection/LLRBBlackValueNode.java index 99a59f980..a0e471637 100644 --- a/src/main/java/com/google/firebase/database/collection/LLRBBlackValueNode.java +++ b/src/main/java/com/google/firebase/database/collection/LLRBBlackValueNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/LLRBEmptyNode.java b/src/main/java/com/google/firebase/database/collection/LLRBEmptyNode.java index e1e72ff91..c2e04d033 100644 --- a/src/main/java/com/google/firebase/database/collection/LLRBEmptyNode.java +++ b/src/main/java/com/google/firebase/database/collection/LLRBEmptyNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/LLRBNode.java b/src/main/java/com/google/firebase/database/collection/LLRBNode.java index bb3214c7f..c8fe496ba 100644 --- a/src/main/java/com/google/firebase/database/collection/LLRBNode.java +++ b/src/main/java/com/google/firebase/database/collection/LLRBNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/LLRBRedValueNode.java b/src/main/java/com/google/firebase/database/collection/LLRBRedValueNode.java index 642d3cbf3..488fdb22b 100644 --- a/src/main/java/com/google/firebase/database/collection/LLRBRedValueNode.java +++ b/src/main/java/com/google/firebase/database/collection/LLRBRedValueNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/LLRBValueNode.java b/src/main/java/com/google/firebase/database/collection/LLRBValueNode.java index 091af493e..b8d29d16a 100644 --- a/src/main/java/com/google/firebase/database/collection/LLRBValueNode.java +++ b/src/main/java/com/google/firebase/database/collection/LLRBValueNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/RBTreeSortedMap.java b/src/main/java/com/google/firebase/database/collection/RBTreeSortedMap.java index 177eb2dcd..283bd2d8b 100644 --- a/src/main/java/com/google/firebase/database/collection/RBTreeSortedMap.java +++ b/src/main/java/com/google/firebase/database/collection/RBTreeSortedMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/StandardComparator.java b/src/main/java/com/google/firebase/database/collection/StandardComparator.java index 9f2cb985a..a4de5bd8d 100644 --- a/src/main/java/com/google/firebase/database/collection/StandardComparator.java +++ b/src/main/java/com/google/firebase/database/collection/StandardComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/collection/package-info.java b/src/main/java/com/google/firebase/database/collection/package-info.java index ab03ce9d9..cd9ec39a9 100644 --- a/src/main/java/com/google/firebase/database/collection/package-info.java +++ b/src/main/java/com/google/firebase/database/collection/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/CompoundHash.java b/src/main/java/com/google/firebase/database/connection/CompoundHash.java index 443a899fb..a3456dbd4 100644 --- a/src/main/java/com/google/firebase/database/connection/CompoundHash.java +++ b/src/main/java/com/google/firebase/database/connection/CompoundHash.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/Connection.java b/src/main/java/com/google/firebase/database/connection/Connection.java index 83e377304..111d3a897 100644 --- a/src/main/java/com/google/firebase/database/connection/Connection.java +++ b/src/main/java/com/google/firebase/database/connection/Connection.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/ConnectionAuthTokenProvider.java b/src/main/java/com/google/firebase/database/connection/ConnectionAuthTokenProvider.java index 8874238ed..21e0a3076 100644 --- a/src/main/java/com/google/firebase/database/connection/ConnectionAuthTokenProvider.java +++ b/src/main/java/com/google/firebase/database/connection/ConnectionAuthTokenProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/ConnectionContext.java b/src/main/java/com/google/firebase/database/connection/ConnectionContext.java index 574bc1df9..110935605 100644 --- a/src/main/java/com/google/firebase/database/connection/ConnectionContext.java +++ b/src/main/java/com/google/firebase/database/connection/ConnectionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/ConnectionUtils.java b/src/main/java/com/google/firebase/database/connection/ConnectionUtils.java index de04b58e6..6821bc5ef 100644 --- a/src/main/java/com/google/firebase/database/connection/ConnectionUtils.java +++ b/src/main/java/com/google/firebase/database/connection/ConnectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/Constants.java b/src/main/java/com/google/firebase/database/connection/Constants.java index 6ec9a3da0..6a42939cf 100644 --- a/src/main/java/com/google/firebase/database/connection/Constants.java +++ b/src/main/java/com/google/firebase/database/connection/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/HostInfo.java b/src/main/java/com/google/firebase/database/connection/HostInfo.java index 1bfa7758d..23be57cbe 100644 --- a/src/main/java/com/google/firebase/database/connection/HostInfo.java +++ b/src/main/java/com/google/firebase/database/connection/HostInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/ListenHashProvider.java b/src/main/java/com/google/firebase/database/connection/ListenHashProvider.java index 9ab58bb23..9be415379 100644 --- a/src/main/java/com/google/firebase/database/connection/ListenHashProvider.java +++ b/src/main/java/com/google/firebase/database/connection/ListenHashProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/PersistentConnection.java b/src/main/java/com/google/firebase/database/connection/PersistentConnection.java index eca676121..4a53a2261 100644 --- a/src/main/java/com/google/firebase/database/connection/PersistentConnection.java +++ b/src/main/java/com/google/firebase/database/connection/PersistentConnection.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java b/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java index fc79b4cfe..2ec6af5e8 100644 --- a/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java +++ b/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/RangeMerge.java b/src/main/java/com/google/firebase/database/connection/RangeMerge.java index eb74cf2a9..5a2508efe 100644 --- a/src/main/java/com/google/firebase/database/connection/RangeMerge.java +++ b/src/main/java/com/google/firebase/database/connection/RangeMerge.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/RequestResultCallback.java b/src/main/java/com/google/firebase/database/connection/RequestResultCallback.java index 53c77f87e..8051c23c6 100644 --- a/src/main/java/com/google/firebase/database/connection/RequestResultCallback.java +++ b/src/main/java/com/google/firebase/database/connection/RequestResultCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java b/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java index a421b90d4..88a2be670 100644 --- a/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java +++ b/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/package-info.java b/src/main/java/com/google/firebase/database/connection/package-info.java index e83511690..9bce1cc1d 100644 --- a/src/main/java/com/google/firebase/database/connection/package-info.java +++ b/src/main/java/com/google/firebase/database/connection/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java b/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java index f8f6bb77a..c92c8e763 100644 --- a/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java +++ b/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/connection/util/package-info.java b/src/main/java/com/google/firebase/database/connection/util/package-info.java index d1b554d62..f31b84af5 100644 --- a/src/main/java/com/google/firebase/database/connection/util/package-info.java +++ b/src/main/java/com/google/firebase/database/connection/util/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/AuthTokenProvider.java b/src/main/java/com/google/firebase/database/core/AuthTokenProvider.java index 15da9e7c0..b93e8154c 100644 --- a/src/main/java/com/google/firebase/database/core/AuthTokenProvider.java +++ b/src/main/java/com/google/firebase/database/core/AuthTokenProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/ChildEventRegistration.java b/src/main/java/com/google/firebase/database/core/ChildEventRegistration.java index 8b0460294..592aa1749 100644 --- a/src/main/java/com/google/firebase/database/core/ChildEventRegistration.java +++ b/src/main/java/com/google/firebase/database/core/ChildEventRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/CompoundWrite.java b/src/main/java/com/google/firebase/database/core/CompoundWrite.java index 22c049645..f8f135319 100644 --- a/src/main/java/com/google/firebase/database/core/CompoundWrite.java +++ b/src/main/java/com/google/firebase/database/core/CompoundWrite.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/Constants.java b/src/main/java/com/google/firebase/database/core/Constants.java index b30b45f3d..c9706e3aa 100644 --- a/src/main/java/com/google/firebase/database/core/Constants.java +++ b/src/main/java/com/google/firebase/database/core/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/Context.java b/src/main/java/com/google/firebase/database/core/Context.java index a0025f997..02e7fe7a8 100644 --- a/src/main/java/com/google/firebase/database/core/Context.java +++ b/src/main/java/com/google/firebase/database/core/Context.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/DatabaseConfig.java b/src/main/java/com/google/firebase/database/core/DatabaseConfig.java index 844f59266..4e4cb7cdf 100644 --- a/src/main/java/com/google/firebase/database/core/DatabaseConfig.java +++ b/src/main/java/com/google/firebase/database/core/DatabaseConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/EventRegistration.java b/src/main/java/com/google/firebase/database/core/EventRegistration.java index 815884303..5517f2ffb 100644 --- a/src/main/java/com/google/firebase/database/core/EventRegistration.java +++ b/src/main/java/com/google/firebase/database/core/EventRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/EventRegistrationZombieListener.java b/src/main/java/com/google/firebase/database/core/EventRegistrationZombieListener.java index bce5fecde..cebcddd6e 100644 --- a/src/main/java/com/google/firebase/database/core/EventRegistrationZombieListener.java +++ b/src/main/java/com/google/firebase/database/core/EventRegistrationZombieListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/EventTarget.java b/src/main/java/com/google/firebase/database/core/EventTarget.java index 2f0b91ad5..f87c6645d 100644 --- a/src/main/java/com/google/firebase/database/core/EventTarget.java +++ b/src/main/java/com/google/firebase/database/core/EventTarget.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java b/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java index 9b862ba86..e4c03cb24 100644 --- a/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java +++ b/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/JvmPlatform.java b/src/main/java/com/google/firebase/database/core/JvmPlatform.java index 2a226c078..fac2f6b93 100644 --- a/src/main/java/com/google/firebase/database/core/JvmPlatform.java +++ b/src/main/java/com/google/firebase/database/core/JvmPlatform.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/Path.java b/src/main/java/com/google/firebase/database/core/Path.java index b6fd451b9..22c2f0dde 100644 --- a/src/main/java/com/google/firebase/database/core/Path.java +++ b/src/main/java/com/google/firebase/database/core/Path.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/Platform.java b/src/main/java/com/google/firebase/database/core/Platform.java index 2d2742a62..e3a2353ed 100644 --- a/src/main/java/com/google/firebase/database/core/Platform.java +++ b/src/main/java/com/google/firebase/database/core/Platform.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/Repo.java b/src/main/java/com/google/firebase/database/core/Repo.java index 98777d04d..0850cca5a 100644 --- a/src/main/java/com/google/firebase/database/core/Repo.java +++ b/src/main/java/com/google/firebase/database/core/Repo.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/RepoInfo.java b/src/main/java/com/google/firebase/database/core/RepoInfo.java index 79f83346a..1d7e31d34 100644 --- a/src/main/java/com/google/firebase/database/core/RepoInfo.java +++ b/src/main/java/com/google/firebase/database/core/RepoInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/RepoManager.java b/src/main/java/com/google/firebase/database/core/RepoManager.java index a1d0eafce..e694fd943 100644 --- a/src/main/java/com/google/firebase/database/core/RepoManager.java +++ b/src/main/java/com/google/firebase/database/core/RepoManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/RunLoop.java b/src/main/java/com/google/firebase/database/core/RunLoop.java index 723f9430d..16b341da5 100644 --- a/src/main/java/com/google/firebase/database/core/RunLoop.java +++ b/src/main/java/com/google/firebase/database/core/RunLoop.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/ServerValues.java b/src/main/java/com/google/firebase/database/core/ServerValues.java index e484976f9..d4af3637b 100644 --- a/src/main/java/com/google/firebase/database/core/ServerValues.java +++ b/src/main/java/com/google/firebase/database/core/ServerValues.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/SnapshotHolder.java b/src/main/java/com/google/firebase/database/core/SnapshotHolder.java index 60d51bd84..55d497739 100644 --- a/src/main/java/com/google/firebase/database/core/SnapshotHolder.java +++ b/src/main/java/com/google/firebase/database/core/SnapshotHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/SparseSnapshotTree.java b/src/main/java/com/google/firebase/database/core/SparseSnapshotTree.java index e73c7587f..c47e31195 100644 --- a/src/main/java/com/google/firebase/database/core/SparseSnapshotTree.java +++ b/src/main/java/com/google/firebase/database/core/SparseSnapshotTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/SyncPoint.java b/src/main/java/com/google/firebase/database/core/SyncPoint.java index 8a3c91fa2..cd8bdbe9c 100644 --- a/src/main/java/com/google/firebase/database/core/SyncPoint.java +++ b/src/main/java/com/google/firebase/database/core/SyncPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/SyncTree.java b/src/main/java/com/google/firebase/database/core/SyncTree.java index f7da11750..01562caa2 100644 --- a/src/main/java/com/google/firebase/database/core/SyncTree.java +++ b/src/main/java/com/google/firebase/database/core/SyncTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/Tag.java b/src/main/java/com/google/firebase/database/core/Tag.java index e25fcb359..d25adb357 100644 --- a/src/main/java/com/google/firebase/database/core/Tag.java +++ b/src/main/java/com/google/firebase/database/core/Tag.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/ThreadPoolEventTarget.java b/src/main/java/com/google/firebase/database/core/ThreadPoolEventTarget.java index 34162fbe2..3a32d9167 100644 --- a/src/main/java/com/google/firebase/database/core/ThreadPoolEventTarget.java +++ b/src/main/java/com/google/firebase/database/core/ThreadPoolEventTarget.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/UserWriteRecord.java b/src/main/java/com/google/firebase/database/core/UserWriteRecord.java index 02c46a2d9..21fa08a79 100644 --- a/src/main/java/com/google/firebase/database/core/UserWriteRecord.java +++ b/src/main/java/com/google/firebase/database/core/UserWriteRecord.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/ValidationPath.java b/src/main/java/com/google/firebase/database/core/ValidationPath.java index 3ea3d03c6..bc3620ec3 100644 --- a/src/main/java/com/google/firebase/database/core/ValidationPath.java +++ b/src/main/java/com/google/firebase/database/core/ValidationPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/ValueEventRegistration.java b/src/main/java/com/google/firebase/database/core/ValueEventRegistration.java index 839ce5344..d46580daa 100644 --- a/src/main/java/com/google/firebase/database/core/ValueEventRegistration.java +++ b/src/main/java/com/google/firebase/database/core/ValueEventRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/WriteTree.java b/src/main/java/com/google/firebase/database/core/WriteTree.java index 86ba8536f..95a57d4d8 100644 --- a/src/main/java/com/google/firebase/database/core/WriteTree.java +++ b/src/main/java/com/google/firebase/database/core/WriteTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/WriteTreeRef.java b/src/main/java/com/google/firebase/database/core/WriteTreeRef.java index d66f041b4..3123987ea 100644 --- a/src/main/java/com/google/firebase/database/core/WriteTreeRef.java +++ b/src/main/java/com/google/firebase/database/core/WriteTreeRef.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/ZombieEventManager.java b/src/main/java/com/google/firebase/database/core/ZombieEventManager.java index 2b23b89fa..c488c4d0b 100644 --- a/src/main/java/com/google/firebase/database/core/ZombieEventManager.java +++ b/src/main/java/com/google/firebase/database/core/ZombieEventManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/operation/AckUserWrite.java b/src/main/java/com/google/firebase/database/core/operation/AckUserWrite.java index 3016e3b2f..0ffeea3e6 100644 --- a/src/main/java/com/google/firebase/database/core/operation/AckUserWrite.java +++ b/src/main/java/com/google/firebase/database/core/operation/AckUserWrite.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/operation/ListenComplete.java b/src/main/java/com/google/firebase/database/core/operation/ListenComplete.java index 816881bb4..fac4dbdbf 100644 --- a/src/main/java/com/google/firebase/database/core/operation/ListenComplete.java +++ b/src/main/java/com/google/firebase/database/core/operation/ListenComplete.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/operation/Merge.java b/src/main/java/com/google/firebase/database/core/operation/Merge.java index 1f859dfa2..520433af5 100644 --- a/src/main/java/com/google/firebase/database/core/operation/Merge.java +++ b/src/main/java/com/google/firebase/database/core/operation/Merge.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/operation/Operation.java b/src/main/java/com/google/firebase/database/core/operation/Operation.java index 951dce939..8a42cd89c 100644 --- a/src/main/java/com/google/firebase/database/core/operation/Operation.java +++ b/src/main/java/com/google/firebase/database/core/operation/Operation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/operation/OperationSource.java b/src/main/java/com/google/firebase/database/core/operation/OperationSource.java index e73fca9a5..fa9aadd82 100644 --- a/src/main/java/com/google/firebase/database/core/operation/OperationSource.java +++ b/src/main/java/com/google/firebase/database/core/operation/OperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/operation/Overwrite.java b/src/main/java/com/google/firebase/database/core/operation/Overwrite.java index 6177e576a..75cc15e69 100644 --- a/src/main/java/com/google/firebase/database/core/operation/Overwrite.java +++ b/src/main/java/com/google/firebase/database/core/operation/Overwrite.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/operation/package-info.java b/src/main/java/com/google/firebase/database/core/operation/package-info.java index d92e9cf5e..91d53d0b7 100644 --- a/src/main/java/com/google/firebase/database/core/operation/package-info.java +++ b/src/main/java/com/google/firebase/database/core/operation/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/package-info.java b/src/main/java/com/google/firebase/database/core/package-info.java index 62dcee2ef..4598d4bfc 100644 --- a/src/main/java/com/google/firebase/database/core/package-info.java +++ b/src/main/java/com/google/firebase/database/core/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/CachePolicy.java b/src/main/java/com/google/firebase/database/core/persistence/CachePolicy.java index c82b5fdcb..52997369f 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/CachePolicy.java +++ b/src/main/java/com/google/firebase/database/core/persistence/CachePolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java b/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java index 40e1b99dc..a528c2767 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java +++ b/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/LRUCachePolicy.java b/src/main/java/com/google/firebase/database/core/persistence/LRUCachePolicy.java index 744c34c48..2f62d38b3 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/LRUCachePolicy.java +++ b/src/main/java/com/google/firebase/database/core/persistence/LRUCachePolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/NoopPersistenceManager.java b/src/main/java/com/google/firebase/database/core/persistence/NoopPersistenceManager.java index 15bb3a970..da44d7ec8 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/NoopPersistenceManager.java +++ b/src/main/java/com/google/firebase/database/core/persistence/NoopPersistenceManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/PersistenceManager.java b/src/main/java/com/google/firebase/database/core/persistence/PersistenceManager.java index 61d11bfa0..68e7f0469 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/PersistenceManager.java +++ b/src/main/java/com/google/firebase/database/core/persistence/PersistenceManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/PersistenceStorageEngine.java b/src/main/java/com/google/firebase/database/core/persistence/PersistenceStorageEngine.java index 31ef88761..edc9abfd0 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/PersistenceStorageEngine.java +++ b/src/main/java/com/google/firebase/database/core/persistence/PersistenceStorageEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/PruneForest.java b/src/main/java/com/google/firebase/database/core/persistence/PruneForest.java index 64872d4f8..7d8499a92 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/PruneForest.java +++ b/src/main/java/com/google/firebase/database/core/persistence/PruneForest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/TrackedQuery.java b/src/main/java/com/google/firebase/database/core/persistence/TrackedQuery.java index 61169b4eb..7efeba5a8 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/TrackedQuery.java +++ b/src/main/java/com/google/firebase/database/core/persistence/TrackedQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java b/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java index 20278b947..d8b072dd1 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java +++ b/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/persistence/package-info.java b/src/main/java/com/google/firebase/database/core/persistence/package-info.java index 710cf90e9..5d7217861 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/package-info.java +++ b/src/main/java/com/google/firebase/database/core/persistence/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/utilities/ImmutableTree.java b/src/main/java/com/google/firebase/database/core/utilities/ImmutableTree.java index 617cbd0e4..e47d2f072 100644 --- a/src/main/java/com/google/firebase/database/core/utilities/ImmutableTree.java +++ b/src/main/java/com/google/firebase/database/core/utilities/ImmutableTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/utilities/Predicate.java b/src/main/java/com/google/firebase/database/core/utilities/Predicate.java index 667ac34c0..45478ace1 100644 --- a/src/main/java/com/google/firebase/database/core/utilities/Predicate.java +++ b/src/main/java/com/google/firebase/database/core/utilities/Predicate.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/utilities/Tree.java b/src/main/java/com/google/firebase/database/core/utilities/Tree.java index 54923ae09..598984c8d 100644 --- a/src/main/java/com/google/firebase/database/core/utilities/Tree.java +++ b/src/main/java/com/google/firebase/database/core/utilities/Tree.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/utilities/TreeNode.java b/src/main/java/com/google/firebase/database/core/utilities/TreeNode.java index 84df758b2..909c0d648 100644 --- a/src/main/java/com/google/firebase/database/core/utilities/TreeNode.java +++ b/src/main/java/com/google/firebase/database/core/utilities/TreeNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/utilities/package-info.java b/src/main/java/com/google/firebase/database/core/utilities/package-info.java index e17c4c192..57a9ee9ae 100644 --- a/src/main/java/com/google/firebase/database/core/utilities/package-info.java +++ b/src/main/java/com/google/firebase/database/core/utilities/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/CacheNode.java b/src/main/java/com/google/firebase/database/core/view/CacheNode.java index f51de2db4..abebe54a0 100644 --- a/src/main/java/com/google/firebase/database/core/view/CacheNode.java +++ b/src/main/java/com/google/firebase/database/core/view/CacheNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/CancelEvent.java b/src/main/java/com/google/firebase/database/core/view/CancelEvent.java index 660111c73..3625d18b2 100644 --- a/src/main/java/com/google/firebase/database/core/view/CancelEvent.java +++ b/src/main/java/com/google/firebase/database/core/view/CancelEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/Change.java b/src/main/java/com/google/firebase/database/core/view/Change.java index c69ca81a7..3c3a7e065 100644 --- a/src/main/java/com/google/firebase/database/core/view/Change.java +++ b/src/main/java/com/google/firebase/database/core/view/Change.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/DataEvent.java b/src/main/java/com/google/firebase/database/core/view/DataEvent.java index ae5f7fa22..cc1fd648c 100644 --- a/src/main/java/com/google/firebase/database/core/view/DataEvent.java +++ b/src/main/java/com/google/firebase/database/core/view/DataEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/Event.java b/src/main/java/com/google/firebase/database/core/view/Event.java index 2c2ea2522..46b6100e1 100644 --- a/src/main/java/com/google/firebase/database/core/view/Event.java +++ b/src/main/java/com/google/firebase/database/core/view/Event.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/EventGenerator.java b/src/main/java/com/google/firebase/database/core/view/EventGenerator.java index 0b28c3bac..3e9e54445 100644 --- a/src/main/java/com/google/firebase/database/core/view/EventGenerator.java +++ b/src/main/java/com/google/firebase/database/core/view/EventGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/EventRaiser.java b/src/main/java/com/google/firebase/database/core/view/EventRaiser.java index d2bbacee6..6cb0f1e3f 100644 --- a/src/main/java/com/google/firebase/database/core/view/EventRaiser.java +++ b/src/main/java/com/google/firebase/database/core/view/EventRaiser.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/QueryParams.java b/src/main/java/com/google/firebase/database/core/view/QueryParams.java index d361f8727..afa7f0a1a 100644 --- a/src/main/java/com/google/firebase/database/core/view/QueryParams.java +++ b/src/main/java/com/google/firebase/database/core/view/QueryParams.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/QuerySpec.java b/src/main/java/com/google/firebase/database/core/view/QuerySpec.java index 923a77d7d..3174ec087 100644 --- a/src/main/java/com/google/firebase/database/core/view/QuerySpec.java +++ b/src/main/java/com/google/firebase/database/core/view/QuerySpec.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/View.java b/src/main/java/com/google/firebase/database/core/view/View.java index 9aa05976f..2aa4fa1cd 100644 --- a/src/main/java/com/google/firebase/database/core/view/View.java +++ b/src/main/java/com/google/firebase/database/core/view/View.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/ViewCache.java b/src/main/java/com/google/firebase/database/core/view/ViewCache.java index 417fcbb41..f008348b1 100644 --- a/src/main/java/com/google/firebase/database/core/view/ViewCache.java +++ b/src/main/java/com/google/firebase/database/core/view/ViewCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/ViewProcessor.java b/src/main/java/com/google/firebase/database/core/view/ViewProcessor.java index 57fcf07c1..3b3aedaff 100644 --- a/src/main/java/com/google/firebase/database/core/view/ViewProcessor.java +++ b/src/main/java/com/google/firebase/database/core/view/ViewProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/filter/ChildChangeAccumulator.java b/src/main/java/com/google/firebase/database/core/view/filter/ChildChangeAccumulator.java index 9f74fe172..f78f7bf51 100644 --- a/src/main/java/com/google/firebase/database/core/view/filter/ChildChangeAccumulator.java +++ b/src/main/java/com/google/firebase/database/core/view/filter/ChildChangeAccumulator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/filter/IndexedFilter.java b/src/main/java/com/google/firebase/database/core/view/filter/IndexedFilter.java index bd2c4b717..ff0da3b1e 100644 --- a/src/main/java/com/google/firebase/database/core/view/filter/IndexedFilter.java +++ b/src/main/java/com/google/firebase/database/core/view/filter/IndexedFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/filter/LimitedFilter.java b/src/main/java/com/google/firebase/database/core/view/filter/LimitedFilter.java index eb6ca2eec..1f71b1758 100644 --- a/src/main/java/com/google/firebase/database/core/view/filter/LimitedFilter.java +++ b/src/main/java/com/google/firebase/database/core/view/filter/LimitedFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/filter/NodeFilter.java b/src/main/java/com/google/firebase/database/core/view/filter/NodeFilter.java index 6a81b9bde..8df4e4c16 100644 --- a/src/main/java/com/google/firebase/database/core/view/filter/NodeFilter.java +++ b/src/main/java/com/google/firebase/database/core/view/filter/NodeFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/filter/RangedFilter.java b/src/main/java/com/google/firebase/database/core/view/filter/RangedFilter.java index 003754745..380309300 100644 --- a/src/main/java/com/google/firebase/database/core/view/filter/RangedFilter.java +++ b/src/main/java/com/google/firebase/database/core/view/filter/RangedFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/filter/package-info.java b/src/main/java/com/google/firebase/database/core/view/filter/package-info.java index 405959f3e..44d2f0f3a 100644 --- a/src/main/java/com/google/firebase/database/core/view/filter/package-info.java +++ b/src/main/java/com/google/firebase/database/core/view/filter/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/core/view/package-info.java b/src/main/java/com/google/firebase/database/core/view/package-info.java index 455b97e2c..fcc5f5e30 100644 --- a/src/main/java/com/google/firebase/database/core/view/package-info.java +++ b/src/main/java/com/google/firebase/database/core/view/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/BooleanNode.java b/src/main/java/com/google/firebase/database/snapshot/BooleanNode.java index 1f6505642..e824964e3 100644 --- a/src/main/java/com/google/firebase/database/snapshot/BooleanNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/BooleanNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/ChildKey.java b/src/main/java/com/google/firebase/database/snapshot/ChildKey.java index 9ac8e25c5..6f7a4670f 100644 --- a/src/main/java/com/google/firebase/database/snapshot/ChildKey.java +++ b/src/main/java/com/google/firebase/database/snapshot/ChildKey.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/ChildrenNode.java b/src/main/java/com/google/firebase/database/snapshot/ChildrenNode.java index 1b5c8d694..163a31234 100644 --- a/src/main/java/com/google/firebase/database/snapshot/ChildrenNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/ChildrenNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/CompoundHash.java b/src/main/java/com/google/firebase/database/snapshot/CompoundHash.java index ff786a855..d1525e4ee 100644 --- a/src/main/java/com/google/firebase/database/snapshot/CompoundHash.java +++ b/src/main/java/com/google/firebase/database/snapshot/CompoundHash.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/DeferredValueNode.java b/src/main/java/com/google/firebase/database/snapshot/DeferredValueNode.java index 4290eee00..7a8df1c16 100644 --- a/src/main/java/com/google/firebase/database/snapshot/DeferredValueNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/DeferredValueNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/DoubleNode.java b/src/main/java/com/google/firebase/database/snapshot/DoubleNode.java index c4c4df50d..36f5cf9c1 100644 --- a/src/main/java/com/google/firebase/database/snapshot/DoubleNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/DoubleNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/EmptyNode.java b/src/main/java/com/google/firebase/database/snapshot/EmptyNode.java index 5b76855fb..de755d7b3 100644 --- a/src/main/java/com/google/firebase/database/snapshot/EmptyNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/EmptyNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/Index.java b/src/main/java/com/google/firebase/database/snapshot/Index.java index ac8876ab5..e4bf10083 100644 --- a/src/main/java/com/google/firebase/database/snapshot/Index.java +++ b/src/main/java/com/google/firebase/database/snapshot/Index.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/IndexedNode.java b/src/main/java/com/google/firebase/database/snapshot/IndexedNode.java index 5809bbaf3..db8f88077 100644 --- a/src/main/java/com/google/firebase/database/snapshot/IndexedNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/IndexedNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/KeyIndex.java b/src/main/java/com/google/firebase/database/snapshot/KeyIndex.java index 5c40a3c1d..0ee62f639 100644 --- a/src/main/java/com/google/firebase/database/snapshot/KeyIndex.java +++ b/src/main/java/com/google/firebase/database/snapshot/KeyIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/LeafNode.java b/src/main/java/com/google/firebase/database/snapshot/LeafNode.java index 27a101ff2..9f6e72a0e 100644 --- a/src/main/java/com/google/firebase/database/snapshot/LeafNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/LeafNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/LongNode.java b/src/main/java/com/google/firebase/database/snapshot/LongNode.java index 493be9e0b..43e1b31e8 100644 --- a/src/main/java/com/google/firebase/database/snapshot/LongNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/LongNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/NamedNode.java b/src/main/java/com/google/firebase/database/snapshot/NamedNode.java index f47884a43..202c108e0 100644 --- a/src/main/java/com/google/firebase/database/snapshot/NamedNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/NamedNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/Node.java b/src/main/java/com/google/firebase/database/snapshot/Node.java index 7108633eb..d1535c77a 100644 --- a/src/main/java/com/google/firebase/database/snapshot/Node.java +++ b/src/main/java/com/google/firebase/database/snapshot/Node.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java b/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java index 36e1e603c..b5c200191 100644 --- a/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java +++ b/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/PathIndex.java b/src/main/java/com/google/firebase/database/snapshot/PathIndex.java index 451f3e6b0..c6007fc6b 100644 --- a/src/main/java/com/google/firebase/database/snapshot/PathIndex.java +++ b/src/main/java/com/google/firebase/database/snapshot/PathIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/PriorityIndex.java b/src/main/java/com/google/firebase/database/snapshot/PriorityIndex.java index fa1836be0..9a014bcc8 100644 --- a/src/main/java/com/google/firebase/database/snapshot/PriorityIndex.java +++ b/src/main/java/com/google/firebase/database/snapshot/PriorityIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/PriorityUtilities.java b/src/main/java/com/google/firebase/database/snapshot/PriorityUtilities.java index 14c758f67..8f2e4a71f 100644 --- a/src/main/java/com/google/firebase/database/snapshot/PriorityUtilities.java +++ b/src/main/java/com/google/firebase/database/snapshot/PriorityUtilities.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/RangeMerge.java b/src/main/java/com/google/firebase/database/snapshot/RangeMerge.java index ce846ea34..e6aa48e6a 100644 --- a/src/main/java/com/google/firebase/database/snapshot/RangeMerge.java +++ b/src/main/java/com/google/firebase/database/snapshot/RangeMerge.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/StringNode.java b/src/main/java/com/google/firebase/database/snapshot/StringNode.java index e1af493b0..52f140c05 100644 --- a/src/main/java/com/google/firebase/database/snapshot/StringNode.java +++ b/src/main/java/com/google/firebase/database/snapshot/StringNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/ValueIndex.java b/src/main/java/com/google/firebase/database/snapshot/ValueIndex.java index e915fe29e..26b6cf0a1 100644 --- a/src/main/java/com/google/firebase/database/snapshot/ValueIndex.java +++ b/src/main/java/com/google/firebase/database/snapshot/ValueIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/snapshot/package-info.java b/src/main/java/com/google/firebase/database/snapshot/package-info.java index 722941ccd..ba871dbf5 100644 --- a/src/main/java/com/google/firebase/database/snapshot/package-info.java +++ b/src/main/java/com/google/firebase/database/snapshot/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/util/EmulatorHelper.java b/src/main/java/com/google/firebase/database/util/EmulatorHelper.java index 11c358fc6..c474261aa 100644 --- a/src/main/java/com/google/firebase/database/util/EmulatorHelper.java +++ b/src/main/java/com/google/firebase/database/util/EmulatorHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/util/GAuthToken.java b/src/main/java/com/google/firebase/database/util/GAuthToken.java index 976724081..dfb4c2d6d 100644 --- a/src/main/java/com/google/firebase/database/util/GAuthToken.java +++ b/src/main/java/com/google/firebase/database/util/GAuthToken.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/util/JsonMapper.java b/src/main/java/com/google/firebase/database/util/JsonMapper.java index 0321c8bb2..b71b2bdc2 100644 --- a/src/main/java/com/google/firebase/database/util/JsonMapper.java +++ b/src/main/java/com/google/firebase/database/util/JsonMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/util/package-info.java b/src/main/java/com/google/firebase/database/util/package-info.java index 321042633..1db9cbbb4 100644 --- a/src/main/java/com/google/firebase/database/util/package-info.java +++ b/src/main/java/com/google/firebase/database/util/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/Clock.java b/src/main/java/com/google/firebase/database/utilities/Clock.java index f25fa6db7..5cd64c9f6 100644 --- a/src/main/java/com/google/firebase/database/utilities/Clock.java +++ b/src/main/java/com/google/firebase/database/utilities/Clock.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/DefaultClock.java b/src/main/java/com/google/firebase/database/utilities/DefaultClock.java index 731096be8..b38d71374 100644 --- a/src/main/java/com/google/firebase/database/utilities/DefaultClock.java +++ b/src/main/java/com/google/firebase/database/utilities/DefaultClock.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/DefaultRunLoop.java b/src/main/java/com/google/firebase/database/utilities/DefaultRunLoop.java index e84b7b561..a06c6f2fa 100644 --- a/src/main/java/com/google/firebase/database/utilities/DefaultRunLoop.java +++ b/src/main/java/com/google/firebase/database/utilities/DefaultRunLoop.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/NodeSizeEstimator.java b/src/main/java/com/google/firebase/database/utilities/NodeSizeEstimator.java index 1c91fc5d4..cb88a0901 100644 --- a/src/main/java/com/google/firebase/database/utilities/NodeSizeEstimator.java +++ b/src/main/java/com/google/firebase/database/utilities/NodeSizeEstimator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/OffsetClock.java b/src/main/java/com/google/firebase/database/utilities/OffsetClock.java index 8fb8ce4a1..0f5f5ce7b 100644 --- a/src/main/java/com/google/firebase/database/utilities/OffsetClock.java +++ b/src/main/java/com/google/firebase/database/utilities/OffsetClock.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/Pair.java b/src/main/java/com/google/firebase/database/utilities/Pair.java index 6372a5e29..0cf41af36 100644 --- a/src/main/java/com/google/firebase/database/utilities/Pair.java +++ b/src/main/java/com/google/firebase/database/utilities/Pair.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/ParsedUrl.java b/src/main/java/com/google/firebase/database/utilities/ParsedUrl.java index e43ee9514..b7a0e1b98 100644 --- a/src/main/java/com/google/firebase/database/utilities/ParsedUrl.java +++ b/src/main/java/com/google/firebase/database/utilities/ParsedUrl.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/PushIdGenerator.java b/src/main/java/com/google/firebase/database/utilities/PushIdGenerator.java index 53d8a25ce..2b2b44534 100644 --- a/src/main/java/com/google/firebase/database/utilities/PushIdGenerator.java +++ b/src/main/java/com/google/firebase/database/utilities/PushIdGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/Utilities.java b/src/main/java/com/google/firebase/database/utilities/Utilities.java index 5a3c64822..54ba9e50f 100644 --- a/src/main/java/com/google/firebase/database/utilities/Utilities.java +++ b/src/main/java/com/google/firebase/database/utilities/Utilities.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import com.google.api.core.ApiFuture; import com.google.api.core.SettableApiFuture; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.io.BaseEncoding; import com.google.common.net.UrlEscapers; @@ -32,6 +31,7 @@ import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -108,8 +108,8 @@ static Map getQueryParamsMap(String queryString) for (String paramPair : paramPairs) { String[] pairParts = paramPair.split("="); // both the first and second part will be encoded now, we must decode them - String decodedKey = URLDecoder.decode(pairParts[0], Charsets.UTF_8.name()); - String decodedValue = URLDecoder.decode(pairParts[1], Charsets.UTF_8.name()); + String decodedKey = URLDecoder.decode(pairParts[0], StandardCharsets.UTF_8.name()); + String decodedValue = URLDecoder.decode(pairParts[1], StandardCharsets.UTF_8.name()); String runningValue = paramsMap.get(decodedKey); if (Strings.isNullOrEmpty(runningValue)) { runningValue = decodedValue; diff --git a/src/main/java/com/google/firebase/database/utilities/Validation.java b/src/main/java/com/google/firebase/database/utilities/Validation.java index 8a0bfcf12..835626866 100644 --- a/src/main/java/com/google/firebase/database/utilities/Validation.java +++ b/src/main/java/com/google/firebase/database/utilities/Validation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/encoding/CustomClassMapper.java b/src/main/java/com/google/firebase/database/utilities/encoding/CustomClassMapper.java index c5ef85e4a..d4ba951e7 100644 --- a/src/main/java/com/google/firebase/database/utilities/encoding/CustomClassMapper.java +++ b/src/main/java/com/google/firebase/database/utilities/encoding/CustomClassMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/encoding/package-info.java b/src/main/java/com/google/firebase/database/utilities/encoding/package-info.java index 87a4778e2..817f3573c 100644 --- a/src/main/java/com/google/firebase/database/utilities/encoding/package-info.java +++ b/src/main/java/com/google/firebase/database/utilities/encoding/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/database/utilities/package-info.java b/src/main/java/com/google/firebase/database/utilities/package-info.java index 3b9bf4945..fd5df4ef4 100644 --- a/src/main/java/com/google/firebase/database/utilities/package-info.java +++ b/src/main/java/com/google/firebase/database/utilities/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java index 47026e6f1..9c0d0970d 100644 --- a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java +++ b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/iid/FirebaseInstanceIdException.java b/src/main/java/com/google/firebase/iid/FirebaseInstanceIdException.java index 482a23a3d..eaee4938e 100644 --- a/src/main/java/com/google/firebase/iid/FirebaseInstanceIdException.java +++ b/src/main/java/com/google/firebase/iid/FirebaseInstanceIdException.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/AbstractHttpErrorHandler.java b/src/main/java/com/google/firebase/internal/AbstractHttpErrorHandler.java index 4d1f6b26e..f34e448da 100644 --- a/src/main/java/com/google/firebase/internal/AbstractHttpErrorHandler.java +++ b/src/main/java/com/google/firebase/internal/AbstractHttpErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/internal/AbstractPlatformErrorHandler.java b/src/main/java/com/google/firebase/internal/AbstractPlatformErrorHandler.java index b0909e4f7..f92ce3171 100644 --- a/src/main/java/com/google/firebase/internal/AbstractPlatformErrorHandler.java +++ b/src/main/java/com/google/firebase/internal/AbstractPlatformErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityConsumer.java b/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityConsumer.java new file mode 100644 index 000000000..ee8733586 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityConsumer.java @@ -0,0 +1,85 @@ +/* + * Copyright 2026 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.internal; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.nio.AsyncEntityConsumer; +import org.apache.hc.core5.http.nio.CapacityChannel; + +public class ApacheHttp2AsyncEntityConsumer implements AsyncEntityConsumer { + + private EntityDetails entityDetails; + private FutureCallback resultCallback; + private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + @Override + public void streamStart( + final EntityDetails entityDetails, + final FutureCallback resultCallback) + throws HttpException, IOException { + this.entityDetails = entityDetails; + this.resultCallback = resultCallback; + } + + @Override + public void updateCapacity(CapacityChannel capacityChannel) throws IOException { + capacityChannel.update(Integer.MAX_VALUE); + } + + @Override + public void consume(ByteBuffer src) throws IOException { + if (src.hasArray()) { + buffer.write(src.array(), src.arrayOffset() + src.position(), src.remaining()); + src.position(src.limit()); + } else { + byte[] bytes = new byte[src.remaining()]; + src.get(bytes); + buffer.write(bytes); + } + } + + @Override + public void streamEnd(List trailers) throws HttpException, IOException { + if (resultCallback != null) { + resultCallback.completed(getContent()); + } + } + + @Override + public void failed(Exception cause) { + if (resultCallback != null) { + resultCallback.failed(cause); + } + } + + @Override + public void releaseResources() { + buffer.reset(); + } + + @Override + public ApacheHttp2Entity getContent() { + return new ApacheHttp2Entity(buffer.toByteArray(), entityDetails); + } +} diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityProducer.java b/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityProducer.java new file mode 100644 index 000000000..aad6184cb --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityProducer.java @@ -0,0 +1,150 @@ +/* + * 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.internal; + +import com.google.api.client.util.StreamingContent; +import com.google.common.annotations.VisibleForTesting; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.nio.AsyncEntityProducer; +import org.apache.hc.core5.http.nio.DataStreamChannel; + +public class ApacheHttp2AsyncEntityProducer implements AsyncEntityProducer { + private ByteBuffer bytebuf; + private ByteArrayOutputStream baos; + private final StreamingContent content; + private final ContentType contentType; + private final long contentLength; + private final String contentEncoding; + private final CompletableFuture writeFuture; + private final AtomicReference exception; + + public ApacheHttp2AsyncEntityProducer(StreamingContent content, ContentType contentType, + String contentEncoding, long contentLength, CompletableFuture writeFuture) { + this.content = content; + this.contentType = contentType; + this.contentEncoding = contentEncoding; + this.contentLength = contentLength; + this.writeFuture = writeFuture; + this.bytebuf = null; + + this.baos = new ByteArrayOutputStream((int) (contentLength < 0 ? 0 : contentLength)); + this.exception = new AtomicReference<>(); + } + + public ApacheHttp2AsyncEntityProducer(ApacheHttp2Request request, + CompletableFuture writeFuture) { + this( + request.getStreamingContent(), + ContentType.parse(request.getContentType()), + request.getContentEncoding(), + request.getContentLength(), + writeFuture); + } + + @Override + public boolean isRepeatable() { + return true; + } + + @Override + public String getContentType() { + return contentType != null ? contentType.toString() : null; + } + + @Override + public long getContentLength() { + return contentLength; + } + + @Override + public int available() { + return Integer.MAX_VALUE; + } + + @Override + public String getContentEncoding() { + return contentEncoding; + } + + @Override + public boolean isChunked() { + return contentLength == -1; + } + + @Override + public Set getTrailerNames() { + return null; + } + + @Override + public void produce(DataStreamChannel channel) throws IOException { + if (bytebuf == null) { + if (content != null) { + try { + content.writeTo(baos); + } catch (IOException e) { + failed(e); + throw e; + } + } + + this.bytebuf = ByteBuffer.wrap(baos.toByteArray()); + } + + if (bytebuf.hasRemaining()) { + channel.write(bytebuf); + } + + if (!bytebuf.hasRemaining()) { + channel.endStream(); + writeFuture.complete(null); + releaseResources(); + } + } + + @Override + public void failed(Exception cause) { + if (exception.compareAndSet(null, cause)) { + releaseResources(); + writeFuture.completeExceptionally(cause); + } + } + + public final Exception getException() { + return exception.get(); + } + + @Override + public void releaseResources() { + if (bytebuf != null) { + bytebuf.clear(); + } + } + + @VisibleForTesting + ByteBuffer getBytebuf() { + return bytebuf; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2Entity.java b/src/main/java/com/google/firebase/internal/ApacheHttp2Entity.java new file mode 100644 index 000000000..9769a28a8 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2Entity.java @@ -0,0 +1,37 @@ +/* + * Copyright 2026 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.internal; + +import org.apache.hc.core5.http.EntityDetails; + +public class ApacheHttp2Entity { + private final byte[] content; + private final EntityDetails entityDetails; + + public ApacheHttp2Entity(byte[] content, EntityDetails entityDetails) { + this.content = content; + this.entityDetails = entityDetails; + } + + public byte[] getContent() { + return content; + } + + public EntityDetails getEntityDetails() { + return entityDetails; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2Request.java b/src/main/java/com/google/firebase/internal/ApacheHttp2Request.java new file mode 100644 index 000000000..ceb213c52 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2Request.java @@ -0,0 +1,157 @@ +/* + * 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.internal; + +import com.google.api.client.http.LowLevelHttpRequest; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.common.annotations.VisibleForTesting; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.hc.client5.http.ConnectTimeoutException; +import org.apache.hc.client5.http.HttpHostConnectException; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.Message; +import org.apache.hc.core5.http.nio.support.BasicRequestProducer; +import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; +import org.apache.hc.core5.http2.H2StreamResetException; +import org.apache.hc.core5.util.Timeout; + +final class ApacheHttp2Request extends LowLevelHttpRequest { + private final CloseableHttpAsyncClient httpAsyncClient; + private final SimpleRequestBuilder requestBuilder; + private SimpleHttpRequest request; + private final RequestConfig.Builder requestConfig; + private int writeTimeout; + private ApacheHttp2AsyncEntityProducer entityProducer; + private ApacheHttp2AsyncEntityConsumer entityConsumer; + + ApacheHttp2Request( + CloseableHttpAsyncClient httpAsyncClient, SimpleRequestBuilder requestBuilder) { + this.httpAsyncClient = httpAsyncClient; + this.requestBuilder = requestBuilder; + this.writeTimeout = 0; + + this.requestConfig = RequestConfig.custom() + .setRedirectsEnabled(false); + } + + @Override + public void addHeader(String name, String value) { + requestBuilder.addHeader(name, value); + } + + @Override + public void setTimeout(int connectionTimeout, int readTimeout) throws IOException { + requestConfig + .setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout)) + .setResponseTimeout(Timeout.ofMilliseconds(readTimeout)); + } + + @Override + public void setWriteTimeout(int writeTimeout) throws IOException { + this.writeTimeout = writeTimeout; + } + + @Override + public LowLevelHttpResponse execute() throws IOException { + // Set request configs + requestBuilder.setRequestConfig(requestConfig.build()); + + // Build request + request = requestBuilder.build(); + + // Make Entity Producer + CompletableFuture writeFuture = new CompletableFuture<>(); + entityProducer = new ApacheHttp2AsyncEntityProducer(this, writeFuture); + + // Make Entity Consumer + entityConsumer = new ApacheHttp2AsyncEntityConsumer(); + + // Execute + final Future> responseFuture = httpAsyncClient.execute( + new BasicRequestProducer(request, entityProducer), + new BasicResponseConsumer(entityConsumer), + new FutureCallback>() { + @Override + public void completed(final Message response) { + } + + @Override + public void failed(final Exception exception) { + } + + @Override + public void cancelled() { + } + }); + + // Wait for write + try { + if (writeTimeout != 0) { + writeFuture.get(writeTimeout, TimeUnit.MILLISECONDS); + } + } catch (TimeoutException e) { + throw new IOException("Write Timeout", e.getCause()); + } catch (Exception e) { + throw new IOException("Exception in write", e.getCause()); + } + + // Wait for response + try { + final Message response = responseFuture.get(); + return new ApacheHttp2Response(response); + } catch (ExecutionException e) { + if (e.getCause() instanceof ConnectTimeoutException + || e.getCause() instanceof SocketTimeoutException) { + throw new IOException("Connection Timeout", e.getCause()); + } else if (e.getCause() instanceof HttpHostConnectException) { + throw new IOException("Connection exception in request", e.getCause()); + } else if (e.getCause() instanceof H2StreamResetException) { + throw new IOException("Stream exception in request", e.getCause()); + } else { + throw new IOException("Unknown exception in request", e); + } + } catch (InterruptedException e) { + throw new IOException("Request Interrupted", e); + } catch (CancellationException e) { + throw new IOException("Request Cancelled", e); + } + } + + @VisibleForTesting + ApacheHttp2AsyncEntityProducer getEntityProducer() { + return entityProducer; + } + + @VisibleForTesting + ApacheHttp2AsyncEntityConsumer getEntityConsumer() { + return entityConsumer; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2Response.java b/src/main/java/com/google/firebase/internal/ApacheHttp2Response.java new file mode 100644 index 000000000..7d277b972 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2Response.java @@ -0,0 +1,111 @@ +/* + * 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.internal; + +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.common.annotations.VisibleForTesting; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.Message; +import org.apache.hc.core5.http.message.StatusLine; + +public class ApacheHttp2Response extends LowLevelHttpResponse { + private final Message message; + private final HttpResponse response; + private final Header[] allHeaders; + private final EntityDetails entity; + private final byte[] content; + + ApacheHttp2Response(Message message) { + this.message = message; + this.response = message.getHead(); + this.allHeaders = response.getHeaders(); + + ApacheHttp2Entity body = message.getBody(); + this.entity = body != null ? body.getEntityDetails() : null; + this.content = body != null ? body.getContent() : null; + } + + @Override + public int getStatusCode() { + return response.getCode(); + } + + @Override + public InputStream getContent() throws IOException { + return content == null ? null : new ByteArrayInputStream(content); + } + + @Override + public String getContentEncoding() { + return entity == null ? null : entity.getContentEncoding(); + } + + @Override + public long getContentLength() { + if (content != null) { + return content.length; + } + return entity == null ? -1 : entity.getContentLength(); + } + + @Override + public String getContentType() { + return entity == null ? null : entity.getContentType(); + } + + @Override + public String getReasonPhrase() { + return response.getReasonPhrase(); + } + + @Override + public String getStatusLine() { + return new StatusLine(response).toString(); + } + + public String getHeaderValue(String name) { + Header header = response.getLastHeader(name); + return header == null ? null : header.getValue(); + } + + @Override + public String getHeaderValue(int index) { + return allHeaders[index].getValue(); + } + + @Override + public int getHeaderCount() { + return allHeaders.length; + } + + @Override + public String getHeaderName(int index) { + return allHeaders[index].getName(); + } + + @VisibleForTesting + public Message getMessage() { + return message; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2Transport.java b/src/main/java/com/google/firebase/internal/ApacheHttp2Transport.java new file mode 100644 index 000000000..c701a4652 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2Transport.java @@ -0,0 +1,125 @@ +/* + * 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.internal; + +import com.google.api.client.http.HttpTransport; + +import java.io.IOException; +import java.net.ProxySelector; +import java.util.concurrent.TimeUnit; + +import org.apache.hc.client5.http.async.HttpAsyncClient; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.core5.http.config.Http1Config; +import org.apache.hc.core5.http2.HttpVersionPolicy; +import org.apache.hc.core5.http2.config.H2Config; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.ssl.SSLContexts; + +/** + * HTTP/2 enabled async transport based on the Apache HTTP Client library + */ +public final class ApacheHttp2Transport extends HttpTransport { + + private final CloseableHttpAsyncClient httpAsyncClient; + private final boolean isMtls; + + public ApacheHttp2Transport() { + this(newDefaultHttpAsyncClient(), false); + } + + public ApacheHttp2Transport(CloseableHttpAsyncClient httpAsyncClient) { + this(httpAsyncClient, false); + } + + public ApacheHttp2Transport(CloseableHttpAsyncClient httpAsyncClient, boolean isMtls) { + this.httpAsyncClient = httpAsyncClient; + this.isMtls = isMtls; + + httpAsyncClient.start(); + } + + public static CloseableHttpAsyncClient newDefaultHttpAsyncClient() { + return defaultHttpAsyncClientBuilder().build(); + } + + public static HttpAsyncClientBuilder defaultHttpAsyncClientBuilder() { + PoolingAsyncClientConnectionManager connectionManager = + PoolingAsyncClientConnectionManagerBuilder.create() + // Set Max total connections to match google api client limits + // https://github.com/googleapis/google-http-java-client/blob/f9d4e15bd3c784b1fd3b0f3468000a91c6f79715/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java#L151 + .setMaxConnTotal(200) + // Set max connections per route to match the concurrent stream limit of the FCM backend. + .setMaxConnPerRoute(100) + .setDefaultConnectionConfig( + ConnectionConfig.custom().setTimeToLive(-1, TimeUnit.MILLISECONDS).build()) + .setDefaultTlsConfig( + TlsConfig.custom().setVersionPolicy(HttpVersionPolicy.NEGOTIATE).build()) + .setTlsStrategy(ClientTlsStrategyBuilder.create() + .setSslContext(SSLContexts.createSystemDefault()) + .build()) + .build(); + + return HttpAsyncClientBuilder.create() + // Set maxConcurrentStreams to 100 to match the concurrent stream limit of the FCM backend. + .setH2Config(H2Config.custom().setMaxConcurrentStreams(100).build()) + .setHttp1Config(Http1Config.DEFAULT) + .setConnectionManager(connectionManager) + .setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault())) + .disableRedirectHandling() + .disableAutomaticRetries(); + } + + @Override + public boolean supportsMethod(String method) { + return true; + } + + @Override + protected ApacheHttp2Request buildRequest(String method, String url) { + SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(method).setUri(url); + return new ApacheHttp2Request(httpAsyncClient, requestBuilder); + } + + /** + * Gracefully shuts down the connection manager and releases allocated resources. This closes all + * connections, whether they are currently used or not. + */ + @Override + public void shutdown() throws IOException { + httpAsyncClient.close(CloseMode.GRACEFUL); + } + + /** Returns the Apache HTTP client. */ + public HttpAsyncClient getHttpClient() { + return httpAsyncClient; + } + + /** Returns if the underlying HTTP client is mTLS. */ + @Override + public boolean isMtls() { + return isMtls; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java index e586bd11f..6121ddae5 100644 --- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java +++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,6 +88,10 @@ public static JsonFactory getDefaultJsonFactory() { } public static HttpTransport getDefaultTransport() { - return Utils.getDefaultTransport(); + return TransportInstanceHolder.INSTANCE; + } + + private static class TransportInstanceHolder { + static final HttpTransport INSTANCE = new ApacheHttp2Transport(); } } diff --git a/src/main/java/com/google/firebase/internal/ApplicationDefaultCredentialsProvider.java b/src/main/java/com/google/firebase/internal/ApplicationDefaultCredentialsProvider.java index 93672bbe9..e197c796c 100644 --- a/src/main/java/com/google/firebase/internal/ApplicationDefaultCredentialsProvider.java +++ b/src/main/java/com/google/firebase/internal/ApplicationDefaultCredentialsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/internal/CallableOperation.java b/src/main/java/com/google/firebase/internal/CallableOperation.java index ac28e8a7c..053cdf35a 100644 --- a/src/main/java/com/google/firebase/internal/CallableOperation.java +++ b/src/main/java/com/google/firebase/internal/CallableOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/EmulatorCredentials.java b/src/main/java/com/google/firebase/internal/EmulatorCredentials.java index b13e971d0..fb6541d63 100644 --- a/src/main/java/com/google/firebase/internal/EmulatorCredentials.java +++ b/src/main/java/com/google/firebase/internal/EmulatorCredentials.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google Inc. + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java index 5efdd0ec2..77a342042 100644 --- a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java +++ b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. @@ -89,6 +89,7 @@ public void sendAndParse(HttpRequestInfo requestInfo, Object destination) throws } public IncomingHttpResponse send(HttpRequestInfo requestInfo) throws T { + requestInfo.addHeader("X-Goog-Api-Client", SdkUtils.getMetricsHeader()); HttpRequest request = createHttpRequest(requestInfo); HttpResponse response = null; diff --git a/src/main/java/com/google/firebase/internal/FirebaseProcessEnvironment.java b/src/main/java/com/google/firebase/internal/FirebaseProcessEnvironment.java index a9c3022d4..b6fd789d4 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseProcessEnvironment.java +++ b/src/main/java/com/google/firebase/internal/FirebaseProcessEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/internal/FirebaseRequestInitializer.java b/src/main/java/com/google/firebase/internal/FirebaseRequestInitializer.java index e73f93953..910526b3a 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseRequestInitializer.java +++ b/src/main/java/com/google/firebase/internal/FirebaseRequestInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -60,16 +60,19 @@ private static class TimeoutInitializer implements HttpRequestInitializer { private final int connectTimeoutMillis; private final int readTimeoutMillis; + private final int writeTimeoutMillis; TimeoutInitializer(FirebaseOptions options) { this.connectTimeoutMillis = options.getConnectTimeout(); this.readTimeoutMillis = options.getReadTimeout(); + this.writeTimeoutMillis = options.getWriteTimeout(); } @Override public void initialize(HttpRequest request) { request.setConnectTimeout(connectTimeoutMillis); request.setReadTimeout(readTimeoutMillis); + request.setWriteTimeout(writeTimeoutMillis); } } } diff --git a/src/main/java/com/google/firebase/internal/FirebaseScheduledExecutor.java b/src/main/java/com/google/firebase/internal/FirebaseScheduledExecutor.java index 0a472ba47..d581a6b91 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseScheduledExecutor.java +++ b/src/main/java/com/google/firebase/internal/FirebaseScheduledExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/internal/FirebaseService.java b/src/main/java/com/google/firebase/internal/FirebaseService.java index 2b802aee8..75de81427 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseService.java +++ b/src/main/java/com/google/firebase/internal/FirebaseService.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java b/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java index 877450fcc..38db746df 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java +++ b/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,10 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,7 +87,11 @@ private static class DefaultThreadManager extends GlobalThreadManager { protected ExecutorService doInit() { ThreadFactory threadFactory = FirebaseScheduledExecutor.getThreadFactoryWithName( getThreadFactory(), "firebase-default-%d"); - return Executors.newCachedThreadPool(threadFactory); + + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 100, 60L, + TimeUnit.SECONDS, new LinkedBlockingQueue(), threadFactory); + threadPoolExecutor.allowCoreThreadTimeOut(true); + return threadPoolExecutor; } @Override diff --git a/src/main/java/com/google/firebase/internal/GuardedBy.java b/src/main/java/com/google/firebase/internal/GuardedBy.java index 9f1bd1ef7..a44353262 100644 --- a/src/main/java/com/google/firebase/internal/GuardedBy.java +++ b/src/main/java/com/google/firebase/internal/GuardedBy.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/HttpErrorHandler.java b/src/main/java/com/google/firebase/internal/HttpErrorHandler.java index 988b45a45..29f7ec540 100644 --- a/src/main/java/com/google/firebase/internal/HttpErrorHandler.java +++ b/src/main/java/com/google/firebase/internal/HttpErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/internal/HttpRequestInfo.java b/src/main/java/com/google/firebase/internal/HttpRequestInfo.java index 375e332fb..22a2cf2b1 100644 --- a/src/main/java/com/google/firebase/internal/HttpRequestInfo.java +++ b/src/main/java/com/google/firebase/internal/HttpRequestInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/internal/ListenableFuture2ApiFuture.java b/src/main/java/com/google/firebase/internal/ListenableFuture2ApiFuture.java index 18d98da7e..8f90d4d8a 100644 --- a/src/main/java/com/google/firebase/internal/ListenableFuture2ApiFuture.java +++ b/src/main/java/com/google/firebase/internal/ListenableFuture2ApiFuture.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/MockApacheHttp2AsyncClient.java b/src/main/java/com/google/firebase/internal/MockApacheHttp2AsyncClient.java new file mode 100644 index 000000000..a61751912 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/MockApacheHttp2AsyncClient.java @@ -0,0 +1,73 @@ +/* + * 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.internal; + +import java.io.IOException; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.nio.AsyncPushConsumer; +import org.apache.hc.core5.http.nio.AsyncRequestProducer; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; +import org.apache.hc.core5.http.nio.HandlerFactory; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.reactor.IOReactorStatus; +import org.apache.hc.core5.util.TimeValue; + +public class MockApacheHttp2AsyncClient extends CloseableHttpAsyncClient { + + @Override + public void close(CloseMode closeMode) { + } + + @Override + public void close() throws IOException { + } + + @Override + public void start() { + } + + @Override + public IOReactorStatus getStatus() { + return null; + } + + @Override + public void awaitShutdown(TimeValue waitTime) throws InterruptedException { + } + + @Override + public void initiateShutdown() { + } + + @Override + protected Future doExecute(HttpHost target, AsyncRequestProducer requestProducer, + AsyncResponseConsumer responseConsumer, + HandlerFactory pushHandlerFactory, + HttpContext context, FutureCallback callback) { + return null; + } + + @Override + public void register(String hostname, String uriPattern, Supplier supplier) { + } +} diff --git a/src/main/java/com/google/firebase/internal/NonNull.java b/src/main/java/com/google/firebase/internal/NonNull.java index a65e082b0..0307a4819 100644 --- a/src/main/java/com/google/firebase/internal/NonNull.java +++ b/src/main/java/com/google/firebase/internal/NonNull.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/Nullable.java b/src/main/java/com/google/firebase/internal/Nullable.java index 48572757e..6f1ef4d08 100644 --- a/src/main/java/com/google/firebase/internal/Nullable.java +++ b/src/main/java/com/google/firebase/internal/Nullable.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/RetryConfig.java b/src/main/java/com/google/firebase/internal/RetryConfig.java index a17780ed0..7096ec659 100644 --- a/src/main/java/com/google/firebase/internal/RetryConfig.java +++ b/src/main/java/com/google/firebase/internal/RetryConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/RetryInitializer.java b/src/main/java/com/google/firebase/internal/RetryInitializer.java index fbe0a13ea..5bba2090b 100644 --- a/src/main/java/com/google/firebase/internal/RetryInitializer.java +++ b/src/main/java/com/google/firebase/internal/RetryInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandler.java b/src/main/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandler.java index dd00d2bfb..51dd34395 100644 --- a/src/main/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandler.java +++ b/src/main/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/internal/SdkUtils.java b/src/main/java/com/google/firebase/internal/SdkUtils.java index 2fa5e5767..79f99c2fd 100644 --- a/src/main/java/com/google/firebase/internal/SdkUtils.java +++ b/src/main/java/com/google/firebase/internal/SdkUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,14 @@ public static String getVersion() { return SDK_VERSION; } + public static String getJavaVersion() { + return System.getProperty("java.version"); + } + + public static String getMetricsHeader() { + return String.format("gl-java/%s fire-admin/%s", getJavaVersion(), getVersion()); + } + private static String loadSdkVersion() { try (InputStream in = SdkUtils.class.getClassLoader() .getResourceAsStream(ADMIN_SDK_PROPERTIES)) { diff --git a/src/main/java/com/google/firebase/internal/package-info.java b/src/main/java/com/google/firebase/internal/package-info.java index a64cc61be..f38373b2a 100644 --- a/src/main/java/com/google/firebase/internal/package-info.java +++ b/src/main/java/com/google/firebase/internal/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/messaging/AndroidConfig.java b/src/main/java/com/google/firebase/messaging/AndroidConfig.java index e38e561d0..22f591680 100644 --- a/src/main/java/com/google/firebase/messaging/AndroidConfig.java +++ b/src/main/java/com/google/firebase/messaging/AndroidConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -55,6 +55,12 @@ public class AndroidConfig { @Key("direct_boot_ok") private final Boolean directBootOk; + @Key("bandwidth_constrained_ok") + private final Boolean bandwidthConstrainedOk; + + @Key("restricted_satellite_ok") + private final Boolean restrictedSatelliteOk; + private AndroidConfig(Builder builder) { this.collapseKey = builder.collapseKey; if (builder.priority != null) { @@ -79,6 +85,8 @@ private AndroidConfig(Builder builder) { this.notification = builder.notification; this.fcmOptions = builder.fcmOptions; this.directBootOk = builder.directBootOk; + this.bandwidthConstrainedOk = builder.bandwidthConstrainedOk; + this.restrictedSatelliteOk = builder.restrictedSatelliteOk; } /** @@ -108,6 +116,8 @@ public static class Builder { private AndroidNotification notification; private AndroidFcmOptions fcmOptions; private Boolean directBootOk; + private Boolean bandwidthConstrainedOk; + private Boolean restrictedSatelliteOk; private Builder() {} @@ -218,6 +228,24 @@ public Builder setDirectBootOk(boolean directBootOk) { return this; } + /** + * Sets the {@code bandwidth_constrained_ok} flag. If set to true, messages will be allowed + * to be delivered to the app while the device is on a bandwidth constrained network. + */ + public Builder setBandwidthConstrainedOk(boolean bandwidthConstrainedOk) { + this.bandwidthConstrainedOk = bandwidthConstrainedOk; + return this; + } + + /** + * Sets the {@code restricted_satellite_ok} flag. If set to true, messages will be allowed + * to be delivered to the app while the device is on a restricted satellite network. + */ + public Builder setRestrictedSatelliteOk(boolean restrictedSatelliteOk) { + this.restrictedSatelliteOk = restrictedSatelliteOk; + return this; + } + /** * Creates a new {@link AndroidConfig} instance from the parameters set on this builder. * diff --git a/src/main/java/com/google/firebase/messaging/AndroidNotification.java b/src/main/java/com/google/firebase/messaging/AndroidNotification.java index 05e680910..bcd5cc962 100644 --- a/src/main/java/com/google/firebase/messaging/AndroidNotification.java +++ b/src/main/java/com/google/firebase/messaging/AndroidNotification.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -112,6 +112,9 @@ public class AndroidNotification { @Key("notification_count") private final Integer notificationCount; + @Key("proxy") + private final String proxy; + private static final Map PRIORITY_MAP = ImmutableMap.builder() .put(Priority.MIN, "PRIORITY_MIN") @@ -179,6 +182,11 @@ private AndroidNotification(Builder builder) { checkArgument(builder.notificationCount >= 0, "notificationCount if specified must be zero or positive valued"); } + if (builder.proxy != null) { + this.proxy = builder.proxy.name(); + } else { + this.proxy = null; + } this.notificationCount = builder.notificationCount; } @@ -194,13 +202,19 @@ public String toString() { return PRIORITY_MAP.get(this); } } - + public enum Visibility { PRIVATE, PUBLIC, SECRET, } + public enum Proxy { + ALLOW, + DENY, + IF_PRIORITY_LOWERED + } + /** * Creates a new {@link AndroidNotification.Builder}. * @@ -237,6 +251,7 @@ public static class Builder { private LightSettings lightSettings; private Boolean defaultLightSettings; private Visibility visibility; + private Proxy proxy; private Builder() {} @@ -602,6 +617,18 @@ public Builder setNotificationCount(int notificationCount) { return this; } + /** + * Sets the proxy of this notification. + * + * @param proxy The proxy value, one of the values in {ALLOW, DENY, IF_PRIORITY_LOWERED} + * + * @return This builder. + */ + public Builder setProxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + /** * Creates a new {@link AndroidNotification} instance from the parameters set on this builder. * diff --git a/src/main/java/com/google/firebase/messaging/ApnsConfig.java b/src/main/java/com/google/firebase/messaging/ApnsConfig.java index 0e5de8619..093baf3a4 100644 --- a/src/main/java/com/google/firebase/messaging/ApnsConfig.java +++ b/src/main/java/com/google/firebase/messaging/ApnsConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -41,6 +41,9 @@ public class ApnsConfig { @Key("fcm_options") private final ApnsFcmOptions fcmOptions; + @Key("live_activity_token") + private final String liveActivityToken; + private ApnsConfig(Builder builder) { checkArgument(builder.aps != null, "aps must be specified"); checkArgument(!builder.customData.containsKey("aps"), @@ -51,6 +54,7 @@ private ApnsConfig(Builder builder) { .put("aps", builder.aps.getFields()) .build(); this.fcmOptions = builder.fcmOptions; + this.liveActivityToken = builder.liveActivityToken; } /** @@ -68,6 +72,7 @@ public static class Builder { private final Map customData = new HashMap<>(); private Aps aps; private ApnsFcmOptions fcmOptions; + private String liveActivityToken; private Builder() {} @@ -137,6 +142,17 @@ public Builder setFcmOptions(ApnsFcmOptions apnsFcmOptions) { return this; } + /** + * Sets the Live Activity token. + * + * @param liveActivityToken Live Activity token. + * @return This builder. + */ + public Builder setLiveActivityToken(String liveActivityToken) { + this.liveActivityToken = liveActivityToken; + return this; + } + /** * Creates a new {@link ApnsConfig} instance from the parameters set on this builder. * diff --git a/src/main/java/com/google/firebase/messaging/Aps.java b/src/main/java/com/google/firebase/messaging/Aps.java index 10c5af181..6ee12a7a0 100644 --- a/src/main/java/com/google/firebase/messaging/Aps.java +++ b/src/main/java/com/google/firebase/messaging/Aps.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/messaging/ApsAlert.java b/src/main/java/com/google/firebase/messaging/ApsAlert.java index 2efa94433..12b553b13 100644 --- a/src/main/java/com/google/firebase/messaging/ApsAlert.java +++ b/src/main/java/com/google/firebase/messaging/ApsAlert.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/messaging/BatchResponse.java b/src/main/java/com/google/firebase/messaging/BatchResponse.java index 164403be4..09a99dbb3 100644 --- a/src/main/java/com/google/firebase/messaging/BatchResponse.java +++ b/src/main/java/com/google/firebase/messaging/BatchResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/messaging/BatchResponseImpl.java b/src/main/java/com/google/firebase/messaging/BatchResponseImpl.java index 99cf63df1..f10c43d25 100644 --- a/src/main/java/com/google/firebase/messaging/BatchResponseImpl.java +++ b/src/main/java/com/google/firebase/messaging/BatchResponseImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/messaging/CriticalSound.java b/src/main/java/com/google/firebase/messaging/CriticalSound.java index ed293ba96..73d489193 100644 --- a/src/main/java/com/google/firebase/messaging/CriticalSound.java +++ b/src/main/java/com/google/firebase/messaging/CriticalSound.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java index 95366eaa7..0e9831588 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -20,18 +20,23 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; import com.google.firebase.internal.CallableOperation; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutionException; /** * This class is the entry point for all server-side Firebase Cloud Messaging actions. @@ -91,7 +96,7 @@ public String send(@NonNull Message message) throws FirebaseMessagingException { * *

If the {@code dryRun} option is set to true, the message will not be actually sent. Instead * FCM performs all the necessary validations and emulates the send operation. The {@code dryRun} - * option is useful for determining whether an FCM registration has been deleted. However, it + * option is useful for determining whether an FCM registration has been deleted. However, it * cannot be used to validate APNs tokens. * * @param message A non-null {@link Message} to be sent. @@ -139,6 +144,204 @@ protected String execute() throws FirebaseMessagingException { }; } + /** + * Sends each message in the given list via Firebase Cloud Messaging. + * Unlike {@link #sendAll(List)}, this method makes an HTTP call for each message in the + * given array. + * + *

The list of responses obtained by calling {@link BatchResponse#getResponses()} on the return + * value is in the same order as the input list. + * + * @param messages A non-null, non-empty list containing up to 500 messages. + * @return A {@link BatchResponse} indicating the result of the operation. + * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for + * delivery. An exception here or a {@link BatchResponse} with all failures indicates a total + * failure, meaning that none of the messages in the list could be sent. Partial failures or + * no failures are only indicated by a {@link BatchResponse}. + */ + public BatchResponse sendEach(@NonNull List messages) throws FirebaseMessagingException { + return sendEach(messages, false); + } + + + /** + * Sends each message in the given list via Firebase Cloud Messaging. + * Unlike {@link #sendAll(List)}, this method makes an HTTP call for each message in the + * given array. + * + *

If the {@code dryRun} option is set to true, the message will not be actually sent. Instead + * FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun} + * option is useful for determining whether an FCM registration has been deleted. But it cannot be + * used to validate APNs tokens. + * + *

The list of responses obtained by calling {@link BatchResponse#getResponses()} on the return + * value is in the same order as the input list. + * + * @param messages A non-null, non-empty list containing up to 500 messages. + * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. + * @return A {@link BatchResponse} indicating the result of the operation. + * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for + * delivery. An exception here or a {@link BatchResponse} with all failures indicates a total + * failure, meaning that none of the messages in the list could be sent. Partial failures or + * no failures are only indicated by a {@link BatchResponse}. + */ + public BatchResponse sendEach( + @NonNull List messages, boolean dryRun) throws FirebaseMessagingException { + try { + return sendEachOpAsync(messages, dryRun).get(); + } catch (InterruptedException | ExecutionException e) { + throw new FirebaseMessagingException(ErrorCode.CANCELLED, SERVICE_ID); + } + } + + /** + * Similar to {@link #sendEach(List)} but performs the operation asynchronously. + * + * @param messages A non-null, non-empty list containing up to 500 messages. + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * the messages have been sent. + */ + public ApiFuture sendEachAsync(@NonNull List messages) { + return sendEachOpAsync(messages, false); + } + + /** + * Similar to {@link #sendEach(List, boolean)} but performs the operation asynchronously. + * + * @param messages A non-null, non-empty list containing up to 500 messages. + * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * the messages have been sent. + */ + public ApiFuture sendEachAsync(@NonNull List messages, boolean dryRun) { + return sendEachOpAsync(messages, dryRun); + } + + // Returns an ApiFuture directly since this function is non-blocking. Individual child send + // requests are still called async and run in background threads. + private ApiFuture sendEachOpAsync( + final List messages, final boolean dryRun) { + final List immutableMessages = ImmutableList.copyOf(messages); + checkArgument(!immutableMessages.isEmpty(), "messages list must not be empty"); + checkArgument(immutableMessages.size() <= 500, + "messages list must not contain more than 500 elements"); + + List> list = new ArrayList<>(); + for (Message message : immutableMessages) { + // Make async send calls per message + ApiFuture messageId = sendOpForSendResponse(message, dryRun).callAsync(app); + list.add(messageId); + } + + // Gather all futures and combine into a list + ApiFuture> responsesFuture = ApiFutures.allAsList(list); + + // Chain this future to wrap the eventual responses in a BatchResponse without blocking + // the main thread. This uses the current thread to execute, but since the transformation + // function is non-blocking the transformation itself is also non-blocking. + return ApiFutures.transform( + responsesFuture, + (responses) -> { + return new BatchResponseImpl(responses); + }, + MoreExecutors.directExecutor()); + } + + private CallableOperation sendOpForSendResponse( + final Message message, final boolean dryRun) { + checkNotNull(message, "message must not be null"); + final FirebaseMessagingClient messagingClient = getMessagingClient(); + return new CallableOperation() { + @Override + protected SendResponse execute() { + try { + String messageId = messagingClient.send(message, dryRun); + return SendResponse.fromMessageId(messageId); + } catch (FirebaseMessagingException e) { + return SendResponse.fromException(e); + } + } + }; + } + + /** + * Sends the given multicast message to all the FCM registration tokens and/or FIDs + * specified in it. + * + *

This method uses the {@link #sendEach(List)} API under the hood to send the given + * message to all the target recipients. The list of responses obtained by calling + * {@link BatchResponse#getResponses()} on the return value is in the same order as the + * tokens and/or FIDs in the {@link MulticastMessage}. If both tokens and FIDs are + * provided, tokens are processed first, followed by FIDs. + * + * @param message A non-null {@link MulticastMessage} + * @return A {@link BatchResponse} indicating the result of the operation. + * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for + * delivery. An exception here or a {@link BatchResponse} with all failures indicates a total + * failure, meaning that none of the messages in the list could be sent. Partial failures or + * no failures are only indicated by a {@link BatchResponse}. + */ + public BatchResponse sendEachForMulticast( + @NonNull MulticastMessage message) throws FirebaseMessagingException { + return sendEachForMulticast(message, false); + } + + /** + * Sends the given multicast message to all the FCM registration tokens and/or FIDs + * specified in it. + * + *

If the {@code dryRun} option is set to true, the message will not be actually sent. + * Instead FCM performs all the necessary validations, and emulates the send operation. + * The {@code dryRun} option is useful for determining whether an FCM registration has + * been deleted. But it cannot be used to validate APNs tokens. + * + *

This method uses the {@link #sendEach(List)} API under the hood to send the given + * message to all the target recipients. The list of responses obtained by calling + * {@link BatchResponse#getResponses()} on the return value is in the same order as the + * tokens and/or FIDs in the {@link MulticastMessage}. If both tokens and FIDs are + * provided, tokens are processed first, followed by FIDs. + * + * @param message A non-null {@link MulticastMessage}. + * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. + * @return A {@link BatchResponse} indicating the result of the operation. + * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for + * delivery. An exception here or a {@link BatchResponse} with all failures indicates a total + * failure, meaning that none of the messages in the list could be sent. Partial failures or + * no failures are only indicated by a {@link BatchResponse}. + */ + public BatchResponse sendEachForMulticast(@NonNull MulticastMessage message, boolean dryRun) + throws FirebaseMessagingException { + checkNotNull(message, "multicast message must not be null"); + return sendEach(message.getMessageList(), dryRun); + } + + /** + * Similar to {@link #sendEachForMulticast(MulticastMessage)} but performs the operation + * asynchronously. + * + * @param message A non-null {@link MulticastMessage}. + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * the messages have been sent. + */ + public ApiFuture sendEachForMulticastAsync(@NonNull MulticastMessage message) { + return sendEachForMulticastAsync(message, false); + } + + /** + * Similar to {@link #sendEachForMulticast(MulticastMessage, boolean)} but performs the operation + * asynchronously. + * + * @param message A non-null {@link MulticastMessage}. + * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * the messages have been sent. + */ + public ApiFuture sendEachForMulticastAsync( + @NonNull MulticastMessage message, boolean dryRun) { + checkNotNull(message, "multicast message must not be null"); + return sendEachAsync(message.getMessageList(), dryRun); + } + /** * Sends all the messages in the given list via Firebase Cloud Messaging. Employs batching to * send the entire list as a single RPC call. Compared to the {@link #send(Message)} method, this @@ -150,9 +353,12 @@ protected String execute() throws FirebaseMessagingException { * @param messages A non-null, non-empty list containing up to 500 messages. * @return A {@link BatchResponse} indicating the result of the operation. * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for - * delivery. An exception here indicates a total failure -- i.e. none of the messages in the - * list could be sent. Partial failures are indicated by a {@link BatchResponse} return value. + * delivery. An exception here indicates a total failure, meaning that none of the messages in + * the list could be sent. Partial failures are indicated by a {@link BatchResponse} return + * value. + * @deprecated Use {@link #sendEach(List)} instead. */ + @Deprecated public BatchResponse sendAll( @NonNull List messages) throws FirebaseMessagingException { return sendAll(messages, false); @@ -175,9 +381,12 @@ public BatchResponse sendAll( * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. * @return A {@link BatchResponse} indicating the result of the operation. * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for - * delivery. An exception here indicates a total failure -- i.e. none of the messages in the - * list could be sent. Partial failures are indicated by a {@link BatchResponse} return value. + * delivery. An exception here indicates a total failure, meaning that none of the messages in + * the list could be sent. Partial failures are indicated by a {@link BatchResponse} return + * value. + * @deprecated Use {@link #sendEach(List, boolean)} instead. */ + @Deprecated public BatchResponse sendAll( @NonNull List messages, boolean dryRun) throws FirebaseMessagingException { return sendAllOp(messages, dryRun).call(); @@ -187,9 +396,11 @@ public BatchResponse sendAll( * Similar to {@link #sendAll(List)} but performs the operation asynchronously. * * @param messages A non-null, non-empty list containing up to 500 messages. - * @return @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when * the messages have been sent. + * @deprecated Use {@link #sendEachAsync(List)} instead. */ + @Deprecated public ApiFuture sendAllAsync(@NonNull List messages) { return sendAllAsync(messages, false); } @@ -199,55 +410,65 @@ public ApiFuture sendAllAsync(@NonNull List messages) { * * @param messages A non-null, non-empty list containing up to 500 messages. * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. - * @return @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when * the messages have been sent, or when the emulation has finished. + * @deprecated Use {@link #sendEachAsync(List, boolean)} instead. */ + @Deprecated public ApiFuture sendAllAsync( @NonNull List messages, boolean dryRun) { return sendAllOp(messages, dryRun).callAsync(app); } /** - * Sends the given multicast message to all the FCM registration tokens specified in it. + * Sends the given multicast message to all the FCM registration tokens and/or FIDs + * specified in it. * *

This method uses the {@link #sendAll(List)} API under the hood to send the given * message to all the target recipients. The responses list obtained by calling - * {@link BatchResponse#getResponses()} on the return value corresponds to the order of tokens - * in the {@link MulticastMessage}. + * {@link BatchResponse#getResponses()} on the return value corresponds to the order of + * tokens and/or FIDs in the {@link MulticastMessage}. If both tokens and FIDs are + * provided, tokens are processed first, followed by FIDs. * * @param message A non-null {@link MulticastMessage} * @return A {@link BatchResponse} indicating the result of the operation. * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for - * delivery. An exception here indicates a total failure -- i.e. the messages could not be - * delivered to any recipient. Partial failures are indicated by a {@link BatchResponse} + * delivery. An exception here indicates a total failure, meaning that the messages could not + * be delivered to any recipient. Partial failures are indicated by a {@link BatchResponse} * return value. + * @deprecated Use {@link #sendEachForMulticast(MulticastMessage)} instead. */ + @Deprecated public BatchResponse sendMulticast( @NonNull MulticastMessage message) throws FirebaseMessagingException { return sendMulticast(message, false); } /** - * Sends the given multicast message to all the FCM registration tokens specified in it. + * Sends the given multicast message to all the FCM registration tokens and/or FIDs + * specified in it. * - *

If the {@code dryRun} option is set to true, the message will not be actually sent. Instead - * FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun} - * option is useful for determining whether an FCM registration has been deleted. But it cannot be - * used to validate APNs tokens. + *

If the {@code dryRun} option is set to true, the message will not be actually sent. + * Instead FCM performs all the necessary validations, and emulates the send operation. + * The {@code dryRun} option is useful for determining whether an FCM registration has + * been deleted. But it cannot be used to validate APNs tokens. * *

This method uses the {@link #sendAll(List)} API under the hood to send the given * message to all the target recipients. The responses list obtained by calling - * {@link BatchResponse#getResponses()} on the return value corresponds to the order of tokens - * in the {@link MulticastMessage}. + * {@link BatchResponse#getResponses()} on the return value corresponds to the order of + * tokens and/or FIDs in the {@link MulticastMessage}. If both tokens and FIDs are + * provided, tokens are processed first, followed by FIDs. * * @param message A non-null {@link MulticastMessage}. * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. * @return A {@link BatchResponse} indicating the result of the operation. * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for - * delivery. An exception here indicates a total failure -- i.e. the messages could not be - * delivered to any recipient. Partial failures are indicated by a {@link BatchResponse} + * delivery. An exception here indicates a total failure, meaning that the messages could not + * be delivered to any recipient. Partial failures are indicated by a {@link BatchResponse} * return value. + * @deprecated Use {@link #sendEachForMulticast(MulticastMessage, boolean)} instead. */ + @Deprecated public BatchResponse sendMulticast( @NonNull MulticastMessage message, boolean dryRun) throws FirebaseMessagingException { checkNotNull(message, "multicast message must not be null"); @@ -261,7 +482,9 @@ public BatchResponse sendMulticast( * @param message A non-null {@link MulticastMessage}. * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when * the messages have been sent. + * @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage)} instead. */ + @Deprecated public ApiFuture sendMulticastAsync(@NonNull MulticastMessage message) { return sendMulticastAsync(message, false); } @@ -274,7 +497,9 @@ public ApiFuture sendMulticastAsync(@NonNull MulticastMessage mes * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when * the messages have been sent. + * @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage, boolean)} instead. */ + @Deprecated public ApiFuture sendMulticastAsync( @NonNull MulticastMessage message, boolean dryRun) { checkNotNull(message, "multicast message must not be null"); diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java b/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java index 1271bf98b..6049b4f4d 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java b/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java index a3d92788a..9ff70e59e 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java b/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java index 5648fcf0c..34dc17d6f 100644 --- a/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java +++ b/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/messaging/LightSettings.java b/src/main/java/com/google/firebase/messaging/LightSettings.java index e0e898374..0052cffbf 100644 --- a/src/main/java/com/google/firebase/messaging/LightSettings.java +++ b/src/main/java/com/google/firebase/messaging/LightSettings.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/messaging/LightSettingsColor.java b/src/main/java/com/google/firebase/messaging/LightSettingsColor.java index cfec64995..28f064f47 100644 --- a/src/main/java/com/google/firebase/messaging/LightSettingsColor.java +++ b/src/main/java/com/google/firebase/messaging/LightSettingsColor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/messaging/Message.java b/src/main/java/com/google/firebase/messaging/Message.java index c69abaeef..da790fa7f 100644 --- a/src/main/java/com/google/firebase/messaging/Message.java +++ b/src/main/java/com/google/firebase/messaging/Message.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -54,9 +54,16 @@ public class Message { @Key("apns") private final ApnsConfig apnsConfig; + /** + * @deprecated Use {@link #fid} instead. + */ + @Deprecated @Key("token") private final String token; + @Key("fid") + private final String fid; + @Key("topic") private final String topic; @@ -74,11 +81,14 @@ private Message(Builder builder) { this.apnsConfig = builder.apnsConfig; int count = Booleans.countTrue( !Strings.isNullOrEmpty(builder.token), + !Strings.isNullOrEmpty(builder.fid), !Strings.isNullOrEmpty(builder.topic), !Strings.isNullOrEmpty(builder.condition) ); - checkArgument(count == 1, "Exactly one of token, topic or condition must be specified"); + checkArgument(count == 1, + "Exactly one of token, fid, topic or condition must be specified"); this.token = builder.token; + this.fid = builder.fid; this.topic = stripPrefix(builder.topic); this.condition = builder.condition; this.fcmOptions = builder.fcmOptions; @@ -109,11 +119,20 @@ ApnsConfig getApnsConfig() { return apnsConfig; } + /** + * @deprecated Use {@link #getFid()} instead. + */ + @Deprecated @VisibleForTesting String getToken() { return token; } + @VisibleForTesting + String getFid() { + return fid; + } + @VisibleForTesting String getTopic() { return topic; @@ -166,7 +185,9 @@ public static class Builder { private AndroidConfig androidConfig; private WebpushConfig webpushConfig; private ApnsConfig apnsConfig; + @Deprecated private String token; + private String fid; private String topic; private String condition; private FcmOptions fcmOptions; @@ -222,12 +243,26 @@ public Builder setApnsConfig(ApnsConfig apnsConfig) { * * @param token A valid device registration token. * @return This builder. + * @deprecated Use {@link #setFid(String)} instead. */ + @Deprecated public Builder setToken(String token) { this.token = token; return this; } + /** + * Sets the Firebase Installation ID (FID) of the app instance to which the message + * should be sent. + * + * @param fid A valid Firebase Installation ID. + * @return This builder. + */ + public Builder setFid(String fid) { + this.fid = fid; + return this; + } + /** * Sets the name of the FCM topic to which the message should be sent. Topic names may * contain the {@code /topics/} prefix. diff --git a/src/main/java/com/google/firebase/messaging/MulticastMessage.java b/src/main/java/com/google/firebase/messaging/MulticastMessage.java index b15e58e5f..a880a88f2 100644 --- a/src/main/java/com/google/firebase/messaging/MulticastMessage.java +++ b/src/main/java/com/google/firebase/messaging/MulticastMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,22 +30,26 @@ /** * Represents a message that can be sent to multiple devices via Firebase Cloud Messaging (FCM). - * Contains payload information as well as the list of device registration tokens to which the - * message should be sent. A single {@code MulticastMessage} may contain up to 500 registration - * tokens. + * Contains payload information as well as the list of device registration tokens and/or + * Firebase Installation IDs (FIDs) to which the message should be sent. A single + * {@code MulticastMessage} may contain up to 500 registration tokens and FIDs combined. * *

Instances of this class are thread-safe and immutable. Use {@link MulticastMessage.Builder} * to create new instances. See {@link FirebaseMessaging#sendMulticast(MulticastMessage)} for * details on how to send the message to FCM for multicast delivery. * - *

This class and the associated Builder retain the order of tokens. Therefore the order of - * the responses list obtained by calling {@link BatchResponse#getResponses()} on the return value - * of {@link FirebaseMessaging#sendMulticast(MulticastMessage)} corresponds to the order in which - * tokens were added to the {@link MulticastMessage.Builder}. + *

This class and the associated Builder retain the order of tokens and FIDs. Therefore + * the order of the responses list obtained by calling {@link BatchResponse#getResponses()} + * on the return value of {@link FirebaseMessaging#sendMulticast(MulticastMessage)} + * corresponds to the order in which targets were added to the + * {@link MulticastMessage.Builder}. If both tokens and FIDs are provided, tokens are + * processed first, followed by FIDs. */ public class MulticastMessage { + @Deprecated private final List tokens; + private final List fids; private final Map data; private final Notification notification; private final AndroidConfig androidConfig; @@ -55,11 +59,18 @@ public class MulticastMessage { private MulticastMessage(Builder builder) { this.tokens = builder.tokens.build(); - checkArgument(!this.tokens.isEmpty(), "at least one token must be specified"); - checkArgument(this.tokens.size() <= 500, "no more than 500 tokens can be specified"); + this.fids = builder.fids.build(); + int tokensSize = this.tokens.size(); + int fidsSize = this.fids.size(); + checkArgument(tokensSize + fidsSize > 0, "at least one token or fid must be specified"); + checkArgument(tokensSize + fidsSize <= 500, + "no more than 500 tokens and fids combined can be specified"); for (String token : this.tokens) { checkArgument(!Strings.isNullOrEmpty(token), "none of the tokens can be null or empty"); } + for (String fid : this.fids) { + checkArgument(!Strings.isNullOrEmpty(fid), "none of the fids can be null or empty"); + } this.data = builder.data.isEmpty() ? null : ImmutableMap.copyOf(builder.data); this.notification = builder.notification; this.androidConfig = builder.androidConfig; @@ -69,6 +80,26 @@ private MulticastMessage(Builder builder) { } List getMessageList() { + ImmutableList.Builder messages = ImmutableList.builder(); + + if (!this.tokens.isEmpty()) { + Message.Builder tokenBuilder = getMetadataBuilder(); + for (String token : this.tokens) { + messages.add(tokenBuilder.setToken(token).build()); + } + } + + if (!this.fids.isEmpty()) { + Message.Builder fidBuilder = getMetadataBuilder(); + for (String fid : this.fids) { + messages.add(fidBuilder.setFid(fid).build()); + } + } + + return messages.build(); + } + + private Message.Builder getMetadataBuilder() { Message.Builder builder = Message.builder() .setNotification(this.notification) .setAndroidConfig(this.androidConfig) @@ -78,11 +109,7 @@ List getMessageList() { if (this.data != null) { builder.putAllData(this.data); } - ImmutableList.Builder messages = ImmutableList.builder(); - for (String token : this.tokens) { - messages.add(builder.setToken(token).build()); - } - return messages.build(); + return builder; } /** @@ -96,7 +123,9 @@ public static Builder builder() { public static class Builder { + @Deprecated private final ImmutableList.Builder tokens = ImmutableList.builder(); + private final ImmutableList.Builder fids = ImmutableList.builder(); private final Map data = new HashMap<>(); private Notification notification; private AndroidConfig androidConfig; @@ -107,29 +136,61 @@ public static class Builder { private Builder() {} /** - * Adds a token to which the message should be sent. Up to 500 tokens can be specified on - * a single instance of {@link MulticastMessage}. + * Adds a token to which the message should be sent. Up to 500 tokens + * and FIDs combined can be specified on a single instance of + * {@link MulticastMessage}. * * @param token A non-null, non-empty Firebase device registration token. * @return This builder. + * @deprecated Use {@link #addFid(String)} instead. */ + @Deprecated public Builder addToken(@NonNull String token) { this.tokens.add(token); return this; } /** - * Adds a collection of tokens to which the message should be sent. Up to 500 tokens can be - * specified on a single instance of {@link MulticastMessage}. + * Adds a Firebase Installation ID (FID) to which the message should be sent. + * Up to 500 tokens and FIDs combined can be specified on a single instance + * of {@link MulticastMessage}. + * + * @param fid A non-null, non-empty Firebase Installation ID. + * @return This builder. + */ + public Builder addFid(@NonNull String fid) { + this.fids.add(fid); + return this; + } + + /** + * Adds a collection of tokens to which the message should be sent. Up to 500 + * tokens and FIDs combined can be specified on a single instance of + * {@link MulticastMessage}. * * @param tokens Collection of Firebase device registration tokens. * @return This builder. + * @deprecated Use {@link #addAllFids(Collection)} instead. */ + @Deprecated public Builder addAllTokens(@NonNull Collection tokens) { this.tokens.addAll(tokens); return this; } + /** + * Adds a collection of Firebase Installation IDs (FIDs) to which the message + * should be sent. Up to 500 tokens and FIDs combined can be specified on a + * single instance of {@link MulticastMessage}. + * + * @param fids Collection of Firebase Installation IDs. + * @return This builder. + */ + public Builder addAllFids(@NonNull Collection fids) { + this.fids.addAll(fids); + return this; + } + /** * Sets the notification information to be included in the message. * diff --git a/src/main/java/com/google/firebase/messaging/Notification.java b/src/main/java/com/google/firebase/messaging/Notification.java index a8f48c2ef..8dcf1e5e0 100644 --- a/src/main/java/com/google/firebase/messaging/Notification.java +++ b/src/main/java/com/google/firebase/messaging/Notification.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/messaging/SendResponse.java b/src/main/java/com/google/firebase/messaging/SendResponse.java index b179df2b2..58c4fe970 100644 --- a/src/main/java/com/google/firebase/messaging/SendResponse.java +++ b/src/main/java/com/google/firebase/messaging/SendResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/messaging/TopicManagementResponse.java b/src/main/java/com/google/firebase/messaging/TopicManagementResponse.java index bbf4c944a..f02590f74 100644 --- a/src/main/java/com/google/firebase/messaging/TopicManagementResponse.java +++ b/src/main/java/com/google/firebase/messaging/TopicManagementResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -37,10 +37,8 @@ public class TopicManagementResponse { // Server error codes as defined in https://developers.google.com/instance-id/reference/server // TODO: Should we handle other error codes here (e.g. PERMISSION_DENIED)? private static final Map ERROR_CODES = ImmutableMap.builder() - .put("INVALID_ARGUMENT", "invalid-argument") .put("NOT_FOUND", "registration-token-not-registered") .put("INTERNAL", "internal-error") - .put("TOO_MANY_TOPICS", "too-many-topics") .build(); private final int successCount; @@ -101,8 +99,11 @@ public static class Error { private Error(int index, String reason) { this.index = index; - this.reason = ERROR_CODES.containsKey(reason) - ? ERROR_CODES.get(reason) : UNKNOWN_ERROR; + if (reason == null || reason.trim().isEmpty()) { + this.reason = UNKNOWN_ERROR; + } else { + this.reason = ERROR_CODES.getOrDefault(reason, reason.toLowerCase().replace('_', '-')); + } } /** diff --git a/src/main/java/com/google/firebase/messaging/WebpushConfig.java b/src/main/java/com/google/firebase/messaging/WebpushConfig.java index 9cde9d041..c73e0bee0 100644 --- a/src/main/java/com/google/firebase/messaging/WebpushConfig.java +++ b/src/main/java/com/google/firebase/messaging/WebpushConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/messaging/WebpushNotification.java b/src/main/java/com/google/firebase/messaging/WebpushNotification.java index d17380543..b86393afb 100644 --- a/src/main/java/com/google/firebase/messaging/WebpushNotification.java +++ b/src/main/java/com/google/firebase/messaging/WebpushNotification.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/messaging/internal/package-info.java b/src/main/java/com/google/firebase/messaging/internal/package-info.java index 5ef722b72..5c55f2534 100644 --- a/src/main/java/com/google/firebase/messaging/internal/package-info.java +++ b/src/main/java/com/google/firebase/messaging/internal/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerification.java b/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerification.java new file mode 100644 index 000000000..6960f5038 --- /dev/null +++ b/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerification.java @@ -0,0 +1,84 @@ +/* + * Copyright 2026 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.phonenumberverification; + +import com.google.firebase.FirebaseApp; +import com.google.firebase.ImplFirebaseTrampolines; +import com.google.firebase.internal.FirebaseService; +import com.google.firebase.phonenumberverification.internal.FirebasePhoneNumberVerificationTokenVerifier; + +/** + * This class is the entry point for the Firebase Phone Number Verification service. + * + *

You can get an instance of {@link FirebasePhoneNumberVerification} via {@link #getInstance()}, + * or {@link #getInstance(FirebaseApp)}. + */ +public final class FirebasePhoneNumberVerification { + private static final String SERVICE_ID = FirebasePhoneNumberVerification.class.getName(); + private final FirebasePhoneNumberVerificationTokenVerifier tokenVerifier; + + private FirebasePhoneNumberVerification(FirebaseApp app) { + this.tokenVerifier = new FirebasePhoneNumberVerificationTokenVerifier(app); + } + + /** + * Gets the {@link FirebasePhoneNumberVerification} instance for the default {@link FirebaseApp}. + * + * @return The {@link FirebasePhoneNumberVerification} instance for the default + * {@link FirebaseApp}. + */ + public static FirebasePhoneNumberVerification getInstance() { + return getInstance(FirebaseApp.getInstance()); + } + + /** + * Gets the {@link FirebasePhoneNumberVerification} instance for the specified + * {@link FirebaseApp}. + * + * @return The {@link FirebasePhoneNumberVerification} instance for the specified + * {@link FirebaseApp}. + */ + public static synchronized FirebasePhoneNumberVerification getInstance(FirebaseApp app) { + FirebasePhoneNumberVerificationService service = + ImplFirebaseTrampolines.getService(app, SERVICE_ID, + FirebasePhoneNumberVerificationService.class); + if (service == null) { + service = ImplFirebaseTrampolines.addService( + app, new FirebasePhoneNumberVerificationService(app)); + } + return service.getInstance(); + } + + /** + * Verifies a Firebase Phone Number Verification token (JWT). + * + * @param phoneNumberVerificationJwt The JWT string to verify. + * @return A verified {@link FirebasePhoneNumberVerificationToken}. + * @throws FirebasePhoneNumberVerificationException If verification fails. + */ + public FirebasePhoneNumberVerificationToken verifyToken(String phoneNumberVerificationJwt) + throws FirebasePhoneNumberVerificationException { + return this.tokenVerifier.verifyToken(phoneNumberVerificationJwt); + } + + private static class FirebasePhoneNumberVerificationService + extends FirebaseService { + FirebasePhoneNumberVerificationService(FirebaseApp app) { + super(SERVICE_ID, new FirebasePhoneNumberVerification(app)); + } + } +} diff --git a/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationErrorCode.java b/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationErrorCode.java new file mode 100644 index 000000000..767f91c1e --- /dev/null +++ b/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationErrorCode.java @@ -0,0 +1,48 @@ +/* + * Copyright 2026 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.phonenumberverification; + +/** + * Error codes that can be raised by the Phone Number Verification APIs. + */ +public enum FirebasePhoneNumberVerificationErrorCode { + + /** + * One or more arguments specified in the request were invalid. + */ + INVALID_ARGUMENT, + + /** + * The provided phone number verification token is invalid or malformed. + */ + INVALID_TOKEN, + + /** + * The provided phone number verification token has expired. + */ + TOKEN_EXPIRED, + + /** + * Internal error encountered during phone number verification. + */ + INTERNAL_ERROR, + + /** + * Phone number verification service is temporarily unavailable. + */ + SERVICE_ERROR, +} diff --git a/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationException.java b/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationException.java new file mode 100644 index 000000000..c2e1ddfe7 --- /dev/null +++ b/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationException.java @@ -0,0 +1,51 @@ +/* + * Copyright 2026 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.phonenumberverification; + +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseException; +import com.google.firebase.IncomingHttpResponse; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; + +/** + * Generic exception related to Firebase Phone Number Verification. Check the error code and message + * for more details. + */ +public class FirebasePhoneNumberVerificationException extends FirebaseException { + + private final FirebasePhoneNumberVerificationErrorCode errorCode; + + public FirebasePhoneNumberVerificationException( + @NonNull ErrorCode errorCode, + @NonNull String message, + Throwable cause, + IncomingHttpResponse response, + FirebasePhoneNumberVerificationErrorCode phoneErrorCode) { + super(errorCode, message, cause, response); + this.errorCode = phoneErrorCode; + } + + public FirebasePhoneNumberVerificationException(FirebaseException base) { + this(base.getErrorCode(), base.getMessage(), base.getCause(), base.getHttpResponse(), null); + } + + @Nullable + public FirebasePhoneNumberVerificationErrorCode getPhoneNumberVerificationErrorCode() { + return errorCode; + } +} diff --git a/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationToken.java b/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationToken.java new file mode 100644 index 000000000..15404cdc2 --- /dev/null +++ b/src/main/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationToken.java @@ -0,0 +1,102 @@ +/* + * Copyright 2026 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.phonenumberverification; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.Map; + +/** + * Represents a verified Firebase Phone Number Verification token. + */ +public class FirebasePhoneNumberVerificationToken { + private final Map claims; + + /** + * Create an instance of {@link FirebasePhoneNumberVerificationToken} from a map of JWT claims. + * + * @param claims A map of JWT claims. + */ + public FirebasePhoneNumberVerificationToken(Map claims) { + checkNotNull(claims, "Claims map must not be null"); + checkArgument(claims.containsKey("sub"), "Claims map must contain sub"); + this.claims = ImmutableMap.copyOf(claims); + } + + /** + * Returns the issuer identifier for the issuer of the response. + */ + public String getIssuer() { + return (String) claims.get("iss"); + } + + /** + * Returns the phone number of the user. + * This corresponds to the 'sub' claim in the JWT. + */ + public String getPhoneNumber() { + return (String) claims.get("sub"); + } + + /** + * Returns the audience for which this token is intended. + */ + public List getAudience() { + Object audience = claims.get("aud"); + if (audience instanceof String) { + return ImmutableList.of((String) audience); + } else if (audience instanceof List) { + @SuppressWarnings("unchecked") + List audienceList = (List) audience; + return ImmutableList.copyOf(audienceList); + } + return ImmutableList.of(); + } + + /** + * Returns the expiration time in seconds since the Unix epoch. + */ + public long getExpirationTime() { + Object exp = claims.get("exp"); + if (exp instanceof java.util.Date) { + return ((java.util.Date) exp).getTime() / 1000L; + } + return exp instanceof Number ? ((Number) exp).longValue() : 0L; + } + + /** + * Returns the issued-at time in seconds since the Unix epoch. + */ + public long getIssuedAt() { + Object iat = claims.get("iat"); + if (iat instanceof java.util.Date) { + return ((java.util.Date) iat).getTime() / 1000L; + } + return iat instanceof Number ? ((Number) iat).longValue() : 0L; + } + + /** + * Returns the entire map of claims. + */ + public Map getClaims() { + return claims; + } +} diff --git a/src/main/java/com/google/firebase/phonenumberverification/internal/FirebasePhoneNumberVerificationTokenVerifier.java b/src/main/java/com/google/firebase/phonenumberverification/internal/FirebasePhoneNumberVerificationTokenVerifier.java new file mode 100644 index 000000000..c51c8f9bf --- /dev/null +++ b/src/main/java/com/google/firebase/phonenumberverification/internal/FirebasePhoneNumberVerificationTokenVerifier.java @@ -0,0 +1,252 @@ +/* + * Copyright 2026 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.phonenumberverification.internal; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Strings; +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseApp; +import com.google.firebase.ImplFirebaseTrampolines; +import com.google.firebase.phonenumberverification.FirebasePhoneNumberVerificationErrorCode; +import com.google.firebase.phonenumberverification.FirebasePhoneNumberVerificationException; +import com.google.firebase.phonenumberverification.FirebasePhoneNumberVerificationToken; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.jwk.source.JWKSourceBuilder; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jose.proc.JWSKeySelector; +import com.nimbusds.jose.proc.JWSVerificationKeySelector; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import com.nimbusds.jwt.proc.DefaultJWTProcessor; +import com.nimbusds.jwt.proc.ExpiredJWTException; +import java.net.MalformedURLException; +import java.net.URI; +import java.text.ParseException; + +/** + * Internal class to verify Firebase Phone Number Verification tokens. + */ +public class FirebasePhoneNumberVerificationTokenVerifier { + private static final String FPNV_JWKS_URL = "https://fpnv.googleapis.com/v1beta/jwks"; + private static final String HEADER_TYP = "JWT"; + + private final String projectId; + private volatile DefaultJWTProcessor jwtProcessor; + + /** + * Create {@link FirebasePhoneNumberVerificationTokenVerifier} for internal purposes. + * + * @param app The {@link FirebaseApp} to get a project ID from. + */ + public FirebasePhoneNumberVerificationTokenVerifier(FirebaseApp app) { + this.projectId = getProjectId(app); + } + + /** + * Package-private constructor designed explicitly for dependency injection + * during isolated unit testing flows. + */ + FirebasePhoneNumberVerificationTokenVerifier( + String projectId, DefaultJWTProcessor jwtProcessor) { + this.projectId = projectId; + this.jwtProcessor = jwtProcessor; + } + + private DefaultJWTProcessor getJwtProcessor() { + DefaultJWTProcessor processor = this.jwtProcessor; + if (processor == null) { + synchronized (this) { + processor = this.jwtProcessor; + if (processor == null) { + processor = createJwtProcessor(); + this.jwtProcessor = processor; + } + } + } + return processor; + } + + /** + * Main method that performs token verification steps. + * + * @param token String input data + * @return {@link FirebasePhoneNumberVerificationToken} + * @throws FirebasePhoneNumberVerificationException If verification fails + */ + public FirebasePhoneNumberVerificationToken verifyToken(String token) + throws FirebasePhoneNumberVerificationException { + checkArgument(!Strings.isNullOrEmpty(token), + "Firebase Phone Number Verification token must not be null or empty"); + + try { + SignedJWT signedJwt = SignedJWT.parse(token); + verifyHeader(signedJwt.getHeader()); + + JWTClaimsSet claims = getJwtProcessor().process(signedJwt, null); + verifyClaims(claims); + + return new FirebasePhoneNumberVerificationToken(claims.getClaims()); + } catch (ParseException e) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + "Failed to parse JWT token: " + e.getMessage(), + e + ); + } catch (ExpiredJWTException e) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.TOKEN_EXPIRED, + "Firebase Phone Number Verification token has expired.", + e + ); + } catch (BadJOSEException e) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + "Check your project: " + projectId + ". " + + "Firebase Phone Number Verification token is invalid: " + + e.getMessage(), + e + ); + } catch (JOSEException e) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INTERNAL_ERROR, + "Check your project: " + projectId + ". Failed to verify " + + "Firebase Phone Number Verification token signature: " + e.getMessage(), + e + ); + } + } + + private void verifyHeader(JWSHeader header) throws FirebasePhoneNumberVerificationException { + if (!JWSAlgorithm.ES256.equals(header.getAlgorithm())) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_ARGUMENT, + "Firebase Phone Number Verification token has incorrect 'algorithm'. " + + "Expected " + JWSAlgorithm.ES256.getName() + " but got " + header.getAlgorithm()); + } + if (Strings.isNullOrEmpty(header.getKeyID())) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_ARGUMENT, + "Firebase Phone Number Verification token has no 'kid' claim." + ); + } + if (!JOSEObjectType.JWT.equals(header.getType())) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_ARGUMENT, + "Firebase Phone Number Verification token has incorrect 'typ'. Expected " + HEADER_TYP + + " but got " + header.getType() + ); + } + } + + private void verifyClaims(JWTClaimsSet claims) throws FirebasePhoneNumberVerificationException { + checkNotNull(claims, "JWTClaimsSet claims must not be null"); + String issuer = claims.getIssuer(); + + if (Strings.isNullOrEmpty(issuer)) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_ARGUMENT, + "Firebase Phone Number Verification token has no 'iss' (issuer) claim."); + } + + String expectedIssuer = "https://fpnv.googleapis.com/projects/" + this.projectId; + if (!expectedIssuer.equals(issuer)) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + "Firebase Phone Number Verification token has an incorrect 'iss' (issuer) claim."); + } + + if (claims.getAudience().isEmpty() || !claims.getAudience().contains(issuer)) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + "Invalid audience. Expected to contain: " + issuer + + " but found: " + claims.getAudience() + ); + } + + if (Strings.isNullOrEmpty(claims.getSubject())) { + throw newException( + FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + "Token has an empty 'sub' (phone number)." + ); + } + } + + private DefaultJWTProcessor createJwtProcessor() { + DefaultJWTProcessor processor = new DefaultJWTProcessor<>(); + try { + JWKSource keySource = createKeySource(); + JWSKeySelector keySelector = + new JWSVerificationKeySelector<>(JWSAlgorithm.ES256, keySource); + processor.setJWSKeySelector(keySelector); + } catch (MalformedURLException e) { + throw new IllegalStateException("Invalid JWKS URL", e); + } + return processor; + } + + protected JWKSource createKeySource() throws MalformedURLException { + return JWKSourceBuilder + .create(URI.create(FPNV_JWKS_URL).toURL()) + .retrying(true) + .build(); + } + + private String getProjectId(FirebaseApp app) { + String projectId = ImplFirebaseTrampolines.getProjectId(app); + if (Strings.isNullOrEmpty(projectId)) { + throw new IllegalArgumentException("Project ID is required in FirebaseOptions."); + } + return projectId; + } + + private FirebasePhoneNumberVerificationException newException( + FirebasePhoneNumberVerificationErrorCode errorCode, String message) { + return newException(errorCode, message, null); + } + + private FirebasePhoneNumberVerificationException newException( + FirebasePhoneNumberVerificationErrorCode errorCode, String message, Throwable cause) { + ErrorCode baseCode = ErrorCode.INTERNAL; + if (errorCode != null) { + switch (errorCode) { + case INVALID_ARGUMENT: + baseCode = ErrorCode.INVALID_ARGUMENT; + break; + case TOKEN_EXPIRED: + case INVALID_TOKEN: + baseCode = ErrorCode.UNAUTHENTICATED; + break; + case SERVICE_ERROR: + baseCode = ErrorCode.UNAVAILABLE; + break; + case INTERNAL_ERROR: + default: + baseCode = ErrorCode.INTERNAL; + break; + } + } + return new FirebasePhoneNumberVerificationException( + baseCode, message, cause, null, errorCode); + } +} diff --git a/src/main/java/com/google/firebase/projectmanagement/AndroidApp.java b/src/main/java/com/google/firebase/projectmanagement/AndroidApp.java index dd7a00e15..891280c72 100644 --- a/src/main/java/com/google/firebase/projectmanagement/AndroidApp.java +++ b/src/main/java/com/google/firebase/projectmanagement/AndroidApp.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/AndroidAppMetadata.java b/src/main/java/com/google/firebase/projectmanagement/AndroidAppMetadata.java index 055994d51..61263fc12 100644 --- a/src/main/java/com/google/firebase/projectmanagement/AndroidAppMetadata.java +++ b/src/main/java/com/google/firebase/projectmanagement/AndroidAppMetadata.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/AndroidAppService.java b/src/main/java/com/google/firebase/projectmanagement/AndroidAppService.java index 081b4467c..d04314b4e 100644 --- a/src/main/java/com/google/firebase/projectmanagement/AndroidAppService.java +++ b/src/main/java/com/google/firebase/projectmanagement/AndroidAppService.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagement.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagement.java index 9e294e099..be60f0b70 100644 --- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagement.java +++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagement.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementException.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementException.java index 580ae76f6..04fc48c2f 100644 --- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementException.java +++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementException.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java index 2bf927008..1387d2226 100644 --- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java +++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java b/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java index fb29f7b86..d0f5942b1 100644 --- a/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java +++ b/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/IosApp.java b/src/main/java/com/google/firebase/projectmanagement/IosApp.java index afd54e711..16ec77b11 100644 --- a/src/main/java/com/google/firebase/projectmanagement/IosApp.java +++ b/src/main/java/com/google/firebase/projectmanagement/IosApp.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/IosAppMetadata.java b/src/main/java/com/google/firebase/projectmanagement/IosAppMetadata.java index 5de66f7ac..b1a67c076 100644 --- a/src/main/java/com/google/firebase/projectmanagement/IosAppMetadata.java +++ b/src/main/java/com/google/firebase/projectmanagement/IosAppMetadata.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/IosAppService.java b/src/main/java/com/google/firebase/projectmanagement/IosAppService.java index c1e68faf8..b6d3e684d 100644 --- a/src/main/java/com/google/firebase/projectmanagement/IosAppService.java +++ b/src/main/java/com/google/firebase/projectmanagement/IosAppService.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/Scheduler.java b/src/main/java/com/google/firebase/projectmanagement/Scheduler.java index 3df12a94a..bac493e3e 100644 --- a/src/main/java/com/google/firebase/projectmanagement/Scheduler.java +++ b/src/main/java/com/google/firebase/projectmanagement/Scheduler.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/google/firebase/projectmanagement/ShaCertificate.java b/src/main/java/com/google/firebase/projectmanagement/ShaCertificate.java index d587f8153..13b298c95 100644 --- a/src/main/java/com/google/firebase/projectmanagement/ShaCertificate.java +++ b/src/main/java/com/google/firebase/projectmanagement/ShaCertificate.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/projectmanagement/ShaCertificateType.java b/src/main/java/com/google/firebase/projectmanagement/ShaCertificateType.java index a367ee410..e62c8ca69 100644 --- a/src/main/java/com/google/firebase/projectmanagement/ShaCertificateType.java +++ b/src/main/java/com/google/firebase/projectmanagement/ShaCertificateType.java @@ -1,4 +1,4 @@ -/* Copyright 2018 Google Inc. +/* 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. diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java new file mode 100644 index 000000000..ec118cef2 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.AndConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; + +import java.util.List; +import java.util.stream.Collectors; + +final class AndCondition { + private final ImmutableList conditions; + + AndCondition(@NonNull List conditions) { + checkNotNull(conditions, "List of conditions for AND operation must not be null."); + checkArgument(!conditions.isEmpty(), + "List of conditions for AND operation must not be empty."); + this.conditions = ImmutableList.copyOf(conditions); + } + + AndCondition(AndConditionResponse andConditionResponse) { + List conditionList = andConditionResponse.getConditions(); + checkNotNull(conditionList, "List of conditions for AND operation must not be null."); + checkArgument(!conditionList.isEmpty(), + "List of conditions for AND operation must not be empty"); + this.conditions = conditionList.stream() + .map(OneOfCondition::new) + .collect(ImmutableList.toImmutableList()); + } + + @NonNull + ImmutableList getConditions() { + return conditions; + } + + AndConditionResponse toAndConditionResponse() { + return new AndConditionResponse() + .setConditions(this.conditions.stream() + .map(OneOfCondition::toOneOfConditionResponse) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java new file mode 100644 index 000000000..63718b32b --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -0,0 +1,346 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.BiPredicate; +import java.util.function.IntPredicate; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class ConditionEvaluator { + private static final int MAX_CONDITION_RECURSION_DEPTH = 10; + private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + private static final BigInteger MICRO_PERCENT_MODULO = BigInteger.valueOf(100_000_000L); + private static final Pattern SEMVER_PATTERN = Pattern.compile("^[0-9]+(?:\\.[0-9]+){0,4}$"); + + /** + * Evaluates server conditions and assigns a boolean value to each condition. + * + * @param conditions List of conditions which are to be evaluated. + * @param context A map with additional metadata used during evaluation. + * @return A map of condition to evaluated value. + */ + @NonNull + Map evaluateConditions( + @NonNull List conditions, @Nullable KeysAndValues context) { + checkNotNull(conditions, "List of conditions must not be null."); + checkArgument(!conditions.isEmpty(), "List of conditions must not be empty."); + if (context == null || conditions.isEmpty()) { + return ImmutableMap.of(); + } + KeysAndValues evaluationContext = + context != null ? context : new KeysAndValues.Builder().build(); + + Map evaluatedConditions = + conditions.stream() + .collect( + toImmutableMap( + ServerCondition::getName, + condition -> + evaluateCondition( + condition.getCondition(), evaluationContext, /* nestingLevel= */ 0))); + + return evaluatedConditions; + } + + private boolean evaluateCondition( + OneOfCondition condition, KeysAndValues context, int nestingLevel) { + if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH) { + logger.warn("Maximum condition recursion depth exceeded."); + return false; + } + + if (condition.getOrCondition() != null) { + return evaluateOrCondition(condition.getOrCondition(), context, nestingLevel + 1); + } else if (condition.getAndCondition() != null) { + return evaluateAndCondition(condition.getAndCondition(), context, nestingLevel + 1); + } else if (condition.isTrue() != null) { + return true; + } else if (condition.isFalse() != null) { + return false; + } else if (condition.getCustomSignal() != null) { + return evaluateCustomSignalCondition(condition.getCustomSignal(), context); + } else if (condition.getPercent() != null) { + return evaluatePercentCondition(condition.getPercent(), context); + } + logger.atWarn().log("Received invalid condition for evaluation."); + return false; + } + + private boolean evaluateOrCondition( + OrCondition condition, KeysAndValues context, int nestingLevel) { + return condition.getConditions().stream() + .anyMatch(subCondition -> evaluateCondition(subCondition, context, nestingLevel + 1)); + } + + private boolean evaluateAndCondition( + AndCondition condition, KeysAndValues context, int nestingLevel) { + return condition.getConditions().stream() + .allMatch(subCondition -> evaluateCondition(subCondition, context, nestingLevel + 1)); + } + + private boolean evaluateCustomSignalCondition( + CustomSignalCondition condition, KeysAndValues context) { + CustomSignalOperator customSignalOperator = condition.getCustomSignalOperator(); + String customSignalKey = condition.getCustomSignalKey(); + ImmutableList targetCustomSignalValues = + ImmutableList.copyOf(condition.getTargetCustomSignalValues()); + + if (targetCustomSignalValues.isEmpty()) { + logger.warn( + String.format( + "Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s", + customSignalOperator, customSignalKey, targetCustomSignalValues)); + return false; + } + + String customSignalValue = context.get(customSignalKey); + if (customSignalValue == null) { + return false; + } + + switch (customSignalOperator) { + // String operations. + case STRING_CONTAINS: + return compareStrings( + targetCustomSignalValues, + customSignalValue, + (customSignal, targetSignal) -> customSignal.contains(targetSignal)); + case STRING_DOES_NOT_CONTAIN: + return !compareStrings( + targetCustomSignalValues, + customSignalValue, + (customSignal, targetSignal) -> customSignal.contains(targetSignal)); + case STRING_EXACTLY_MATCHES: + return compareStrings( + targetCustomSignalValues, + customSignalValue, + (customSignal, targetSignal) -> customSignal.equals(targetSignal)); + case STRING_CONTAINS_REGEX: + return compareStrings( + targetCustomSignalValues, + customSignalValue, + (customSignal, targetSignal) -> compareStringRegex(customSignal, targetSignal)); + + // Numeric operations. + case NUMERIC_LESS_THAN: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result < 0); + case NUMERIC_LESS_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result <= 0); + case NUMERIC_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result == 0); + case NUMERIC_NOT_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result != 0); + case NUMERIC_GREATER_THAN: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result > 0); + case NUMERIC_GREATER_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result >= 0); + + // Semantic operations. + case SEMANTIC_VERSION_EQUAL: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result == 0); + case SEMANTIC_VERSION_GREATER_EQUAL: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result >= 0); + case SEMANTIC_VERSION_GREATER_THAN: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result > 0); + case SEMANTIC_VERSION_LESS_EQUAL: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result <= 0); + case SEMANTIC_VERSION_LESS_THAN: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result < 0); + case SEMANTIC_VERSION_NOT_EQUAL: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result != 0); + default: + return false; + } + } + + private boolean evaluatePercentCondition(PercentCondition condition, KeysAndValues context) { + if (!context.containsKey("randomizationId")) { + logger.warn("Percentage operation must not be performed without randomizationId"); + return false; + } + + PercentConditionOperator operator = condition.getPercentConditionOperator(); + + // The micro-percent interval to be used with the BETWEEN operator. + MicroPercentRange microPercentRange = condition.getMicroPercentRange(); + int microPercentUpperBound = + microPercentRange != null ? microPercentRange.getMicroPercentUpperBound() : 0; + int microPercentLowerBound = + microPercentRange != null ? microPercentRange.getMicroPercentLowerBound() : 0; + // The limit of percentiles to target in micro-percents when using the + // LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 + // and 100000000]. + int microPercent = condition.getMicroPercent(); + BigInteger microPercentile = + getMicroPercentile(condition.getSeed(), context.get("randomizationId")); + switch (operator) { + case LESS_OR_EQUAL: + return microPercentile.compareTo(BigInteger.valueOf(microPercent)) <= 0; + case GREATER_THAN: + return microPercentile.compareTo(BigInteger.valueOf(microPercent)) > 0; + case BETWEEN: + return microPercentile.compareTo(BigInteger.valueOf(microPercentLowerBound)) > 0 + && microPercentile.compareTo(BigInteger.valueOf(microPercentUpperBound)) <= 0; + case UNSPECIFIED: + default: + return false; + } + } + + private BigInteger getMicroPercentile(String seed, String randomizationId) { + String seedPrefix = seed != null && !seed.isEmpty() ? seed + "." : ""; + String stringToHash = seedPrefix + randomizationId; + BigInteger hash = hashSeededRandomizationId(stringToHash); + BigInteger microPercentile = hash.mod(MICRO_PERCENT_MODULO); + + return microPercentile; + } + + private BigInteger hashSeededRandomizationId(String seededRandomizationId) { + try { + // Create a SHA-256 hash. + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(seededRandomizationId.getBytes(StandardCharsets.UTF_8)); + + // Convert the hash bytes to a BigInteger + return new BigInteger(1, hashBytes); + } catch (NoSuchAlgorithmException e) { + logger.error("SHA-256 algorithm not found", e); + throw new RuntimeException("SHA-256 algorithm not found", e); + } + } + + private boolean compareStrings( + ImmutableList targetValues, + String customSignal, + BiPredicate compareFunction) { + return targetValues.stream() + .anyMatch(targetValue -> compareFunction.test(customSignal, targetValue)); + } + + private boolean compareStringRegex(String customSignal, String targetSignal) { + try { + return Pattern.compile(targetSignal).matcher(customSignal).matches(); + } catch (PatternSyntaxException e) { + return false; + } + } + + private boolean compareNumbers( + ImmutableList targetValues, String customSignal, IntPredicate compareFunction) { + if (targetValues.size() != 1) { + logger.warn( + String.format( + "Target values must contain 1 element for numeric operations. Target Value: %s", + targetValues)); + return false; + } + + try { + double customSignalDouble = Double.parseDouble(customSignal); + double targetValue = Double.parseDouble(targetValues.get(0)); + int comparisonResult = Double.compare(customSignalDouble, targetValue); + return compareFunction.test(comparisonResult); + } catch (NumberFormatException e) { + logger.warn( + "Error parsing numeric values: customSignal=%s, targetValue=%s", + customSignal, targetValues.get(0), e); + return false; + } + } + + private boolean compareSemanticVersions( + ImmutableList targetValues, String customSignal, IntPredicate compareFunction) { + if (targetValues.size() != 1) { + logger.warn(String.format("Target values must contain 1 element for semantic operation.")); + return false; + } + + String targetValueString = targetValues.get(0); + if (!validateSemanticVersion(targetValueString) || !validateSemanticVersion(customSignal)) { + return false; + } + + List targetVersion = parseSemanticVersion(targetValueString); + List customSignalVersion = parseSemanticVersion(customSignal); + + int maxLength = 5; + if (targetVersion.size() > maxLength || customSignalVersion.size() > maxLength) { + logger.warn( + "Semantic version max length(%s) exceeded. Target: %s, Custom Signal: %s", + maxLength, targetValueString, customSignal); + return false; + } + + int comparison = compareSemanticVersions(customSignalVersion, targetVersion); + return compareFunction.test(comparison); + } + + private int compareSemanticVersions(List version1, List version2) { + int maxLength = Math.max(version1.size(), version2.size()); + int version1Size = version1.size(); + int version2Size = version2.size(); + + for (int i = 0; i < maxLength; i++) { + // Default to 0 if segment is missing + int v1 = i < version1Size ? version1.get(i) : 0; + int v2 = i < version2Size ? version2.get(i) : 0; + + int comparison = Integer.compare(v1, v2); + if (comparison != 0) { + return comparison; + } + } + // Versions are equal + return 0; + } + + private List parseSemanticVersion(String versionString) { + return Arrays.stream(versionString.split("\\.")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } + + private boolean validateSemanticVersion(String version) { + return SEMVER_PATTERN.matcher(version).matches(); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java new file mode 100644 index 000000000..a8d96efdf --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java @@ -0,0 +1,140 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.CustomSignalConditionResponse; + +import java.util.ArrayList; +import java.util.List; + +final class CustomSignalCondition { + private final String customSignalKey; + private final CustomSignalOperator customSignalOperator; + private final ImmutableList targetCustomSignalValues; + + public CustomSignalCondition( + @NonNull String customSignalKey, + @NonNull CustomSignalOperator customSignalOperator, + @NonNull List targetCustomSignalValues) { + checkArgument( + !Strings.isNullOrEmpty(customSignalKey), "Custom signal key must not be null or empty."); + checkNotNull(customSignalOperator); + checkNotNull(targetCustomSignalValues); + checkArgument( + !targetCustomSignalValues.isEmpty(), "Target custom signal values must not be empty."); + this.customSignalKey = customSignalKey.trim(); + this.customSignalOperator = customSignalOperator; + this.targetCustomSignalValues = ImmutableList.copyOf(targetCustomSignalValues); + } + + CustomSignalCondition(CustomSignalConditionResponse customSignalCondition) { + checkArgument( + !Strings.isNullOrEmpty(customSignalCondition.getKey()), + "Custom signal key must not be null or empty."); + checkArgument( + !customSignalCondition.getTargetValues().isEmpty(), + "Target custom signal values must not be empty."); + this.customSignalKey = customSignalCondition.getKey().trim(); + List targetCustomSignalValuesList = customSignalCondition.getTargetValues(); + this.targetCustomSignalValues = ImmutableList.copyOf(targetCustomSignalValuesList); + switch (customSignalCondition.getOperator()) { + case "NUMERIC_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_EQUAL; + break; + case "NUMERIC_GREATER_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_GREATER_EQUAL; + break; + case "NUMERIC_GREATER_THAN": + this.customSignalOperator = CustomSignalOperator.NUMERIC_GREATER_THAN; + break; + case "NUMERIC_LESS_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_LESS_EQUAL; + break; + case "NUMERIC_LESS_THAN": + this.customSignalOperator = CustomSignalOperator.NUMERIC_LESS_THAN; + break; + case "NUMERIC_NOT_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_NOT_EQUAL; + break; + case "SEMANTIC_VERSION_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_EQUAL; + break; + case "SEMANTIC_VERSION_GREATER_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL; + break; + case "SEMANTIC_VERSION_GREATER_THAN": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN; + break; + case "SEMANTIC_VERSION_LESS_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL; + break; + case "SEMANTIC_VERSION_LESS_THAN": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN; + break; + case "SEMANTIC_VERSION_NOT_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL; + break; + case "STRING_CONTAINS": + this.customSignalOperator = CustomSignalOperator.STRING_CONTAINS; + break; + case "STRING_CONTAINS_REGEX": + this.customSignalOperator = CustomSignalOperator.STRING_CONTAINS_REGEX; + break; + case "STRING_DOES_NOT_CONTAIN": + this.customSignalOperator = CustomSignalOperator.STRING_DOES_NOT_CONTAIN; + break; + case "STRING_EXACTLY_MATCHES": + this.customSignalOperator = CustomSignalOperator.STRING_EXACTLY_MATCHES; + break; + default: + this.customSignalOperator = CustomSignalOperator.UNSPECIFIED; + } + checkArgument( + this.customSignalOperator != CustomSignalOperator.UNSPECIFIED, + "Custom signal operator passed is invalid"); + } + + @NonNull + String getCustomSignalKey() { + return customSignalKey; + } + + @NonNull + CustomSignalOperator getCustomSignalOperator() { + return customSignalOperator; + } + + @NonNull + List getTargetCustomSignalValues() { + return new ArrayList<>(targetCustomSignalValues); + } + + CustomSignalConditionResponse toCustomConditonResponse() { + CustomSignalConditionResponse customSignalConditionResponse = + new CustomSignalConditionResponse(); + customSignalConditionResponse.setKey(this.customSignalKey); + customSignalConditionResponse.setOperator(this.customSignalOperator.getOperator()); + customSignalConditionResponse.setTargetValues(this.targetCustomSignalValues); + return customSignalConditionResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java new file mode 100644 index 000000000..0c0924d62 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +enum CustomSignalOperator { + NUMERIC_EQUAL("NUMERIC_EQUAL"), + NUMERIC_GREATER_EQUAL("NUMERIC_GREATER_EQUAL"), + NUMERIC_GREATER_THAN("NUMERIC_GREATER_THAN"), + NUMERIC_LESS_EQUAL("NUMERIC_LESS_EQUAL"), + NUMERIC_LESS_THAN("NUMERIC_LESS_THAN"), + NUMERIC_NOT_EQUAL("NUMERIC_NOT_EQUAL"), + SEMANTIC_VERSION_EQUAL("SEMANTIC_VERSION_EQUAL"), + SEMANTIC_VERSION_GREATER_EQUAL("SEMANTIC_VERSION_GREATER_EQUAL"), + SEMANTIC_VERSION_GREATER_THAN("SEMANTIC_VERSION_GREATER_THAN"), + SEMANTIC_VERSION_LESS_EQUAL("SEMANTIC_VERSION_LESS_EQUAL"), + SEMANTIC_VERSION_LESS_THAN("SEMANTIC_VERSION_LESS_THAN"), + SEMANTIC_VERSION_NOT_EQUAL("SEMANTIC_VERSION_NOT_EQUAL"), + STRING_CONTAINS("STRING_CONTAINS"), + STRING_CONTAINS_REGEX("STRING_CONTAINS_REGEX"), + STRING_DOES_NOT_CONTAIN("STRING_DOES_NOT_CONTAIN"), + STRING_EXACTLY_MATCHES("STRING_EXACTLY_MATCHES"), + UNSPECIFIED("CUSTOM_SIGNAL_OPERATOR_UNSPECIFIED"); + + private final String operator; + + CustomSignalOperator(@NonNull String operator) { + checkArgument(!Strings.isNullOrEmpty(operator), "Operator must not be null or empty."); + this.operator = operator; + } + + @NonNull + String getOperator() { + return operator; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java index 41a0afbe4..e3edce4e4 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java @@ -101,6 +101,73 @@ protected Template execute() throws FirebaseRemoteConfigException { }; } + /** + * Alternative to {@link #getServerTemplate} where developers can initialize with a pre-cached + * template or config. + */ + public ServerTemplateImpl.Builder serverTemplateBuilder() { + return new ServerTemplateImpl.Builder(this.remoteConfigClient); + } + + /** + * Initializes a template instance and loads the latest template data. + * + * @param defaultConfig Default parameter values to use if a getter references a parameter not + * found in the template. + * @return A {@link Template} instance with the latest template data. + */ + public ServerTemplate getServerTemplate(KeysAndValues defaultConfig) + throws FirebaseRemoteConfigException { + return getServerTemplateOp(defaultConfig).call(); + } + + /** + * Initializes a template instance without any defaults and loads the latest template data. + * + * @return A {@link Template} instance with the latest template data. + */ + public ServerTemplate getServerTemplate() throws FirebaseRemoteConfigException { + return getServerTemplate(null); + } + + /** + * Initializes a template instance and asynchronously loads the latest template data. + * + * @param defaultConfig Default parameter values to use if a getter references a parameter not + * found in the template. + * @return A {@link Template} instance with the latest template data. + */ + public ApiFuture getServerTemplateAsync(KeysAndValues defaultConfig) { + return getServerTemplateOp(defaultConfig).callAsync(app); + } + + /** + * Initializes a template instance without any defaults and asynchronously loads the latest + * template data. + * + * @return A {@link Template} instance with the latest template data. + */ + public ApiFuture getServerTemplateAsync() { + return getServerTemplateAsync(null); + } + + private CallableOperation getServerTemplateOp( + KeysAndValues defaultConfig) { + return new CallableOperation() { + @Override + protected ServerTemplate execute() throws FirebaseRemoteConfigException { + String serverTemplateData = remoteConfigClient.getServerTemplate(); + ServerTemplate template = + serverTemplateBuilder() + .defaultConfig(defaultConfig) + .cachedTemplate(serverTemplateData) + .build(); + + return template; + } + }; + } + /** * Gets the requested version of the of the Remote Config template. * @@ -413,3 +480,4 @@ public void destroy() { } } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java index 9fdb596d6..2143d07d1 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java @@ -40,4 +40,7 @@ Template publishTemplate(Template template, boolean validateOnly, ListVersionsResponse listVersions( ListVersionsOptions options) throws FirebaseRemoteConfigException; + + String getServerTemplate() throws FirebaseRemoteConfigException; } + diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index 7425673fb..d84abae84 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -38,6 +38,7 @@ import com.google.firebase.internal.NonNull; import com.google.firebase.internal.SdkUtils; import com.google.firebase.remoteconfig.internal.RemoteConfigServiceErrorResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse; import com.google.firebase.remoteconfig.internal.TemplateResponse; import java.io.IOException; @@ -51,6 +52,9 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient private static final String REMOTE_CONFIG_URL = "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/remoteConfig"; + private static final String SERVER_REMOTE_CONFIG_URL = + "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/namespaces/firebase-server/serverRemoteConfig"; + private static final Map COMMON_HEADERS = ImmutableMap.of( "X-Firebase-Client", "fire-admin-java/" + SdkUtils.getVersion(), @@ -62,6 +66,7 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient ); private final String remoteConfigUrl; + private final String serverRemoteConfigUrl; private final HttpRequestFactory requestFactory; private final JsonFactory jsonFactory; private final ErrorHandlingHttpClient httpClient; @@ -69,6 +74,7 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient private FirebaseRemoteConfigClientImpl(Builder builder) { checkArgument(!Strings.isNullOrEmpty(builder.projectId)); this.remoteConfigUrl = String.format(REMOTE_CONFIG_URL, builder.projectId); + this.serverRemoteConfigUrl = String.format(SERVER_REMOTE_CONFIG_URL, builder.projectId); this.requestFactory = checkNotNull(builder.requestFactory); this.jsonFactory = checkNotNull(builder.jsonFactory); HttpResponseInterceptor responseInterceptor = builder.responseInterceptor; @@ -82,6 +88,11 @@ String getRemoteConfigUrl() { return remoteConfigUrl; } + @VisibleForTesting + String getServerRemoteConfigUrl() { + return serverRemoteConfigUrl; + } + @VisibleForTesting HttpRequestFactory getRequestFactory() { return requestFactory; @@ -102,6 +113,18 @@ public Template getTemplate() throws FirebaseRemoteConfigException { return template.setETag(getETag(response)); } + @Override + public String getServerTemplate() throws FirebaseRemoteConfigException { + HttpRequestInfo request = + HttpRequestInfo.buildGetRequest(serverRemoteConfigUrl).addAllHeaders(COMMON_HEADERS); + IncomingHttpResponse response = httpClient.send(request); + ServerTemplateResponse templateResponse = httpClient.parse(response, + ServerTemplateResponse.class); + ServerTemplateData serverTemplateData = new ServerTemplateData(templateResponse); + serverTemplateData.setETag(getETag(response)); + return serverTemplateData.toJSON(); + } + @Override public Template getTemplateAtVersion( @NonNull String versionNumber) throws FirebaseRemoteConfigException { @@ -267,3 +290,4 @@ private RemoteConfigServiceErrorResponse safeParse(String response) { } } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java new file mode 100644 index 000000000..47b159bf7 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java @@ -0,0 +1,135 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents data stored in context passed to server-side Remote Config. + */ +public class KeysAndValues { + final ImmutableMap keysAndValues; + + private KeysAndValues(@NonNull Builder builder) { + keysAndValues = ImmutableMap.builder().putAll(builder.keysAndValues).build(); + } + + /** + * Checks whether a key is present in the context. + * + * @param key The key for data stored in context. + * @return Boolean representing whether the key passed is present in context. + */ + public boolean containsKey(String key) { + return keysAndValues.containsKey(key); + } + + /** + * Gets the value of the data stored in context. + * + * @param key The key for data stored in context. + * @return Value assigned to the key in context. + */ + public String get(String key) { + return keysAndValues.get(key); + } + + /** + * Builder class for KeysAndValues using which values will be assigned to + * private variables. + */ + public static class Builder { + // Holds the converted pairs of custom keys and values. + private final Map keysAndValues = new HashMap<>(); + + /** + * Adds a context data with string value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, @NonNull String value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + checkArgument(!Strings.isNullOrEmpty(value), "Context key must not be null or empty."); + keysAndValues.put(key, value); + return this; + } + + /** + * Adds a context data with boolean value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, boolean value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Boolean.toString(value)); + return this; + } + + /** + * Adds a context data with double value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, double value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Double.toString(value)); + return this; + } + + /** + * Adds a context data with long value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, long value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Long.toString(value)); + return this; + } + + /** + * Creates an instance of KeysAndValues with the values assigned through + * builder. + * + * @return instance of KeysAndValues + */ + @NonNull + public KeysAndValues build() { + return new KeysAndValues(this); + } + } +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java new file mode 100644 index 000000000..abd5711c6 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java @@ -0,0 +1,46 @@ +/* +* Copyright 2025 Google LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.google.firebase.remoteconfig; + +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.MicroPercentRangeResponse; + +class MicroPercentRange { + private final int microPercentLowerBound; + private final int microPercentUpperBound; + + public MicroPercentRange(@Nullable Integer microPercentLowerBound, + @Nullable Integer microPercentUpperBound) { + this.microPercentLowerBound = microPercentLowerBound != null ? microPercentLowerBound : 0; + this.microPercentUpperBound = microPercentUpperBound != null ? microPercentUpperBound : 0; + } + + int getMicroPercentLowerBound() { + return microPercentLowerBound; + } + + int getMicroPercentUpperBound() { + return microPercentUpperBound; + } + + MicroPercentRangeResponse toMicroPercentRangeResponse() { + MicroPercentRangeResponse microPercentRangeResponse = new MicroPercentRangeResponse(); + microPercentRangeResponse.setMicroPercentLowerBound(this.microPercentLowerBound); + microPercentRangeResponse.setMicroPercentUpperBound(this.microPercentUpperBound); + return microPercentRangeResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java new file mode 100644 index 000000000..be3f5fd3e --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -0,0 +1,140 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; + +class OneOfCondition { + private OrCondition orCondition; + private AndCondition andCondition; + private PercentCondition percent; + private CustomSignalCondition customSignal; + private String trueValue; + private String falseValue; + + OneOfCondition(OneOfConditionResponse oneOfconditionResponse) { + if (oneOfconditionResponse.getOrCondition() != null) { + this.orCondition = new OrCondition(oneOfconditionResponse.getOrCondition()); + } + if (oneOfconditionResponse.getAndCondition() != null) { + this.andCondition = new AndCondition(oneOfconditionResponse.getAndCondition()); + } + if (oneOfconditionResponse.getPercentCondition() != null) { + this.percent = new PercentCondition(oneOfconditionResponse.getPercentCondition()); + } + if (oneOfconditionResponse.getCustomSignalCondition() != null) { + this.customSignal = + new CustomSignalCondition(oneOfconditionResponse.getCustomSignalCondition()); + } + } + + @VisibleForTesting + OneOfCondition() { + this.orCondition = null; + this.andCondition = null; + this.percent = null; + this.trueValue = null; + this.falseValue = null; + } + + @Nullable + OrCondition getOrCondition() { + return orCondition; + } + + @Nullable + AndCondition getAndCondition() { + return andCondition; + } + + @Nullable + String isTrue() { + return trueValue; + } + + @Nullable + String isFalse() { + return falseValue; + } + + @Nullable + PercentCondition getPercent() { + return percent; + } + + @Nullable + CustomSignalCondition getCustomSignal() { + return customSignal; + } + + OneOfCondition setOrCondition(@NonNull OrCondition orCondition) { + checkNotNull(orCondition, "`Or` condition cannot be set to null."); + this.orCondition = orCondition; + return this; + } + + OneOfCondition setAndCondition(@NonNull AndCondition andCondition) { + checkNotNull(andCondition, "`And` condition cannot be set to null."); + this.andCondition = andCondition; + return this; + } + + OneOfCondition setPercent(@NonNull PercentCondition percent) { + checkNotNull(percent, "`Percent` condition cannot be set to null."); + this.percent = percent; + return this; + } + + OneOfCondition setCustomSignal(@NonNull CustomSignalCondition customSignal) { + checkNotNull(customSignal, "`Custom signal` condition cannot be set to null."); + this.customSignal = customSignal; + return this; + } + + OneOfCondition setTrue() { + this.trueValue = "true"; + return this; + } + + OneOfCondition setFalse() { + this.falseValue = "false"; + return this; + } + + OneOfConditionResponse toOneOfConditionResponse() { + OneOfConditionResponse oneOfConditionResponse = new OneOfConditionResponse(); + if (this.andCondition != null) { + oneOfConditionResponse.setAndCondition(this.andCondition.toAndConditionResponse()); + } + if (this.orCondition != null) { + oneOfConditionResponse.setOrCondition(this.orCondition.toOrConditionResponse()); + } + if (this.customSignal != null) { + oneOfConditionResponse.setCustomSignalCondition(this.customSignal.toCustomConditonResponse()); + } + if (this.percent != null) { + oneOfConditionResponse.setPercentCondition(this.percent.toPercentConditionResponse()); + } + return oneOfConditionResponse; + } +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java new file mode 100644 index 000000000..36a1c682a --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java @@ -0,0 +1,58 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OrConditionResponse; +import java.util.List; +import java.util.stream.Collectors; + +final class OrCondition { + private final ImmutableList conditions; + + public OrCondition(@NonNull List conditions) { + checkNotNull(conditions, "List of conditions for OR operation must not be null."); + checkArgument(!conditions.isEmpty(), "List of conditions for OR operation must not be empty."); + this.conditions = ImmutableList.copyOf(conditions); + } + + OrCondition(OrConditionResponse orConditionResponse) { + List conditionList = orConditionResponse.getConditions(); + checkNotNull(conditionList, "List of conditions for AND operation cannot be null."); + checkArgument(!conditionList.isEmpty(), "List of conditions for AND operation cannot be empty"); + this.conditions = conditionList.stream() + .map(OneOfCondition::new) + .collect(ImmutableList.toImmutableList()); + } + + @NonNull + ImmutableList getConditions() { + return conditions; + } + + OrConditionResponse toOrConditionResponse() { + return new OrConditionResponse() + .setConditions(this.conditions.stream() + .map(OneOfCondition::toOneOfConditionResponse) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ParameterValue.java b/src/main/java/com/google/firebase/remoteconfig/ParameterValue.java index 90bf0e5df..1d48bbb03 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ParameterValue.java +++ b/src/main/java/com/google/firebase/remoteconfig/ParameterValue.java @@ -17,15 +17,21 @@ package com.google.firebase.remoteconfig; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.stream.Collectors.toList; import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ExperimentValueResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ExperimentVariantValueResponse; import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse; - +import com.google.firebase.remoteconfig.internal.TemplateResponse.PersonalizationValueResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.RolloutValueResponse; +import java.util.List; import java.util.Objects; -/** - * Represents a Remote Config parameter value that can be used in a {@link Template}. - */ +/** Represents a Remote Config parameter value + * that can be used in a + * {@link Template}. */ public abstract class ParameterValue { /** @@ -47,20 +53,87 @@ public static InAppDefault inAppDefault() { return new InAppDefault(); } + /** + * Creates a new {@link ParameterValue.RolloutValue} instance. + * + * @param rolloutId The rollout ID. + * @param value The value of the rollout. + * @param percent The percentage of the rollout. + * @return A {@link ParameterValue.RolloutValue} instance. + */ + public static RolloutValue ofRollout(String rolloutId, String value, double percent) { + return new RolloutValue(rolloutId, value, percent); + } + + /** + * Creates a new {@link ParameterValue.PersonalizationValue} instance. + * + * @param personalizationId The personalization ID. + * @return A {@link ParameterValue.PersonalizationValue} instance. + */ + public static PersonalizationValue ofPersonalization(String personalizationId) { + return new PersonalizationValue(personalizationId); + } + + /** + * Creates a new {@link ParameterValue.ExperimentValue} instance. + * + * @param experimentId The experiment ID. + * @param variantValues The list of experiment variant values. + * @param exposurePercent The exposure percentage of the experiment. + * @return A {@link ParameterValue.ExperimentValue} instance. + */ + public static ExperimentValue ofExperiment( + String experimentId, List variantValues, double exposurePercent) { + return new ExperimentValue(experimentId, variantValues, exposurePercent); + } + abstract ParameterValueResponse toParameterValueResponse(); static ParameterValue fromParameterValueResponse( - @NonNull ParameterValueResponse parameterValueResponse) { + @NonNull ParameterValueResponse parameterValueResponse) { checkNotNull(parameterValueResponse); if (parameterValueResponse.isUseInAppDefault()) { return ParameterValue.inAppDefault(); } + if (parameterValueResponse.getRolloutValue() != null) { + RolloutValueResponse rv = parameterValueResponse.getRolloutValue(); + // Protobuf serialization does not set values for fields on the wire when + // they are equal to the default value for the field type. When deserializing, + // can appear as the value not being set. Explicitly handle default value for + // the percent field since 0 is a valid value. + double percent = 0; + if (rv.getPercent() != null) { + percent = rv.getPercent(); + } + return ParameterValue.ofRollout(rv.getRolloutId(), rv.getValue(), percent); + } + if (parameterValueResponse.getPersonalizationValue() != null) { + PersonalizationValueResponse pv = parameterValueResponse.getPersonalizationValue(); + return ParameterValue.ofPersonalization(pv.getPersonalizationId()); + } + if (parameterValueResponse.getExperimentValue() != null) { + ExperimentValueResponse ev = parameterValueResponse.getExperimentValue(); + List variantValues = + ev.getExperimentVariantValues().stream() + .map( + evv -> + new ExperimentVariantValue( + evv.getVariantId(), evv.getValue(), evv.getNoChange())) + .collect(toList()); + // Handle null exposurePercent by defaulting to 0 + double exposurePercent = 0; + if (ev.getExposurePercent() != null) { + exposurePercent = ev.getExposurePercent(); + } + return ParameterValue.ofExperiment( + ev.getExperimentId(), variantValues, exposurePercent); + } return ParameterValue.of(parameterValueResponse.getValue()); } /** - * Represents an explicit Remote Config parameter value with a value that the - * parameter is set to. + * Represents an explicit Remote Config parameter value with a value that the parameter is set to. */ public static final class Explicit extends ParameterValue { @@ -81,8 +154,7 @@ public String getValue() { @Override ParameterValueResponse toParameterValueResponse() { - return new ParameterValueResponse() - .setValue(this.value); + return new ParameterValueResponse().setValue(this.value); } @Override @@ -103,9 +175,7 @@ public int hashCode() { } } - /** - * Represents an in app default parameter value. - */ + /** Represents an in app default parameter value. */ public static final class InAppDefault extends ParameterValue { @Override @@ -124,4 +194,281 @@ public boolean equals(Object o) { return true; } } + + /** Represents a Rollout value. */ + public static final class RolloutValue extends ParameterValue { + private final String rolloutId; + private final String value; + private final double percent; + + private RolloutValue(String rolloutId, String value, double percent) { + this.rolloutId = rolloutId; + this.value = value; + this.percent = percent; + } + + /** + * Gets the ID of the Rollout linked to this value. + * + * @return The Rollout ID + */ + public String getRolloutId() { + return rolloutId; + } + + /** + * Gets the value that is being rolled out. + * + * @return The rollout value + */ + public String getValue() { + return value; + } + + /** + * Gets the rollout percentage representing the exposure of rollout value in the target + * audience. + * + * @return Percentage of audience exposed to the rollout + */ + public double getPercent() { + return percent; + } + + @Override + ParameterValueResponse toParameterValueResponse() { + return new ParameterValueResponse() + .setRolloutValue( + new RolloutValueResponse() + .setRolloutId(this.rolloutId) + .setValue(this.value) + .setPercent(this.percent)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RolloutValue that = (RolloutValue) o; + return Double.compare(that.percent, percent) == 0 + && Objects.equals(rolloutId, that.rolloutId) + && Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(rolloutId, value, percent); + } + } + + /** Represents a Personalization value. */ + public static final class PersonalizationValue extends ParameterValue { + private final String personalizationId; + + private PersonalizationValue(String personalizationId) { + this.personalizationId = personalizationId; + } + + /** + * Gets the ID of the Personalization linked to this value. + * + * @return The Personalization ID + */ + public String getPersonalizationId() { + return personalizationId; + } + + @Override + ParameterValueResponse toParameterValueResponse() { + return new ParameterValueResponse() + .setPersonalizationValue( + new PersonalizationValueResponse().setPersonalizationId(this.personalizationId)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PersonalizationValue that = (PersonalizationValue) o; + return Objects.equals(personalizationId, that.personalizationId); + } + + @Override + public int hashCode() { + return Objects.hash(personalizationId); + } + } + + /** Represents a specific variant within an Experiment. */ + public static final class ExperimentVariantValue { + private final String variantId; + private final String value; + private final Boolean noChange; + + ExperimentVariantValue(String variantId, String value, Boolean noChange) { + this.variantId = variantId; + this.value = value; + this.noChange = noChange; + } + + /** + * Creates a new {@link ExperimentVariantValue} instance. + * + * @param variantId The variant ID. + * @param value The value of the variant. + * @return A {@link ExperimentVariantValue} instance. + */ + public static ExperimentVariantValue of(String variantId, String value) { + return new ExperimentVariantValue(variantId, value, null); + } + + /** + * Creates a new {@link ExperimentVariantValue} instance. + * + * @param variantId The variant ID. + * @return A {@link ExperimentVariantValue} instance. + */ + public static ExperimentVariantValue ofNoChange(String variantId) { + return new ExperimentVariantValue(variantId, null, true); + } + + /** + * Gets the ID of the experiment variant. + * + * @return The variant ID + */ + public String getVariantId() { + return variantId; + } + + /** + * Gets the value of the experiment variant. + * + * @return The variant value + */ + @Nullable + public String getValue() { + return value; + } + + @Nullable + Boolean getNoChange() { + return noChange; + } + + /** + * Returns whether the experiment variant is a no-change variant. + * + * @return true if the experiment variant is a no-change variant, and false otherwise. + */ + public boolean isNoChange() { + return Boolean.TRUE.equals(noChange); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExperimentVariantValue that = (ExperimentVariantValue) o; + return noChange == that.noChange + && Objects.equals(variantId, that.variantId) + && Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(variantId, value, noChange); + } + } + + /** Represents an Experiment value. */ + public static final class ExperimentValue extends ParameterValue { + private final String experimentId; + private final List variantValues; + private final double exposurePercent; + + private ExperimentValue( + String experimentId, List variantValues, double exposurePercent) { + this.experimentId = experimentId; + this.variantValues = variantValues; + this.exposurePercent = exposurePercent; + } + + /** + * Gets the ID of the experiment linked to this value. + * + * @return The Experiment ID + */ + public String getExperimentId() { + return experimentId; + } + + /** + * Gets the exposure percentage of the experiment linked to this value. + * + * @return Exposure percentage of the experiment linked to this value. + */ + public double getExposurePercent() { + return exposurePercent; + } + + /** + * Gets a collection of variant values served by the experiment. + * + * @return List of {@link ExperimentVariantValue} + */ + public List getExperimentVariantValues() { + return variantValues; + } + + @Override + ParameterValueResponse toParameterValueResponse() { + List variantValueResponses = + variantValues.stream() + .map( + variantValue -> + new ExperimentVariantValueResponse() + .setVariantId(variantValue.getVariantId()) + .setValue(variantValue.getValue()) + .setNoChange(variantValue.getNoChange())) + .collect(toList()); + return new ParameterValueResponse() + .setExperimentValue( + new ExperimentValueResponse() + .setExperimentId(this.experimentId) + .setExperimentVariantValues(variantValueResponses) + .setExposurePercent(this.exposurePercent)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExperimentValue that = (ExperimentValue) o; + return Objects.equals(experimentId, that.experimentId) + && Objects.equals(variantValues, that.variantValues) + && Double.compare(that.exposurePercent, exposurePercent) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(experimentId, variantValues, exposurePercent); + } + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java new file mode 100644 index 000000000..c5763200b --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java @@ -0,0 +1,163 @@ +/* +* Copyright 2025 Google LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.PercentConditionResponse; + +/** Represents a condition that compares the instance pseudo-random percentile to a given limit. */ +public final class PercentCondition { + private int microPercent; + private MicroPercentRange microPercentRange; + private final PercentConditionOperator percentConditionOperator; + private final String seed; + + /** + * Create a percent condition for operator BETWEEN. + * + * @param microPercent The limit of percentiles to target in micro-percents when using the + * LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 and 100000000]. + * @param percentConditionOperator The choice of percent operator to determine how to compare + * targets to percent(s). + * @param seed The seed used when evaluating the hash function to map an instance to a value in + * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII + * characters [-_.0-9a-zA-Z].The string is case-sensitive. + */ + PercentCondition( + @Nullable Integer microPercent, + @NonNull PercentConditionOperator percentConditionOperator, + @NonNull String seed) { + checkNotNull(percentConditionOperator, "Percentage operator must not be null."); + checkArgument(!Strings.isNullOrEmpty(seed), "Seed must not be null or empty."); + this.microPercent = microPercent != null ? microPercent : 0; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } + + /** + * Create a percent condition for operators GREATER_THAN and LESS_OR_EQUAL. + * + * @param microPercentRange The micro-percent interval to be used with the BETWEEN operator. + * @param percentConditionOperator The choice of percent operator to determine how to compare + * targets to percent(s). + * @param seed The seed used when evaluating the hash function to map an instance to a value in + * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII + * characters [-_.0-9a-zA-Z].The string is case-sensitive. + */ + PercentCondition( + @NonNull MicroPercentRange microPercentRange, + @NonNull PercentConditionOperator percentConditionOperator, + String seed) { + checkNotNull(microPercentRange, "Percent range must not be null."); + checkNotNull(percentConditionOperator, "Percentage operator must not be null."); + this.microPercentRange = microPercentRange; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } + + /** + * Creates a new {@link PercentCondition} from API response. + * + * @param percentCondition the conditions obtained from server call. + */ + PercentCondition(PercentConditionResponse percentCondition) { + checkArgument( + !Strings.isNullOrEmpty(percentCondition.getSeed()), "Seed must not be empty or null"); + this.microPercent = percentCondition.getMicroPercent(); + this.seed = percentCondition.getSeed(); + switch (percentCondition.getPercentOperator()) { + case "BETWEEN": + this.percentConditionOperator = PercentConditionOperator.BETWEEN; + break; + case "GREATER_THAN": + this.percentConditionOperator = PercentConditionOperator.GREATER_THAN; + break; + case "LESS_OR_EQUAL": + this.percentConditionOperator = PercentConditionOperator.LESS_OR_EQUAL; + break; + default: + this.percentConditionOperator = PercentConditionOperator.UNSPECIFIED; + } + checkArgument( + this.percentConditionOperator != PercentConditionOperator.UNSPECIFIED, + "Percentage operator is invalid"); + if (percentCondition.getMicroPercentRange() != null) { + this.microPercentRange = + new MicroPercentRange( + percentCondition.getMicroPercentRange().getMicroPercentLowerBound(), + percentCondition.getMicroPercentRange().getMicroPercentUpperBound()); + } + } + + /** + * Gets the limit of percentiles to target in micro-percents when using the LESS_OR_EQUAL and + * GREATER_THAN operators. The value must be in the range [0 and 100000000]. + * + * @return micro percent. + */ + @Nullable + public int getMicroPercent() { + return microPercent; + } + + /** + * Gets micro-percent interval to be used with the BETWEEN operator. + * + * @return micro percent range. + */ + @Nullable + public MicroPercentRange getMicroPercentRange() { + return microPercentRange; + } + + /** + * Gets choice of percent operator to determine how to compare targets to percent(s). + * + * @return operator. + */ + @NonNull + public PercentConditionOperator getPercentConditionOperator() { + return percentConditionOperator; + } + + /** + * The seed used when evaluating the hash function to map an instance to a value in the hash + * space. This is a string which can have 0 - 32 characters and can contain ASCII characters + * [-_.0-9a-zA-Z].The string is case-sensitive. + * + * @return seed. + */ + @NonNull + public String getSeed() { + return seed; + } + + PercentConditionResponse toPercentConditionResponse() { + PercentConditionResponse percentConditionResponse = new PercentConditionResponse(); + percentConditionResponse.setMicroPercent(this.microPercent); + percentConditionResponse.setMicroPercentRange( + this.microPercentRange.toMicroPercentRangeResponse()); + percentConditionResponse.setPercentOperator(this.percentConditionOperator.getOperator()); + percentConditionResponse.setSeed(this.seed); + return percentConditionResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java new file mode 100644 index 000000000..478f13e4e --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java @@ -0,0 +1,55 @@ +/* +* Copyright 2025 Google LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +/** +* Defines supported operators for percent conditions. +*/ +public enum PercentConditionOperator { + BETWEEN("BETWEEN"), + GREATER_THAN("GREATER_THAN"), + LESS_OR_EQUAL("LESS_OR_EQUAL"), + UNSPECIFIED("PERCENT_OPERATOR_UNSPECIFIED"); + + private final String operator; + + /** + * Creates percent condition operator. + * + * @param operator The choice of percent operator to determine how to compare targets to + * percent(s). + */ + PercentConditionOperator(@NonNull String operator) { + checkArgument(!Strings.isNullOrEmpty(operator), "Operator must not be null or empty."); + this.operator = operator; + } + + /** + * Gets percent condition operator. + * + * @return operator. + */ + @NonNull + public String getOperator() { + return operator; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java new file mode 100644 index 000000000..f16aeffc8 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java @@ -0,0 +1,90 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.ServerConditionResponse; + +import java.util.Objects; + +final class ServerCondition { + + private String name; + private OneOfCondition serverCondition; + + ServerCondition(@NonNull String name, @NonNull OneOfCondition condition) { + checkArgument(!Strings.isNullOrEmpty(name), "condition name must not be null or empty"); + this.name = name; + this.serverCondition = condition; + } + + ServerCondition(@NonNull ServerConditionResponse serverConditionResponse) { + checkNotNull(serverConditionResponse); + this.name = serverConditionResponse.getName(); + this.serverCondition = new OneOfCondition(serverConditionResponse.getServerCondition()); + } + + @NonNull + String getName() { + return name; + } + + @NonNull + OneOfCondition getCondition() { + return serverCondition; + } + + ServerCondition setName(@NonNull String name) { + checkArgument(!Strings.isNullOrEmpty(name), "condition name must not be null or empty"); + this.name = name; + return this; + } + + ServerCondition setServerCondition(@NonNull OneOfCondition condition) { + checkNotNull(condition, "condition must not be null or empty"); + this.serverCondition = condition; + return this; + } + + ServerConditionResponse toServerConditionResponse() { + return new ServerConditionResponse().setName(this.name) + .setServerCondition(this.serverCondition.toOneOfConditionResponse()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerCondition condition = (ServerCondition) o; + return Objects.equals(name, condition.name) + && Objects.equals(serverCondition, condition.serverCondition); + } + + @Override + public int hashCode() { + return Objects.hash(name, serverCondition); + } +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java new file mode 100644 index 000000000..8540578b0 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -0,0 +1,102 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +import java.util.Map; + +/** + * Represents the configuration produced by evaluating a server template. + */ +public final class ServerConfig { + private final Map configValues; + + ServerConfig(Map configValues) { + this.configValues = configValues; + } + + /** + * Gets the value for the given key as a string. Convenience method for calling + * serverConfig.getValue(key).asString(). + * + * @param key The name of the parameter. + * @return config value for the given key as string. + */ + @NonNull + public String getString(@NonNull String key) { + return this.getValue(key).asString(); + } + + /** + * Gets the value for the given key as a boolean.Convenience method for calling + * serverConfig.getValue(key).asBoolean(). + * + * @param key The name of the parameter. + * @return config value for the given key as boolean. + */ + @NonNull + public boolean getBoolean(@NonNull String key) { + return this.getValue(key).asBoolean(); + } + + /** + * Gets the value for the given key as long.Convenience method for calling + * serverConfig.getValue(key).asLong(). + * + * @param key The name of the parameter. + * @return config value for the given key as long. + */ + @NonNull + public long getLong(@NonNull String key) { + return this.getValue(key).asLong(); + } + + /** + * Gets the value for the given key as double.Convenience method for calling + * serverConfig.getValue(key).asDouble(). + * + * @param key The name of the parameter. + * @return config value for the given key as double. + */ + @NonNull + public double getDouble(@NonNull String key) { + return this.getValue(key).asDouble(); + } + + /** + * Gets the {@link ValueSource} for the given key. + * + * @param key The name of the parameter. + * @return config value source for the given key. + */ + @NonNull + public ValueSource getValueSource(@NonNull String key) { + return this.getValue(key).getSource(); + } + + private Value getValue(String key) { + checkArgument(!Strings.isNullOrEmpty(key), "Server config key cannot be null or empty."); + if (configValues.containsKey(key)) { + return configValues.get(key); + } + return new Value(ValueSource.STATIC); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java new file mode 100644 index 000000000..bd1a59940 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -0,0 +1,45 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import com.google.api.core.ApiFuture; + +public interface ServerTemplate { + public interface Builder { + + Builder defaultConfig(KeysAndValues config); + + Builder cachedTemplate(String templateJson); + + ServerTemplate build(); + } + /** + * Process the template data with a condition evaluator + * based on the provided context. + */ + ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigException; + /** + * Process the template data without context. + */ + ServerConfig evaluate() throws FirebaseRemoteConfigException; + /** + * Fetches and caches the current active version of the project. + */ + ApiFuture load() throws FirebaseRemoteConfigException; + + String toJson(); +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java new file mode 100644 index 000000000..59d51b51a --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -0,0 +1,215 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.json.JsonFactory; +import com.google.common.base.Strings; +import com.google.firebase.ErrorCode; +import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +final class ServerTemplateData { + + private String etag; + private Map parameters; + private List serverConditions; + private Map parameterGroups; + private Version version; + + + ServerTemplateData(String etag) { + this.parameters = new HashMap<>(); + this.serverConditions = new ArrayList<>(); + this.parameterGroups = new HashMap<>(); + this.etag = etag; + } + + ServerTemplateData() { + this((String) null); + } + + ServerTemplateData(@NonNull ServerTemplateResponse serverTemplateResponse) { + checkNotNull(serverTemplateResponse); + this.parameters = new HashMap<>(); + this.serverConditions = new ArrayList<>(); + this.parameterGroups = new HashMap<>(); + if (serverTemplateResponse.getParameters() != null) { + for (Map.Entry entry : + serverTemplateResponse.getParameters().entrySet()) { + this.parameters.put(entry.getKey(), new Parameter(entry.getValue())); + } + } + if (serverTemplateResponse.getServerConditions() != null) { + for (ServerTemplateResponse.ServerConditionResponse conditionResponse : + serverTemplateResponse.getServerConditions()) { + this.serverConditions.add(new ServerCondition(conditionResponse)); + } + } + if (serverTemplateResponse.getParameterGroups() != null) { + for (Map.Entry entry : + serverTemplateResponse.getParameterGroups().entrySet()) { + this.parameterGroups.put(entry.getKey(), new ParameterGroup(entry.getValue())); + } + } + if (serverTemplateResponse.getVersion() != null) { + this.version = new Version(serverTemplateResponse.getVersion()); + } + this.etag = serverTemplateResponse.getEtag(); + } + + + static ServerTemplateData fromJSON(@NonNull String json) + throws FirebaseRemoteConfigException { + checkArgument(!Strings.isNullOrEmpty(json), "JSON String must not be null or empty."); + // using the default json factory as no rpc calls are made here + JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + try { + ServerTemplateResponse serverTemplateResponse = + jsonFactory.createJsonParser(json).parseAndClose(ServerTemplateResponse.class); + return new ServerTemplateData(serverTemplateResponse); + } catch (IOException e) { + throw new FirebaseRemoteConfigException( + ErrorCode.INVALID_ARGUMENT, "Unable to parse JSON string."); + } + } + + + String getETag() { + return this.etag; + } + + + @NonNull + public Map getParameters() { + return this.parameters; + } + + @NonNull + List getServerConditions() { + return serverConditions; + } + + @NonNull + Map getParameterGroups() { + return parameterGroups; + } + + Version getVersion() { + return version; + } + + ServerTemplateData setParameters(@NonNull Map parameters) { + checkNotNull(parameters, "parameters must not be null."); + this.parameters = parameters; + return this; + } + + + ServerTemplateData setServerConditions(@NonNull List conditions) { + checkNotNull(conditions, "conditions must not be null."); + this.serverConditions = conditions; + return this; + } + + ServerTemplateData setParameterGroups( + @NonNull Map parameterGroups) { + checkNotNull(parameterGroups, "parameter groups must not be null."); + this.parameterGroups = parameterGroups; + return this; + } + + ServerTemplateData setVersion(Version version) { + this.version = version; + return this; + } + + String toJSON() { + JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + try { + return jsonFactory.toString(this.toServerTemplateResponse(true)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + ServerTemplateData setETag(String etag) { + this.etag = etag; + return this; + } + + ServerTemplateResponse toServerTemplateResponse(boolean includeAll) { + Map parameterResponses = new HashMap<>(); + for (Map.Entry entry : this.parameters.entrySet()) { + parameterResponses.put(entry.getKey(), entry.getValue().toParameterResponse()); + } + List serverConditionResponses = + new ArrayList<>(); + for (ServerCondition condition : this.serverConditions) { + serverConditionResponses.add(condition.toServerConditionResponse()); + } + Map parameterGroupResponse = new HashMap<>(); + for (Map.Entry entry : this.parameterGroups.entrySet()) { + parameterGroupResponse.put(entry.getKey(), entry.getValue().toParameterGroupResponse()); + } + TemplateResponse.VersionResponse versionResponse = + (this.version == null) ? null : this.version.toVersionResponse(includeAll); + ServerTemplateResponse serverTemplateResponse = + new ServerTemplateResponse() + .setParameters(parameterResponses) + .setServerConditions(serverConditionResponses) + .setParameterGroups(parameterGroupResponse) + .setVersion(versionResponse); + if (includeAll) { + return serverTemplateResponse.setEtag(this.etag); + } + return serverTemplateResponse; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerTemplateData template = (ServerTemplateData) o; + return Objects.equals(etag, template.etag) + && Objects.equals(parameters, template.parameters) + && Objects.equals(serverConditions, template.serverConditions) + && Objects.equals(parameterGroups, template.parameterGroups) + && Objects.equals(version, template.version); + } + + @Override + public int hashCode() { + return Objects.hash(etag, parameters, serverConditions, parameterGroups, version); + } +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java new file mode 100644 index 000000000..633730bf3 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java @@ -0,0 +1,195 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ServerTemplateImpl implements ServerTemplate { + + private final KeysAndValues defaultConfig; + private FirebaseRemoteConfigClient client; + private AtomicReference cache; + private final AtomicReference cachedTemplate; + private static final Logger logger = LoggerFactory.getLogger(ServerTemplate.class); + + public static class Builder implements ServerTemplate.Builder { + private KeysAndValues defaultConfig; + private String cachedTemplate; + private FirebaseRemoteConfigClient client; + + Builder(FirebaseRemoteConfigClient remoteConfigClient) { + this.client = remoteConfigClient; + } + + @Override + public Builder defaultConfig(KeysAndValues config) { + this.defaultConfig = config; + return this; + } + + @Override + public Builder cachedTemplate(String templateJson) { + this.cachedTemplate = templateJson; + return this; + } + + @Override + public ServerTemplate build() { + return new ServerTemplateImpl(this); + } + } + + private ServerTemplateImpl(Builder builder) { + this.defaultConfig = builder.defaultConfig; + this.cachedTemplate = new AtomicReference<>(builder.cachedTemplate); + this.client = builder.client; + this.cache = new AtomicReference<>(null); + + String initialTemplate = this.cachedTemplate.get(); + try { + this.cache.set(ServerTemplateData.fromJSON(initialTemplate)); + } catch (FirebaseRemoteConfigException e) { + throw new IllegalArgumentException("Unable to parse JSON string.", e); + } + } + + @Override + public ServerConfig evaluate(@Nullable KeysAndValues context) + throws FirebaseRemoteConfigException { + ServerTemplateData cachedData = this.cache.get(); + if (cachedData == null) { + throw new FirebaseRemoteConfigException(ErrorCode.FAILED_PRECONDITION, + "No Remote Config Server template in cache. Call load() before calling evaluate()."); + } + + Map configValues = new HashMap<>(); + ImmutableMap defaultConfigValues = defaultConfig.keysAndValues; + // Initializes configValue objects with default values. + for (String configName : defaultConfigValues.keySet()) { + configValues.put(configName, new Value(ValueSource.DEFAULT, + defaultConfigValues.get(configName))); + } + + ConditionEvaluator conditionEvaluator = new ConditionEvaluator(); + ImmutableMap evaluatedCondition = ImmutableMap.copyOf( + conditionEvaluator.evaluateConditions(cachedData.getServerConditions(), context)); + ImmutableMap parameters = ImmutableMap.copyOf(cachedData.getParameters()); + mergeDerivedConfigValues(evaluatedCondition, parameters, configValues); + + return new ServerConfig(configValues); + } + + @Override + public ServerConfig evaluate() throws FirebaseRemoteConfigException { + return evaluate(null); + } + + @Override + public ApiFuture load() throws FirebaseRemoteConfigException { + String serverTemplate = client.getServerTemplate(); + this.cachedTemplate.set(serverTemplate); + this.cache.set(ServerTemplateData.fromJSON(serverTemplate)); + return ApiFutures.immediateFuture(null); + } + + // Add getters or other methods as needed + public KeysAndValues getDefaultConfig() { + return defaultConfig; + } + + public String getCachedTemplate() { + return cachedTemplate.get(); + } + + @Override + public String toJson() { + ServerTemplateData currentCache = this.cache.get(); + if (currentCache == null) { + return "{}"; + } + return currentCache.toJSON(); + } + + private void mergeDerivedConfigValues(ImmutableMap evaluatedCondition, + ImmutableMap parameters, Map configValues) { + for (String parameterName : parameters.keySet()) { + Parameter parameter = parameters.get(parameterName); + if (parameter == null) { + logger.warn(String.format("Parameter value is not assigned for %s", parameterName)); + continue; + } + + ImmutableMap conditionalValues = ImmutableMap.copyOf( + parameter.getConditionalValues()); + ParameterValue derivedValue = null; + + // Iterates in order over condition list. If there is a value associated + // with a condition, this checks if the condition is true. + for (String conditionName : evaluatedCondition.keySet()) { + boolean conditionEvaluation = evaluatedCondition.get(conditionName); + if (conditionalValues.containsKey(conditionName) && conditionEvaluation) { + derivedValue = conditionalValues.get(conditionName); + break; + } + } + + if (derivedValue != null && derivedValue.toParameterValueResponse().isUseInAppDefault()) { + logger.warn( + String.format("Derived value found for %s but parameter is set to use in app default.", + parameterName)); + continue; + } + + if (derivedValue != null) { + String parameterValue = derivedValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterValue); + configValues.put(parameterName, value); + continue; + } + + ParameterValue defaultValue = parameter.getDefaultValue(); + if (defaultValue == null) { + logger.warn(String.format("Default parameter value for %s is not set.", + parameterName)); + continue; + } + + ParameterValueResponse defaultValueResponse = defaultValue.toParameterValueResponse(); + if (defaultValueResponse != null && defaultValueResponse.isUseInAppDefault()) { + logger.info(String.format("Default value for %s is set to use in app default.", + parameterName)); + continue; + } + + String parameterDefaultValue = defaultValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterDefaultValue); + configValues.put(parameterName, value); + } + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/Template.java b/src/main/java/com/google/firebase/remoteconfig/Template.java index d94cfc89e..5dfe06aaf 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Template.java +++ b/src/main/java/com/google/firebase/remoteconfig/Template.java @@ -60,7 +60,7 @@ public Template(String etag) { this((String) null); } - Template(@NonNull TemplateResponse templateResponse) { + Template(@NonNull TemplateResponse templateResponse) throws FirebaseRemoteConfigException { checkNotNull(templateResponse); this.parameters = new HashMap<>(); this.conditions = new ArrayList<>(); @@ -86,6 +86,7 @@ public Template(String etag) { if (templateResponse.getVersion() != null) { this.version = new Version(templateResponse.getVersion()); } + validateExperimentExposurePercents(this.parameters, this.parameterGroups); this.etag = templateResponse.getEtag(); } @@ -278,4 +279,59 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(etag, parameters, conditions, parameterGroups, version); } + + private void validateExperimentExposurePercents( + Map parameters, + Map parameterGroups) throws FirebaseRemoteConfigException { + Map experimentExposurePercents = new HashMap<>(); + validateParameters(parameters, experimentExposurePercents); + if (parameterGroups != null) { + for (ParameterGroup group : parameterGroups.values()) { + validateParameters(group.getParameters(), experimentExposurePercents); + } + } + } + + private void validateParameters( + Map parameters, + Map experimentExposurePercents) throws FirebaseRemoteConfigException { + if (parameters == null) { + return; + } + for (Map.Entry entry : parameters.entrySet()) { + Parameter parameter = entry.getValue(); + String parameterName = entry.getKey(); + checkExposurePercent(parameter.getDefaultValue(), parameterName, experimentExposurePercents); + if (parameter.getConditionalValues() != null) { + for (ParameterValue value : parameter.getConditionalValues().values()) { + checkExposurePercent(value, parameterName, experimentExposurePercents); + } + } + } + } + + private void checkExposurePercent( + ParameterValue value, + String parameterName, + Map experimentExposurePercents) throws FirebaseRemoteConfigException { + if (value instanceof ParameterValue.ExperimentValue) { + ParameterValue.ExperimentValue experimentValue = (ParameterValue.ExperimentValue) value; + Double exposurePercent = experimentValue.getExposurePercent(); + if (exposurePercent != null) { + // Enforce range [0, 100] + if (exposurePercent < 0 || exposurePercent > 100) { + return; + } + // Enforce consistency for the same experimentId + String experimentId = experimentValue.getExperimentId(); + if (experimentExposurePercents.containsKey(experimentId)) { + if (!Objects.equals(experimentExposurePercents.get(experimentId), exposurePercent)) { + return; + } + } else { + experimentExposurePercents.put(experimentId, exposurePercent); + } + } + } + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java new file mode 100644 index 000000000..7935e8491 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -0,0 +1,135 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wraps a parameter value with metadata and type-safe getters. Type-safe + * getters insulate application logic from remote changes to parameter names and + * types. + */ +class Value { + private static final Logger logger = LoggerFactory.getLogger(Value.class); + private static final boolean DEFAULT_VALUE_FOR_BOOLEAN = false; + private static final String DEFAULT_VALUE_FOR_STRING = ""; + private static final long DEFAULT_VALUE_FOR_LONG = 0; + private static final double DEFAULT_VALUE_FOR_DOUBLE = 0; + private static final ImmutableList BOOLEAN_TRUTHY_VALUES = ImmutableList.of("1", "true", + "t", "yes", "y", "on"); + + private final ValueSource source; + private final String value; + + /** + * Creates a new {@link Value} object. + * + * @param source Indicates the source of a value. + * @param value Indicates a parameter value. + */ + Value(@NonNull ValueSource source, String value) { + checkNotNull(source, "Value source cannot be null."); + this.source = source; + this.value = value; + } + + /** + * Creates a new {@link Value} object with default value. + * + * @param source Indicates the source of a value. + */ + Value(@NonNull ValueSource source) { + this(source, DEFAULT_VALUE_FOR_STRING); + } + + /** + * Gets the value as a string. + * + * @return value as string + */ + @NonNull + String asString() { + return this.value; + } + + /** + * Gets the value as a boolean.The following values (case + * insensitive) are interpreted as true: "1", "true", "t", "yes", "y", "on". + * Other values are interpreted as false. + * + * @return value as boolean + */ + @NonNull + boolean asBoolean() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_BOOLEAN; + } + return BOOLEAN_TRUTHY_VALUES.contains(value.toLowerCase()); + } + + /** + * Gets the value as long. Comparable to calling Number(value) || 0. + * + * @return value as long + */ + @NonNull + long asLong() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_LONG; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + logger.warn("Unable to convert {} to long type.", value); + return DEFAULT_VALUE_FOR_LONG; + } + } + + /** + * Gets the value as double. Comparable to calling Number(value) || 0. + * + * @return value as double + */ + @NonNull + double asDouble() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_DOUBLE; + } + try { + return Double.parseDouble(this.value); + } catch (NumberFormatException e) { + logger.warn("Unable to convert {} to double type.", value); + return DEFAULT_VALUE_FOR_DOUBLE; + } + } + + /** + * Gets the {@link ValueSource} for the given key. + * + * @return source. + */ + @NonNull + ValueSource getSource() { + return source; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ValueSource.java b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java new file mode 100644 index 000000000..c870e8514 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java @@ -0,0 +1,31 @@ + +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +/** + * Indicates the source of a value. + * "static" indicates the value was defined by a static constant. + * "default" indicates the value was defined by default config. + * "remote" indicates the value was defined by config produced by evaluating a template. + */ +public enum ValueSource { + STATIC, + REMOTE, + DEFAULT +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java new file mode 100644 index 000000000..db3785afe --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java @@ -0,0 +1,322 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig.internal; + +import com.google.api.client.util.Key; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterGroupResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; + +import java.util.List; +import java.util.Map; + +/** + * The Data Transfer Object for parsing Remote Config template responses from + * the Remote Config + * service. + */ +public final class ServerTemplateResponse { + @Key("parameters") + private Map parameters; + + @Key("conditions") + private List serverConditions; + + @Key("parameterGroups") + private Map parameterGroups; + + @Key("version") + private VersionResponse version; + + // For local JSON serialization and deserialization purposes only. + // ETag in response type is never set by the HTTP response. + @Key("etag") + private String etag; + + public Map getParameters() { + return parameters; + } + + public List getServerConditions() { + return serverConditions; + } + + public Map getParameterGroups() { + return parameterGroups; + } + + public VersionResponse getVersion() { + return version; + } + + public String getEtag() { + return etag; + } + + public ServerTemplateResponse setParameters(Map parameters) { + this.parameters = parameters; + return this; + } + + public ServerTemplateResponse setServerConditions( + List serverConditions) { + this.serverConditions = serverConditions; + return this; + } + + public ServerTemplateResponse setParameterGroups( + Map parameterGroups) { + this.parameterGroups = parameterGroups; + return this; + } + + public ServerTemplateResponse setVersion(VersionResponse version) { + this.version = version; + return this; + } + + public ServerTemplateResponse setEtag(String etag) { + this.etag = etag; + return this; + } + + /** + * The Data Transfer Object for parsing Remote Config condition responses from + * the Remote Config + * service. + */ + public static final class ServerConditionResponse { + + @Key("name") + private String name; + + @Key("condition") + private OneOfConditionResponse condition; + + public String getName() { + return name; + } + + public OneOfConditionResponse getServerCondition() { + return condition; + } + + public ServerConditionResponse setName(String name) { + this.name = name; + return this; + } + + public ServerConditionResponse setServerCondition(OneOfConditionResponse condition) { + this.condition = condition; + return this; + } + } + + public static final class OneOfConditionResponse { + @Key("orCondition") + private OrConditionResponse orCondition; + + @Key("andCondition") + private AndConditionResponse andCondition; + + @Key("customSignal") + private CustomSignalConditionResponse customSignalCondition; + + @Key("percent") + private PercentConditionResponse percentCondition; + + public OrConditionResponse getOrCondition() { + return orCondition; + } + + public AndConditionResponse getAndCondition() { + return andCondition; + } + + public PercentConditionResponse getPercentCondition() { + return percentCondition; + } + + public CustomSignalConditionResponse getCustomSignalCondition() { + return customSignalCondition; + } + + public OneOfConditionResponse setOrCondition(OrConditionResponse orCondition) { + this.orCondition = orCondition; + return this; + } + + public OneOfConditionResponse setAndCondition(AndConditionResponse andCondition) { + this.andCondition = andCondition; + return this; + } + + public OneOfConditionResponse setCustomSignalCondition( + CustomSignalConditionResponse customSignalCondition) { + this.customSignalCondition = customSignalCondition; + return this; + } + + public OneOfConditionResponse setPercentCondition(PercentConditionResponse percentCondition) { + this.percentCondition = percentCondition; + return this; + } + } + + public static final class OrConditionResponse { + @Key("conditions") + private List conditions; + + public List getConditions() { + return conditions; + } + + public OrConditionResponse setConditions(List conditions) { + this.conditions = conditions; + return this; + } + } + + public static final class AndConditionResponse { + @Key("conditions") + private List conditions; + + public List getConditions() { + return conditions; + } + + public AndConditionResponse setConditions(List conditions) { + this.conditions = conditions; + return this; + } + } + + public static final class CustomSignalConditionResponse { + @Key("customSignalOperator") + private String operator; + + @Key("customSignalKey") + private String key; + + @Key("targetCustomSignalValues") + private List targetValues; + + public String getOperator() { + return operator; + } + + public String getKey() { + return key; + } + + public List getTargetValues() { + return targetValues; + } + + public CustomSignalConditionResponse setOperator(String operator) { + this.operator = operator; + return this; + } + + public CustomSignalConditionResponse setKey(String key) { + this.key = key; + return this; + } + + public CustomSignalConditionResponse setTargetValues(List targetValues) { + this.targetValues = targetValues; + return this; + } + } + + public static final class PercentConditionResponse { + @Key("microPercent") + private int microPercent; + + @Key("microPercentRange") + private MicroPercentRangeResponse microPercentRange; + + @Key("percentOperator") + private String percentOperator; + + @Key("seed") + private String seed; + + public int getMicroPercent() { + return microPercent; + } + + public MicroPercentRangeResponse getMicroPercentRange() { + return microPercentRange; + } + + public String getPercentOperator() { + return percentOperator; + } + + public String getSeed() { + return seed; + } + + public PercentConditionResponse setMicroPercent(int microPercent) { + this.microPercent = microPercent; + return this; + } + + public PercentConditionResponse setMicroPercentRange( + MicroPercentRangeResponse microPercentRange) { + this.microPercentRange = microPercentRange; + return this; + } + + public PercentConditionResponse setPercentOperator(String percentOperator) { + this.percentOperator = percentOperator; + return this; + } + + public PercentConditionResponse setSeed(String seed) { + this.seed = seed; + return this; + } + } + + public static final class MicroPercentRangeResponse { + @Key("microPercentLowerBound") + private int microPercentLowerBound; + + @Key("microPercentUpperBound") + private int microPercentUpperBound; + + public int getMicroPercentLowerBound() { + return microPercentLowerBound; + } + + public int getMicroPercentUpperBound() { + return microPercentUpperBound; + } + + public MicroPercentRangeResponse setMicroPercentLowerBound(int microPercentLowerBound) { + this.microPercentLowerBound = microPercentLowerBound; + return this; + } + + public MicroPercentRangeResponse setMicroPercentUpperBound(int microPercentUpperBound) { + this.microPercentUpperBound = microPercentUpperBound; + return this; + } + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java index 57a066bf2..32f5bcef9 100644 --- a/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java +++ b/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java @@ -161,6 +161,15 @@ public static final class ParameterValueResponse { @Key("useInAppDefault") private Boolean useInAppDefault; + @Key("rolloutValue") + private RolloutValueResponse rolloutValue; + + @Key("personalizationValue") + private PersonalizationValueResponse personalizationValue; + + @Key("experimentValue") + private ExperimentValueResponse experimentValue; + public String getValue() { return value; } @@ -169,6 +178,18 @@ public boolean isUseInAppDefault() { return Boolean.TRUE.equals(this.useInAppDefault); } + public RolloutValueResponse getRolloutValue() { + return rolloutValue; + } + + public PersonalizationValueResponse getPersonalizationValue() { + return personalizationValue; + } + + public ExperimentValueResponse getExperimentValue() { + return experimentValue; + } + public ParameterValueResponse setValue(String value) { this.value = value; return this; @@ -178,6 +199,167 @@ public ParameterValueResponse setUseInAppDefault(boolean useInAppDefault) { this.useInAppDefault = useInAppDefault; return this; } + + public ParameterValueResponse setRolloutValue(RolloutValueResponse rolloutValue) { + this.rolloutValue = rolloutValue; + return this; + } + + public ParameterValueResponse setPersonalizationValue( + PersonalizationValueResponse personalizationValue) { + this.personalizationValue = personalizationValue; + return this; + } + + public ParameterValueResponse setExperimentValue(ExperimentValueResponse experimentValue) { + this.experimentValue = experimentValue; + return this; + } + } + + /** + * The Data Transfer Object for parsing Remote Config Rollout value responses from the + * Remote Config service. + **/ + public static final class RolloutValueResponse { + @Key("rolloutId") + private String rolloutId; + + @Key("value") + private String value; + + @Key("percent") + private Double percent; + + public String getRolloutId() { + return rolloutId; + } + + public String getValue() { + return value; + } + + public Double getPercent() { + return percent; + } + + public RolloutValueResponse setRolloutId(String rolloutId) { + this.rolloutId = rolloutId; + return this; + } + + public RolloutValueResponse setValue(String value) { + this.value = value; + return this; + } + + public RolloutValueResponse setPercent(Double percent) { + this.percent = percent; + return this; + } + } + + /** + * The Data Transfer Object for parsing Remote Config Personalization value responses from the + * Remote Config service. + **/ + public static final class PersonalizationValueResponse { + @Key("personalizationId") + private String personalizationId; + + public String getPersonalizationId() { + return personalizationId; + } + + public PersonalizationValueResponse setPersonalizationId(String personalizationId) { + this.personalizationId = personalizationId; + return this; + } + } + + /** + * The Data Transfer Object for parsing Remote Config Experiment value responses from the + * Remote Config service. + **/ + public static final class ExperimentValueResponse { + @Key("experimentId") + private String experimentId; + + @Key("variantValue") + private List experimentVariantValues; + + @Key("exposurePercent") + private Double exposurePercent; + + public String getExperimentId() { + return experimentId; + } + + public List getExperimentVariantValues() { + return experimentVariantValues; + } + + public Double getExposurePercent() { + return exposurePercent; + } + + public ExperimentValueResponse setExperimentId(String experimentId) { + this.experimentId = experimentId; + return this; + } + + public ExperimentValueResponse setExperimentVariantValues( + List experimentVariantValues) { + this.experimentVariantValues = experimentVariantValues; + return this; + } + + public ExperimentValueResponse setExposurePercent(Double exposurePercent) { + this.exposurePercent = exposurePercent; + return this; + } + } + + /** + * The Data Transfer Object for parsing Remote Config Experiment variant value responses from the + * Remote Config service. + **/ + public static final class ExperimentVariantValueResponse { + @Key("variantId") + private String variantId; + + @Key("value") + private String value; + + @Key("noChange") + private Boolean noChange; + + public String getVariantId() { + return variantId; + } + + public String getValue() { + return value; + } + + public Boolean getNoChange() { + return noChange; + } + + public ExperimentVariantValueResponse setVariantId(String variantId) { + this.variantId = variantId; + return this; + } + + public ExperimentVariantValueResponse setValue(String value) { + this.value = value; + return this; + } + + public ExperimentVariantValueResponse setNoChange(Boolean noChange) { + this.noChange = noChange; + return this; + } } /** diff --git a/src/main/resources/admin_sdk.properties b/src/main/resources/admin_sdk.properties index 800db26c3..2074da12c 100644 --- a/src/main/resources/admin_sdk.properties +++ b/src/main/resources/admin_sdk.properties @@ -1,5 +1,5 @@ # -# Copyright 2017 Google Inc. +# Copyright 2017 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/CodeCoverageReporter.java b/src/test/java/com/google/firebase/CodeCoverageReporter.java index c5522a5be..8a8a9cf6c 100644 --- a/src/test/java/com/google/firebase/CodeCoverageReporter.java +++ b/src/test/java/com/google/firebase/CodeCoverageReporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java index a35aefe15..d5ab18343 100644 --- a/src/test/java/com/google/firebase/FirebaseAppTest.java +++ b/src/test/java/com/google/firebase/FirebaseAppTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/FirebaseExceptionTest.java b/src/test/java/com/google/firebase/FirebaseExceptionTest.java index efe4e39fa..7c1b24df8 100644 --- a/src/test/java/com/google/firebase/FirebaseExceptionTest.java +++ b/src/test/java/com/google/firebase/FirebaseExceptionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/FirebaseOptionsTest.java b/src/test/java/com/google/firebase/FirebaseOptionsTest.java index c74215beb..1219eabaf 100644 --- a/src/test/java/com/google/firebase/FirebaseOptionsTest.java +++ b/src/test/java/com/google/firebase/FirebaseOptionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,12 +24,12 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.firestore.FirestoreOptions; +import com.google.firebase.internal.ApacheHttp2Transport; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestUtils; import java.io.IOException; @@ -38,7 +38,7 @@ import org.junit.Test; -/** +/** * Tests for {@link FirebaseOptions}. */ public class FirebaseOptionsTest { @@ -74,7 +74,7 @@ protected ThreadFactory getThreadFactory() { @Test public void createOptionsWithAllValuesSet() throws IOException { GsonFactory jsonFactory = new GsonFactory(); - NetHttpTransport httpTransport = new NetHttpTransport(); + ApacheHttp2Transport httpTransport = new ApacheHttp2Transport(); FirestoreOptions firestoreOptions = FirestoreOptions.newBuilder().build(); FirebaseOptions firebaseOptions = FirebaseOptions.builder() @@ -87,6 +87,7 @@ public void createOptionsWithAllValuesSet() throws IOException { .setThreadManager(MOCK_THREAD_MANAGER) .setConnectTimeout(30000) .setReadTimeout(60000) + .setWriteTimeout(90000) .setFirestoreOptions(firestoreOptions) .build(); assertEquals(FIREBASE_DB_URL, firebaseOptions.getDatabaseUrl()); @@ -97,6 +98,7 @@ public void createOptionsWithAllValuesSet() throws IOException { assertSame(MOCK_THREAD_MANAGER, firebaseOptions.getThreadManager()); assertEquals(30000, firebaseOptions.getConnectTimeout()); assertEquals(60000, firebaseOptions.getReadTimeout()); + assertEquals(90000, firebaseOptions.getWriteTimeout()); assertSame(firestoreOptions, firebaseOptions.getFirestoreOptions()); GoogleCredentials credentials = firebaseOptions.getCredentials(); @@ -209,6 +211,14 @@ public void createOptionsWithInvalidReadTimeout() { .build(); } + @Test(expected = IllegalArgumentException.class) + public void createOptionsWithInvalidWriteTimeout() { + FirebaseOptions.builder() + .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream())) + .setWriteTimeout(-1) + .build(); + } + @Test public void testNotEquals() throws IOException { GoogleCredentials credentials = GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()); diff --git a/src/test/java/com/google/firebase/IncomingHttpResponseTest.java b/src/test/java/com/google/firebase/IncomingHttpResponseTest.java index 4da63d12b..2eca41f5a 100644 --- a/src/test/java/com/google/firebase/IncomingHttpResponseTest.java +++ b/src/test/java/com/google/firebase/IncomingHttpResponseTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java b/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java index ffd17f057..02dc12cfd 100644 --- a/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java +++ b/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/TestOnlyImplFirebaseTrampolines.java b/src/test/java/com/google/firebase/TestOnlyImplFirebaseTrampolines.java index cd29dbb80..bb386480b 100644 --- a/src/test/java/com/google/firebase/TestOnlyImplFirebaseTrampolines.java +++ b/src/test/java/com/google/firebase/TestOnlyImplFirebaseTrampolines.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/ThreadManagerTest.java b/src/test/java/com/google/firebase/ThreadManagerTest.java index 22401de30..4e1f9983f 100644 --- a/src/test/java/com/google/firebase/ThreadManagerTest.java +++ b/src/test/java/com/google/firebase/ThreadManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/ActionCodeSettingsTest.java b/src/test/java/com/google/firebase/auth/ActionCodeSettingsTest.java index 501aa53ba..c15e25596 100644 --- a/src/test/java/com/google/firebase/auth/ActionCodeSettingsTest.java +++ b/src/test/java/com/google/firebase/auth/ActionCodeSettingsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -68,6 +68,7 @@ public void testAllSettings() { .setUrl("https://example.com") .setHandleCodeInApp(true) .setDynamicLinkDomain("myapp.page.link") + .setLinkDomain("myapp.custom.link") .setIosBundleId("com.example.ios") .setAndroidPackageName("com.example.android") .setAndroidMinimumVersion("6.0") @@ -77,6 +78,7 @@ public void testAllSettings() { .put("continueUrl", "https://example.com") .put("canHandleCodeInApp", true) .put("dynamicLinkDomain", "myapp.page.link") + .put("linkDomain", "myapp.custom.link") .put("iOSBundleId", "com.example.ios") .put("androidPackageName", "com.example.android") .put("androidMinimumVersion", "6.0") diff --git a/src/test/java/com/google/firebase/auth/EmulatorFirebaseTokenVerifierImplTest.java b/src/test/java/com/google/firebase/auth/EmulatorFirebaseTokenVerifierImplTest.java index 55ca7bf6c..be6ada669 100644 --- a/src/test/java/com/google/firebase/auth/EmulatorFirebaseTokenVerifierImplTest.java +++ b/src/test/java/com/google/firebase/auth/EmulatorFirebaseTokenVerifierImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index 5862450ae..383f0d168 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,6 +82,7 @@ public class FirebaseAuthIT { private static final JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); private static final HttpTransport transport = ApiClientUtils.getDefaultTransport(); private static final String ACTION_LINK_CONTINUE_URL = "http://localhost/?a=1&b=2#c=3"; + private static final String INVALID_ACTION_LINK_CONTINUE_URL = "http://www.localhost/?a=1&b=2#c=3"; private static final FirebaseAuth auth = FirebaseAuth.getInstance( IntegrationTestUtils.ensureDefaultApp()); @@ -868,6 +869,31 @@ public void testGenerateSignInWithEmailLink() throws Exception { assertTrue(auth.getUser(user.getUid()).isEmailVerified()); } + @Test + public void testAuthErrorCodeParse() throws Exception { + RandomUser user = UserTestUtils.generateRandomUserInfo(); + temporaryUser.create(new UserRecord.CreateRequest() + .setUid(user.getUid()) + .setEmail(user.getEmail()) + .setEmailVerified(false) + .setPassword("password")); + try { + auth.generateSignInWithEmailLink(user.getEmail(), ActionCodeSettings.builder() + .setUrl(INVALID_ACTION_LINK_CONTINUE_URL) + .build()); + fail("No error thrown for invlaid custom hosting domain"); + } catch (FirebaseAuthException e) { + assertEquals( + "The domain of the continue URL is not whitelisted (UNAUTHORIZED_DOMAIN): Domain not " + + "allowlisted by project", + e.getMessage()); + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + assertNotNull(e.getCause()); + assertNotNull(e.getHttpResponse()); + assertEquals(AuthErrorCode.UNAUTHORIZED_CONTINUE_URL, e.getAuthErrorCode()); + } + } + @Test public void testOidcProviderConfigLifecycle() throws Exception { // Create provider config diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java index da46b6322..5c1829b0a 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/FirebaseCustomTokenTest.java b/src/test/java/com/google/firebase/auth/FirebaseCustomTokenTest.java index b51fe0c7b..f13f8c47b 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseCustomTokenTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseCustomTokenTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenTest.java index af65561a5..ca7d2132c 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseTokenTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseTokenTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenUtilsTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenUtilsTest.java index a274c0b11..911fd69f2 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseTokenUtilsTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseTokenUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java index d02d2b5da..c5f3ac952 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index f83c5f56f..057754087 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -402,9 +402,11 @@ public void testGetUserByProviderUidWithPhone() throws Exception { TestResponseInterceptor interceptor = initializeAppForUserManagement( TestUtils.loadResource("getUser.json")); UserRecord userRecord = FirebaseAuth.getInstance() - .getUserByProviderUidAsync("phone", "+1234567890").get(); + .getUserByProviderUidAsync(new String("phone"), "+1234567890").get(); checkUserRecord(userRecord); checkRequestHeaders(interceptor); + GenericJson parsed = parseRequestContent(interceptor); + assertEquals(ImmutableList.of("+1234567890"), parsed.get("phoneNumber")); } @Test @@ -412,9 +414,11 @@ public void testGetUserByProviderUidWithEmail() throws Exception { TestResponseInterceptor interceptor = initializeAppForUserManagement( TestUtils.loadResource("getUser.json")); UserRecord userRecord = FirebaseAuth.getInstance() - .getUserByProviderUidAsync("email", "testuser@example.com").get(); + .getUserByProviderUidAsync(new String("email"), "testuser@example.com").get(); checkUserRecord(userRecord); checkRequestHeaders(interceptor); + GenericJson parsed = parseRequestContent(interceptor); + assertEquals(ImmutableList.of("testuser@example.com"), parsed.get("email")); } @Test @@ -979,6 +983,7 @@ public void testTimeout() throws Exception { .setHttpTransport(transport) .setConnectTimeout(30000) .setReadTimeout(60000) + .setWriteTimeout(90000) .build()); FirebaseAuth auth = FirebaseAuth.getInstance(); FirebaseUserManager userManager = auth.getUserManager(); @@ -989,6 +994,7 @@ public void testTimeout() throws Exception { HttpRequest request = interceptor.getResponse().getRequest(); assertEquals(30000, request.getConnectTimeout()); assertEquals(60000, request.getReadTimeout()); + assertEquals(90000, request.getWriteTimeout()); } @Test @@ -1246,11 +1252,12 @@ public void testDeleteProviderAndPhone() { @Test public void testDoubleDeletePhoneProvider() throws Exception { + String providerId = new String("phone"); UserRecord.UpdateRequest update = new UserRecord.UpdateRequest("uid") .setPhoneNumber(null); try { - update.setProvidersToUnlink(ImmutableList.of("phone")); + update.setProvidersToUnlink(ImmutableList.of(providerId)); fail("No error thrown for double delete phone provider"); } catch (IllegalArgumentException expected) { } @@ -1258,8 +1265,9 @@ public void testDoubleDeletePhoneProvider() throws Exception { @Test public void testDoubleDeletePhoneProviderReverseOrder() throws Exception { + String providerId = new String("phone"); UserRecord.UpdateRequest update = new UserRecord.UpdateRequest("uid") - .setProvidersToUnlink(ImmutableList.of("phone")); + .setProvidersToUnlink(ImmutableList.of(providerId)); try { update.setPhoneNumber(null); @@ -3024,6 +3032,8 @@ private static void checkRequestHeaders(TestResponseInterceptor interceptor) { String clientVersion = "Java/Admin/" + SdkUtils.getVersion(); assertEquals(clientVersion, headers.getFirstHeaderStringValue("X-Client-Version")); + assertEquals(SdkUtils.getMetricsHeader(), headers.get("X-Goog-Api-Client")); + } private static void checkUrl(TestResponseInterceptor interceptor, String method, String url) { diff --git a/src/test/java/com/google/firebase/auth/GetUsersIT.java b/src/test/java/com/google/firebase/auth/GetUsersIT.java index a0bdcb6c6..033c8740f 100644 --- a/src/test/java/com/google/firebase/auth/GetUsersIT.java +++ b/src/test/java/com/google/firebase/auth/GetUsersIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/auth/ImportUserRecordTest.java b/src/test/java/com/google/firebase/auth/ImportUserRecordTest.java index 5a90690eb..cc58296de 100644 --- a/src/test/java/com/google/firebase/auth/ImportUserRecordTest.java +++ b/src/test/java/com/google/firebase/auth/ImportUserRecordTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/auth/ListUsersPageTest.java b/src/test/java/com/google/firebase/auth/ListUsersPageTest.java index db490fb6a..ee60e7db0 100644 --- a/src/test/java/com/google/firebase/auth/ListUsersPageTest.java +++ b/src/test/java/com/google/firebase/auth/ListUsersPageTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/MockGoogleCredentials.java b/src/test/java/com/google/firebase/auth/MockGoogleCredentials.java index 5e1bc84da..261bd62f1 100644 --- a/src/test/java/com/google/firebase/auth/MockGoogleCredentials.java +++ b/src/test/java/com/google/firebase/auth/MockGoogleCredentials.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/TestTokenFactory.java b/src/test/java/com/google/firebase/auth/TestTokenFactory.java index 7bf9eebb7..5ecd09566 100644 --- a/src/test/java/com/google/firebase/auth/TestTokenFactory.java +++ b/src/test/java/com/google/firebase/auth/TestTokenFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/UserImportHashTest.java b/src/test/java/com/google/firebase/auth/UserImportHashTest.java index 95d85088a..1141a486f 100644 --- a/src/test/java/com/google/firebase/auth/UserImportHashTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportHashTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/auth/UserImportOptionsTest.java b/src/test/java/com/google/firebase/auth/UserImportOptionsTest.java index 7913997b8..809fa3f48 100644 --- a/src/test/java/com/google/firebase/auth/UserImportOptionsTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportOptionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/auth/UserProviderTest.java b/src/test/java/com/google/firebase/auth/UserProviderTest.java index 7ed8611e1..526fe6058 100644 --- a/src/test/java/com/google/firebase/auth/UserProviderTest.java +++ b/src/test/java/com/google/firebase/auth/UserProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java b/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java index 8087b8583..ff643947a 100644 --- a/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java +++ b/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java index 679fe5a3c..96159f473 100644 --- a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java +++ b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -39,6 +39,7 @@ import com.google.firebase.auth.FirebaseAuthException; import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.MultiRequestMockHttpTransport; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestResponseInterceptor; @@ -87,6 +88,8 @@ public void testIAMCryptoSigner() throws Exception { final String url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" + "test-service-account@iam.gserviceaccount.com:signBlob"; assertEquals(url, interceptor.getResponse().getRequest().getUrl().toString()); + HttpRequest request = interceptor.getResponse().getRequest(); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } @Test @@ -173,6 +176,7 @@ public void testMetadataService() throws Exception { HttpRequest request = interceptor.getResponse().getRequest(); assertEquals(url, request.getUrl().toString()); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } @Test @@ -203,6 +207,7 @@ public void testExplicitServiceAccountEmail() throws Exception { HttpRequest request = interceptor.getResponse().getRequest(); assertEquals(url, request.getUrl().toString()); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } @Test diff --git a/src/test/java/com/google/firebase/auth/internal/FirebaseTokenFactoryTest.java b/src/test/java/com/google/firebase/auth/internal/FirebaseTokenFactoryTest.java index 875c781e9..5da5c4589 100644 --- a/src/test/java/com/google/firebase/auth/internal/FirebaseTokenFactoryTest.java +++ b/src/test/java/com/google/firebase/auth/internal/FirebaseTokenFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java index eca55ee66..cdb5852d0 100644 --- a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java +++ b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java @@ -353,6 +353,7 @@ private static void checkRequestHeaders(TestResponseInterceptor interceptor) { String clientVersion = "Java/Admin/" + SdkUtils.getVersion(); assertEquals(clientVersion, headers.getFirstHeaderStringValue("X-Client-Version")); + assertEquals(SdkUtils.getMetricsHeader(), headers.get("X-Goog-Api-Client")); } private static void checkUrl(TestResponseInterceptor interceptor, String method, String url) { diff --git a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java index 68e35492e..4cf36a3d7 100644 --- a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java +++ b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java @@ -38,6 +38,7 @@ import com.google.firebase.auth.MockTokenVerifier; import com.google.firebase.auth.SessionCookieOptions; import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; import java.io.ByteArrayOutputStream; @@ -85,6 +86,7 @@ public void testCreateSessionCookieAsync() throws Exception { assertEquals("testToken", parsed.get("idToken")); assertEquals(new BigDecimal(3600), parsed.get("validDuration")); checkUrl(interceptor, AUTH_BASE_URL + ":createSessionCookie"); + checkHeaders(interceptor); } @Test @@ -121,6 +123,7 @@ public void testCreateSessionCookie() throws Exception { assertEquals("testToken", parsed.get("idToken")); assertEquals(new BigDecimal(3600), parsed.get("validDuration")); checkUrl(interceptor, AUTH_BASE_URL + ":createSessionCookie"); + checkHeaders(interceptor); } @Test @@ -212,6 +215,7 @@ public void testVerifySessionCookieWithCheckRevoked() throws FirebaseAuthExcepti assertEquals("uid", token.getUid()); assertEquals("cookie", tokenVerifier.getLastTokenString()); checkUrl(interceptor, AUTH_BASE_URL + "/accounts:lookup"); + checkHeaders(interceptor); } @Test @@ -290,4 +294,9 @@ private static void checkUrl(TestResponseInterceptor interceptor, String url) { assertEquals(HttpMethods.POST, request.getRequestMethod()); assertEquals(url, request.getUrl().getRawPath()); } + + private static void checkHeaders(TestResponseInterceptor interceptor) { + HttpRequest request = interceptor.getResponse().getRequest(); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); + } } diff --git a/src/test/java/com/google/firebase/cloud/StorageClientIT.java b/src/test/java/com/google/firebase/cloud/StorageClientIT.java index 57fe58b82..7dda68716 100644 --- a/src/test/java/com/google/firebase/cloud/StorageClientIT.java +++ b/src/test/java/com/google/firebase/cloud/StorageClientIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ public void testCloudStorageCustomBucket() { public void testCloudStorageNonExistingBucket() { StorageClient storage = StorageClient.getInstance(IntegrationTestUtils.ensureDefaultApp()); try { - storage.bucket("non-existing"); + storage.bucket("non.existing"); fail("No error thrown for non-existing bucket"); } catch (IllegalArgumentException expected) { // ignore diff --git a/src/test/java/com/google/firebase/cloud/StorageClientTest.java b/src/test/java/com/google/firebase/cloud/StorageClientTest.java index 190fad6cc..87b2a709d 100644 --- a/src/test/java/com/google/firebase/cloud/StorageClientTest.java +++ b/src/test/java/com/google/firebase/cloud/StorageClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/DataSnapshotTest.java b/src/test/java/com/google/firebase/database/DataSnapshotTest.java index 083d8ce09..ea95f7c17 100644 --- a/src/test/java/com/google/firebase/database/DataSnapshotTest.java +++ b/src/test/java/com/google/firebase/database/DataSnapshotTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/DatabaseReferenceTest.java b/src/test/java/com/google/firebase/database/DatabaseReferenceTest.java index 207173f74..5012b284f 100644 --- a/src/test/java/com/google/firebase/database/DatabaseReferenceTest.java +++ b/src/test/java/com/google/firebase/database/DatabaseReferenceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/EventRecord.java b/src/test/java/com/google/firebase/database/EventRecord.java index dc32541b3..38518f17e 100644 --- a/src/test/java/com/google/firebase/database/EventRecord.java +++ b/src/test/java/com/google/firebase/database/EventRecord.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java index 3dc23746d..2a85036e3 100644 --- a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java +++ b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/MapBuilder.java b/src/test/java/com/google/firebase/database/MapBuilder.java index ea6b83831..e6bf5c37a 100644 --- a/src/test/java/com/google/firebase/database/MapBuilder.java +++ b/src/test/java/com/google/firebase/database/MapBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/MapperTest.java b/src/test/java/com/google/firebase/database/MapperTest.java index 941fbde9d..707065d63 100644 --- a/src/test/java/com/google/firebase/database/MapperTest.java +++ b/src/test/java/com/google/firebase/database/MapperTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/MutableDataTest.java b/src/test/java/com/google/firebase/database/MutableDataTest.java index d47d8aa97..77c1c655a 100644 --- a/src/test/java/com/google/firebase/database/MutableDataTest.java +++ b/src/test/java/com/google/firebase/database/MutableDataTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/ObjectMapTest.java b/src/test/java/com/google/firebase/database/ObjectMapTest.java index bd1cdcd97..55187d11e 100644 --- a/src/test/java/com/google/firebase/database/ObjectMapTest.java +++ b/src/test/java/com/google/firebase/database/ObjectMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/OnDisconnectTest.java b/src/test/java/com/google/firebase/database/OnDisconnectTest.java index 7d3174883..dba575215 100644 --- a/src/test/java/com/google/firebase/database/OnDisconnectTest.java +++ b/src/test/java/com/google/firebase/database/OnDisconnectTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/TestChildEventListener.java b/src/test/java/com/google/firebase/database/TestChildEventListener.java index 5407425ad..94c3e7fb6 100644 --- a/src/test/java/com/google/firebase/database/TestChildEventListener.java +++ b/src/test/java/com/google/firebase/database/TestChildEventListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/TestFailure.java b/src/test/java/com/google/firebase/database/TestFailure.java index b75ac7321..a767494fb 100644 --- a/src/test/java/com/google/firebase/database/TestFailure.java +++ b/src/test/java/com/google/firebase/database/TestFailure.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/TestHelpers.java b/src/test/java/com/google/firebase/database/TestHelpers.java index 00d9a3797..67878996d 100644 --- a/src/test/java/com/google/firebase/database/TestHelpers.java +++ b/src/test/java/com/google/firebase/database/TestHelpers.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/TestTokenProvider.java b/src/test/java/com/google/firebase/database/TestTokenProvider.java index 8c1830eb5..7f712612b 100644 --- a/src/test/java/com/google/firebase/database/TestTokenProvider.java +++ b/src/test/java/com/google/firebase/database/TestTokenProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/UtilitiesTest.java b/src/test/java/com/google/firebase/database/UtilitiesTest.java index be4aaad99..3e40b9df2 100644 --- a/src/test/java/com/google/firebase/database/UtilitiesTest.java +++ b/src/test/java/com/google/firebase/database/UtilitiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/ValueExpectationHelper.java b/src/test/java/com/google/firebase/database/ValueExpectationHelper.java index 9732bf73b..660748955 100644 --- a/src/test/java/com/google/firebase/database/ValueExpectationHelper.java +++ b/src/test/java/com/google/firebase/database/ValueExpectationHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/collection/ArraySortedMapTest.java b/src/test/java/com/google/firebase/database/collection/ArraySortedMapTest.java index 8f9a0d6b6..96d02a067 100644 --- a/src/test/java/com/google/firebase/database/collection/ArraySortedMapTest.java +++ b/src/test/java/com/google/firebase/database/collection/ArraySortedMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/collection/RBTreeSortedMapTest.java b/src/test/java/com/google/firebase/database/collection/RBTreeSortedMapTest.java index f29ef208a..c4779a6e5 100644 --- a/src/test/java/com/google/firebase/database/collection/RBTreeSortedMapTest.java +++ b/src/test/java/com/google/firebase/database/collection/RBTreeSortedMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/connection/ConnectionTest.java b/src/test/java/com/google/firebase/database/connection/ConnectionTest.java index 4e413fb48..18b1db929 100644 --- a/src/test/java/com/google/firebase/database/connection/ConnectionTest.java +++ b/src/test/java/com/google/firebase/database/connection/ConnectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/connection/ConnectionUtilsTest.java b/src/test/java/com/google/firebase/database/connection/ConnectionUtilsTest.java index 5f892bf41..bbcdcaa11 100644 --- a/src/test/java/com/google/firebase/database/connection/ConnectionUtilsTest.java +++ b/src/test/java/com/google/firebase/database/connection/ConnectionUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/connection/ListenAggregator.java b/src/test/java/com/google/firebase/database/connection/ListenAggregator.java index 868e0dfba..75c432ab5 100644 --- a/src/test/java/com/google/firebase/database/connection/ListenAggregator.java +++ b/src/test/java/com/google/firebase/database/connection/ListenAggregator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/connection/WebsocketConnectionTest.java b/src/test/java/com/google/firebase/database/connection/WebsocketConnectionTest.java index 02f2bfe50..fbc3092a1 100644 --- a/src/test/java/com/google/firebase/database/connection/WebsocketConnectionTest.java +++ b/src/test/java/com/google/firebase/database/connection/WebsocketConnectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/CompoundWriteTest.java b/src/test/java/com/google/firebase/database/core/CompoundWriteTest.java index 22f9161e9..b84f72786 100644 --- a/src/test/java/com/google/firebase/database/core/CompoundWriteTest.java +++ b/src/test/java/com/google/firebase/database/core/CompoundWriteTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/CoreTestHelpers.java b/src/test/java/com/google/firebase/database/core/CoreTestHelpers.java index 2f03679ec..9bee07ede 100644 --- a/src/test/java/com/google/firebase/database/core/CoreTestHelpers.java +++ b/src/test/java/com/google/firebase/database/core/CoreTestHelpers.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/JvmAuthTokenProviderTest.java b/src/test/java/com/google/firebase/database/core/JvmAuthTokenProviderTest.java index 9bc55950b..83039d42b 100644 --- a/src/test/java/com/google/firebase/database/core/JvmAuthTokenProviderTest.java +++ b/src/test/java/com/google/firebase/database/core/JvmAuthTokenProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java b/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java index 044b583b3..8153723a6 100644 --- a/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java +++ b/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/PathTest.java b/src/test/java/com/google/firebase/database/core/PathTest.java index 804966bd1..61de7cde7 100644 --- a/src/test/java/com/google/firebase/database/core/PathTest.java +++ b/src/test/java/com/google/firebase/database/core/PathTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/RandomOperationGenerator.java b/src/test/java/com/google/firebase/database/core/RandomOperationGenerator.java index e201d62e0..e0b737629 100644 --- a/src/test/java/com/google/firebase/database/core/RandomOperationGenerator.java +++ b/src/test/java/com/google/firebase/database/core/RandomOperationGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/RandomViewProcessorTest.java b/src/test/java/com/google/firebase/database/core/RandomViewProcessorTest.java index e2b377f0a..b30d7223a 100644 --- a/src/test/java/com/google/firebase/database/core/RandomViewProcessorTest.java +++ b/src/test/java/com/google/firebase/database/core/RandomViewProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/RangeMergeTest.java b/src/test/java/com/google/firebase/database/core/RangeMergeTest.java index 6da110920..b8e65bbf6 100644 --- a/src/test/java/com/google/firebase/database/core/RangeMergeTest.java +++ b/src/test/java/com/google/firebase/database/core/RangeMergeTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/RepoInfoTest.java b/src/test/java/com/google/firebase/database/core/RepoInfoTest.java index bc147ac3d..01aa55c41 100644 --- a/src/test/java/com/google/firebase/database/core/RepoInfoTest.java +++ b/src/test/java/com/google/firebase/database/core/RepoInfoTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/SyncPointTest.java b/src/test/java/com/google/firebase/database/core/SyncPointTest.java index 158a803e4..556c879c3 100644 --- a/src/test/java/com/google/firebase/database/core/SyncPointTest.java +++ b/src/test/java/com/google/firebase/database/core/SyncPointTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/ZombieVerifier.java b/src/test/java/com/google/firebase/database/core/ZombieVerifier.java index 8590f7684..28c24e437 100644 --- a/src/test/java/com/google/firebase/database/core/ZombieVerifier.java +++ b/src/test/java/com/google/firebase/database/core/ZombieVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java b/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java index 342700a3d..4397aaff4 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java +++ b/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java b/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java index 1135d98a1..ab9a78ce1 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java +++ b/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/persistence/MockListenProvider.java b/src/test/java/com/google/firebase/database/core/persistence/MockListenProvider.java index f0fdb019f..aa2f92ed7 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/MockListenProvider.java +++ b/src/test/java/com/google/firebase/database/core/persistence/MockListenProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/persistence/MockPersistenceStorageEngine.java b/src/test/java/com/google/firebase/database/core/persistence/MockPersistenceStorageEngine.java index 1e8d7887f..d76fce60f 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/MockPersistenceStorageEngine.java +++ b/src/test/java/com/google/firebase/database/core/persistence/MockPersistenceStorageEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/persistence/PruneForestTest.java b/src/test/java/com/google/firebase/database/core/persistence/PruneForestTest.java index bf3c7146a..1382c5435 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/PruneForestTest.java +++ b/src/test/java/com/google/firebase/database/core/persistence/PruneForestTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java b/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java index d55db51c3..096aa5ee2 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java +++ b/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/persistence/TestCachePolicy.java b/src/test/java/com/google/firebase/database/core/persistence/TestCachePolicy.java index f7c65e8e6..5d67c33c2 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/TestCachePolicy.java +++ b/src/test/java/com/google/firebase/database/core/persistence/TestCachePolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/persistence/TrackedQueryManagerTest.java b/src/test/java/com/google/firebase/database/core/persistence/TrackedQueryManagerTest.java index 8b293cedf..db382bccd 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/TrackedQueryManagerTest.java +++ b/src/test/java/com/google/firebase/database/core/persistence/TrackedQueryManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/utilities/TestClock.java b/src/test/java/com/google/firebase/database/core/utilities/TestClock.java index 570485224..10b11004b 100644 --- a/src/test/java/com/google/firebase/database/core/utilities/TestClock.java +++ b/src/test/java/com/google/firebase/database/core/utilities/TestClock.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/utilities/TreeTest.java b/src/test/java/com/google/firebase/database/core/utilities/TreeTest.java index 2eb0098b8..d5ca6aa4e 100644 --- a/src/test/java/com/google/firebase/database/core/utilities/TreeTest.java +++ b/src/test/java/com/google/firebase/database/core/utilities/TreeTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/view/QueryParamsTest.java b/src/test/java/com/google/firebase/database/core/view/QueryParamsTest.java index af7271f0a..b455e69f9 100644 --- a/src/test/java/com/google/firebase/database/core/view/QueryParamsTest.java +++ b/src/test/java/com/google/firebase/database/core/view/QueryParamsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/core/view/ViewAccess.java b/src/test/java/com/google/firebase/database/core/view/ViewAccess.java index e31c44d1e..49e09d7e3 100644 --- a/src/test/java/com/google/firebase/database/core/view/ViewAccess.java +++ b/src/test/java/com/google/firebase/database/core/view/ViewAccess.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/future/ReadFuture.java b/src/test/java/com/google/firebase/database/future/ReadFuture.java index 31a780b32..04510c232 100644 --- a/src/test/java/com/google/firebase/database/future/ReadFuture.java +++ b/src/test/java/com/google/firebase/database/future/ReadFuture.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/future/WriteFuture.java b/src/test/java/com/google/firebase/database/future/WriteFuture.java index 84f5ffbfe..54ac435e7 100644 --- a/src/test/java/com/google/firebase/database/future/WriteFuture.java +++ b/src/test/java/com/google/firebase/database/future/WriteFuture.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/DataTestIT.java b/src/test/java/com/google/firebase/database/integration/DataTestIT.java index d832f995d..343675fe8 100644 --- a/src/test/java/com/google/firebase/database/integration/DataTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/DataTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/EventHelper.java b/src/test/java/com/google/firebase/database/integration/EventHelper.java index 2dc20e381..f7afa19a9 100644 --- a/src/test/java/com/google/firebase/database/integration/EventHelper.java +++ b/src/test/java/com/google/firebase/database/integration/EventHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/EventTestIT.java b/src/test/java/com/google/firebase/database/integration/EventTestIT.java index 7eb6fdda3..bb9f8e36c 100644 --- a/src/test/java/com/google/firebase/database/integration/EventTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/EventTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java index 787e69ae3..36f3f819b 100644 --- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java index 84ec321a5..e421015c5 100644 --- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/InfoTestIT.java b/src/test/java/com/google/firebase/database/integration/InfoTestIT.java index 5acd581a2..c3d3404b9 100644 --- a/src/test/java/com/google/firebase/database/integration/InfoTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/InfoTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java b/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java index 416e56ca1..79451e7fe 100644 --- a/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/OrderTestIT.java b/src/test/java/com/google/firebase/database/integration/OrderTestIT.java index fff65efdf..94b9dd8e3 100644 --- a/src/test/java/com/google/firebase/database/integration/OrderTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/OrderTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/QueryTestIT.java b/src/test/java/com/google/firebase/database/integration/QueryTestIT.java index 9a5d50334..89425b04e 100644 --- a/src/test/java/com/google/firebase/database/integration/QueryTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/QueryTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java b/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java index 364d0f966..f20be2001 100644 --- a/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/RulesClient.java b/src/test/java/com/google/firebase/database/integration/RulesClient.java index 2e88d3a82..8352730cf 100644 --- a/src/test/java/com/google/firebase/database/integration/RulesClient.java +++ b/src/test/java/com/google/firebase/database/integration/RulesClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java index b41e2993d..2b7ad846f 100644 --- a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/ShutdownExample.java b/src/test/java/com/google/firebase/database/integration/ShutdownExample.java index ded28fe0f..e10557d00 100644 --- a/src/test/java/com/google/firebase/database/integration/ShutdownExample.java +++ b/src/test/java/com/google/firebase/database/integration/ShutdownExample.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java b/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java index fe65d8686..44df6fba8 100644 --- a/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/snapshot/CompoundHashTest.java b/src/test/java/com/google/firebase/database/snapshot/CompoundHashTest.java index 56afb50e8..d139be806 100644 --- a/src/test/java/com/google/firebase/database/snapshot/CompoundHashTest.java +++ b/src/test/java/com/google/firebase/database/snapshot/CompoundHashTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/snapshot/NodeTest.java b/src/test/java/com/google/firebase/database/snapshot/NodeTest.java index f94cea907..9dc82df7e 100644 --- a/src/test/java/com/google/firebase/database/snapshot/NodeTest.java +++ b/src/test/java/com/google/firebase/database/snapshot/NodeTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/util/GAuthTokenTest.java b/src/test/java/com/google/firebase/database/util/GAuthTokenTest.java index 3bd6678e7..b7dfbb684 100644 --- a/src/test/java/com/google/firebase/database/util/GAuthTokenTest.java +++ b/src/test/java/com/google/firebase/database/util/GAuthTokenTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/util/JsonMapperTest.java b/src/test/java/com/google/firebase/database/util/JsonMapperTest.java index 0134d2733..6218a4660 100644 --- a/src/test/java/com/google/firebase/database/util/JsonMapperTest.java +++ b/src/test/java/com/google/firebase/database/util/JsonMapperTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/utilities/DefaultRunLoopTest.java b/src/test/java/com/google/firebase/database/utilities/DefaultRunLoopTest.java index 065612568..9f5406f97 100644 --- a/src/test/java/com/google/firebase/database/utilities/DefaultRunLoopTest.java +++ b/src/test/java/com/google/firebase/database/utilities/DefaultRunLoopTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/utilities/NodeSizeEstimatorTest.java b/src/test/java/com/google/firebase/database/utilities/NodeSizeEstimatorTest.java index f886a4ac2..516f8442f 100644 --- a/src/test/java/com/google/firebase/database/utilities/NodeSizeEstimatorTest.java +++ b/src/test/java/com/google/firebase/database/utilities/NodeSizeEstimatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/utilities/PairTest.java b/src/test/java/com/google/firebase/database/utilities/PairTest.java index e667de02a..e077d8d04 100644 --- a/src/test/java/com/google/firebase/database/utilities/PairTest.java +++ b/src/test/java/com/google/firebase/database/utilities/PairTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/utilities/PushIdGeneratorTest.java b/src/test/java/com/google/firebase/database/utilities/PushIdGeneratorTest.java index 5aa3a8b95..af01e3e1a 100644 --- a/src/test/java/com/google/firebase/database/utilities/PushIdGeneratorTest.java +++ b/src/test/java/com/google/firebase/database/utilities/PushIdGeneratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java b/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java index 0dcc1cbd0..416d8eb45 100644 --- a/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java +++ b/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/database/utilities/ValidationTest.java b/src/test/java/com/google/firebase/database/utilities/ValidationTest.java index 3293f5d6d..d2979612d 100644 --- a/src/test/java/com/google/firebase/database/utilities/ValidationTest.java +++ b/src/test/java/com/google/firebase/database/utilities/ValidationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdIT.java b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdIT.java index b05f23949..b049ff21a 100644 --- a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdIT.java +++ b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java index 27864d992..4efb1e2a5 100644 --- a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java +++ b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ import com.google.firebase.OutgoingHttpRequest; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.GenericFunction; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; @@ -174,6 +175,7 @@ public Void call(Object... args) throws Exception { assertEquals(HttpMethods.DELETE, request.getRequestMethod()); assertEquals(TEST_URL, request.getUrl().toString()); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } } diff --git a/src/test/java/com/google/firebase/internal/AbstractPlatformErrorHandlerTest.java b/src/test/java/com/google/firebase/internal/AbstractPlatformErrorHandlerTest.java index ca4b063f0..5660c0a00 100644 --- a/src/test/java/com/google/firebase/internal/AbstractPlatformErrorHandlerTest.java +++ b/src/test/java/com/google/firebase/internal/AbstractPlatformErrorHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java new file mode 100644 index 000000000..7106ce5c5 --- /dev/null +++ b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java @@ -0,0 +1,355 @@ +/* + * 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.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.util.GenericData; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseException; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.IncomingHttpResponse; +import com.google.firebase.auth.MockGoogleCredentials; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestInterceptor; +import org.apache.hc.core5.http.protocol.HttpContext; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ApacheHttp2TransportIT { + private static FirebaseApp app; + private static final GoogleCredentials MOCK_CREDENTIALS = new MockGoogleCredentials("test_token"); + private static final ImmutableMap payload = + ImmutableMap.of("foo", "bar"); + + // Sets a 5 second delay before server response to simulate a slow network that + // results in a read timeout. + private static final String DELAY_URL = "https://nghttp2.org/httpbin/delay/5"; + private static final String GET_URL = "https://nghttp2.org/httpbin/get"; + private static final String POST_URL = "https://nghttp2.org/httpbin/post"; + + private static ServerSocket serverSocket; + private static Socket fillerSocket; + private static int port; + + @BeforeClass + public static void setUpClass() throws IOException { + // Start server socket with a backlog queue of 1 and a automatically assigned + // port + serverSocket = new ServerSocket(0, 1); + port = serverSocket.getLocalPort(); + // Fill the backlog queue to force socket to ignore future connections + fillerSocket = new Socket(); + fillerSocket.connect(serverSocket.getLocalSocketAddress()); + } + + @AfterClass + public static void cleanUpClass() throws IOException { + if (serverSocket != null && !serverSocket.isClosed()) { + serverSocket.close(); + } + if (fillerSocket != null && !fillerSocket.isClosed()) { + fillerSocket.close(); + } + } + + @After + public void cleanup() { + if (app != null) { + app.delete(); + } + + System.clearProperty("http.proxyHost"); + System.clearProperty("http.proxyPort"); + System.clearProperty("https.proxyHost"); + System.clearProperty("https.proxyPort"); + } + + @Test(timeout = 10_000L) + public void testUnauthorizedGetRequest() throws FirebaseException { + ErrorHandlingHttpClient httpClient = getHttpClient(false); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest(GET_URL); + IncomingHttpResponse response = httpClient.send(request); + assertEquals(200, response.getStatusCode()); + } + + @Test(timeout = 10_000L) + public void testUnauthorizedPostRequest() throws FirebaseException { + ErrorHandlingHttpClient httpClient = getHttpClient(false); + HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(POST_URL, payload); + GenericData body = httpClient.sendAndParse(request, GenericData.class); + assertEquals("{\"foo\":\"bar\"}", body.get("data")); + } + + @Test(timeout = 10_000L) + public void testConnectTimeoutAuthorizedGet() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setConnectTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest("https://localhost:" + port); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Connection Timeout", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testConnectTimeoutAuthorizedPost() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setConnectTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest("https://localhost:" + port, payload); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Connection Timeout", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testReadTimeoutAuthorizedGet() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setReadTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest(DELAY_URL); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Stream exception in request", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testReadTimeoutAuthorizedPost() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setReadTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(DELAY_URL, payload); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Stream exception in request", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testWriteTimeoutAuthorizedGet() throws FirebaseException { + // Use a fresh transport so that writeTimeout triggers while waiting for the transport to + // be ready to receive data. + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setWriteTimeout(100) + .setHttpTransport(new ApacheHttp2Transport()) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest(GET_URL); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Write Timeout", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testWriteTimeoutAuthorizedPost() throws FirebaseException { + // Use a fresh transport so that writeTimeout triggers while waiting for the transport to + // be ready to receive data. + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setWriteTimeout(100) + .setHttpTransport(new ApacheHttp2Transport()) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(POST_URL, payload); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Write Timeout", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testRequestShouldNotFollowRedirects() throws IOException { + ApacheHttp2Transport transport = new ApacheHttp2Transport(); + ApacheHttp2Request request = transport.buildRequest("GET", + "https://google.com"); + LowLevelHttpResponse response = request.execute(); + + assertEquals(301, response.getStatusCode()); + assert (response instanceof ApacheHttp2Response); + assertEquals("https://www.google.com/", ((ApacheHttp2Response) response).getHeaderValue("location")); + } + + @Test(timeout = 10_000L) + public void testRequestCanSetHeaders() { + final AtomicBoolean interceptorCalled = new AtomicBoolean(false); + CloseableHttpAsyncClient client = HttpAsyncClients.custom() + .addRequestInterceptorFirst( + new HttpRequestInterceptor() { + @Override + public void process( + HttpRequest request, EntityDetails details, HttpContext context) + throws HttpException, IOException { + Header header = request.getFirstHeader("foo"); + assertNotNull("Should have found header", header); + assertEquals("bar", header.getValue()); + interceptorCalled.set(true); + throw new IOException("cancelling request"); + } + }) + .build(); + + ApacheHttp2Transport transport = new ApacheHttp2Transport(client); + ApacheHttp2Request request = transport.buildRequest("GET", "http://www.google.com"); + request.addHeader("foo", "bar"); + try { + request.execute(); + fail("should not actually make the request"); + } catch (IOException exception) { + assertEquals("Unknown exception in request", exception.getMessage()); + } + assertTrue("Expected to have called our test interceptor", interceptorCalled.get()); + } + + @Test(timeout = 10_000L) + public void testVerifyProxyIsRespected() { + try { + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", "8080"); + + HttpTransport transport = new ApacheHttp2Transport(); + transport.createRequestFactory().buildGetRequest(new GenericUrl(GET_URL)).execute(); + fail("No exception thrown for HTTP error response"); + } catch (IOException e) { + assertEquals("Connection exception in request", e.getMessage()); + assertTrue(e.getCause().getMessage().contains("localhost:8080")); + } + } + + @Test + public void testVerifyDefaultTransportReused() { + FirebaseOptions o1 = FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .build(); + + FirebaseOptions o2 = FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .build(); + + HttpTransport t1 = o1.getHttpTransport(); + HttpTransport t2 = o2.getHttpTransport(); + assertEquals(t1, t2); + } + + private static ErrorHandlingHttpClient getHttpClient(boolean authorized, + FirebaseApp app) { + HttpRequestFactory requestFactory; + if (authorized) { + requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app); + } else { + requestFactory = ApiClientUtils.newUnauthorizedRequestFactory(app); + } + JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + TestHttpErrorHandler errorHandler = new TestHttpErrorHandler(); + return new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler); + } + + private static ErrorHandlingHttpClient getHttpClient(boolean authorized) { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .build(), "test-app"); + return getHttpClient(authorized, app); + } + + private static class TestHttpErrorHandler implements HttpErrorHandler { + @Override + public FirebaseException handleIOException(IOException e) { + return new FirebaseException( + ErrorCode.UNKNOWN, "IO error: " + e.getMessage(), e); + } + + @Override + public FirebaseException handleHttpResponseException( + HttpResponseException e, IncomingHttpResponse response) { + return new FirebaseException( + ErrorCode.INTERNAL, "Example error message: " + e.getContent(), e, response); + } + + @Override + public FirebaseException handleParseException(IOException e, IncomingHttpResponse response) { + return new FirebaseException(ErrorCode.UNKNOWN, "Parse error", e, response); + } + } +} diff --git a/src/test/java/com/google/firebase/internal/ApacheHttp2TransportTest.java b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportTest.java new file mode 100644 index 000000000..ba604d3d1 --- /dev/null +++ b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportTest.java @@ -0,0 +1,474 @@ +/* + * 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.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.api.client.http.ByteArrayContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpMethods; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.InputStreamContent; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.util.ByteArrayStreamingContent; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.async.HttpAsyncClient; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestMapper; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.Message; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.io.HttpService; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler; +import org.apache.hc.core5.http.message.BasicHttpResponse; +import org.apache.hc.core5.http.nio.AsyncPushConsumer; +import org.apache.hc.core5.http.nio.AsyncRequestProducer; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; +import org.apache.hc.core5.http.nio.HandlerFactory; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpProcessor; +import org.junit.Assert; +import org.junit.Test; + +public class ApacheHttp2TransportTest { + @Test + public void testContentLengthSet() throws Exception { + SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(HttpMethods.POST) + .setUri("http://www.google.com"); + + ApacheHttp2Request request = new ApacheHttp2Request( + new MockApacheHttp2AsyncClient() { + @SuppressWarnings("unchecked") + @Override + public Future doExecute( + final HttpHost target, + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HandlerFactory pushHandlerFactory, + final HttpContext context, + final FutureCallback callback) { + return (Future) CompletableFuture + .completedFuture( + new Message(new BasicHttpResponse(200), null)); + } + }, requestBuilder); + + HttpContent content = new ByteArrayContent("text/plain", + "sample".getBytes(StandardCharsets.UTF_8)); + request.setStreamingContent(content); + request.setContentLength(content.getLength()); + request.execute(); + + assertFalse(request.getEntityProducer().isChunked()); + assertEquals(6, request.getEntityProducer().getContentLength()); + } + + @Test + public void testChunked() throws Exception { + byte[] buf = new byte[300]; + Arrays.fill(buf, (byte) ' '); + SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(HttpMethods.POST) + .setUri("http://www.google.com"); + ApacheHttp2Request request = new ApacheHttp2Request( + new MockApacheHttp2AsyncClient() { + @SuppressWarnings("unchecked") + @Override + public Future doExecute( + final HttpHost target, + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HandlerFactory pushHandlerFactory, + final HttpContext context, + final FutureCallback callback) { + return (Future) CompletableFuture + .completedFuture( + new Message(new BasicHttpResponse(200), null)); + } + }, requestBuilder); + + HttpContent content = new InputStreamContent("text/plain", new ByteArrayInputStream(buf)); + request.setStreamingContent(content); + request.execute(); + + assertTrue(request.getEntityProducer().isChunked()); + assertEquals(-1, request.getEntityProducer().getContentLength()); + } + + @Test + public void testExecute() throws Exception { + SimpleHttpResponse simpleHttpResponse = SimpleHttpResponse.create(200, new byte[] { 1, 2, 3 }); + + SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(HttpMethods.POST) + .setUri("http://www.google.com"); + + ApacheHttp2Request request = new ApacheHttp2Request( + new MockApacheHttp2AsyncClient() { + @SuppressWarnings("unchecked") + @Override + public Future doExecute( + final HttpHost target, + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HandlerFactory pushHandlerFactory, + final HttpContext context, + final FutureCallback callback) { + return (Future) CompletableFuture + .completedFuture(new Message(simpleHttpResponse, + new ApacheHttp2Entity(simpleHttpResponse.getBodyBytes(), null))); + } + }, requestBuilder); + LowLevelHttpResponse response = request.execute(); + assertTrue(response instanceof ApacheHttp2Response); + + // we confirm that the simple response we prepared in this test is the same as + // the content's response + assertTrue(response.getContent() instanceof ByteArrayInputStream); + assertEquals(simpleHttpResponse, ((ApacheHttp2Response) response).getMessage().getHead()); + // No need to cloase ByteArrayInputStream since close() has no effect. + } + + @Test + public void testApacheHttpTransport() { + ApacheHttp2Transport transport = new ApacheHttp2Transport(); + checkHttpTransport(transport); + assertFalse(transport.isMtls()); + } + + @Test + public void testApacheHttpTransportWithParam() { + HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom(); + ApacheHttp2Transport transport = new ApacheHttp2Transport(clientBuilder.build(), true); + checkHttpTransport(transport); + assertTrue(transport.isMtls()); + } + + @Test + public void testNewDefaultHttpClient() { + HttpAsyncClient client = ApacheHttp2Transport.newDefaultHttpAsyncClient(); + checkHttpClient(client); + } + + @Test + public void testDefaultHttpClientBuilder() { + HttpAsyncClientBuilder clientBuilder = ApacheHttp2Transport.defaultHttpAsyncClientBuilder(); + HttpAsyncClient client = clientBuilder.build(); + checkHttpClient(client); + } + + private void checkHttpTransport(ApacheHttp2Transport transport) { + assertNotNull(transport); + HttpAsyncClient client = transport.getHttpClient(); + checkHttpClient(client); + } + + private void checkHttpClient(HttpAsyncClient client) { + assertNotNull(client); + } + + @Test + public void testRequestsWithContent() throws IOException { + // This test confirms that we can set the content on any type of request + CloseableHttpAsyncClient mockClient = new MockApacheHttp2AsyncClient() { + @SuppressWarnings("unchecked") + @Override + public Future doExecute( + final HttpHost target, + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HandlerFactory pushHandlerFactory, + final HttpContext context, + final FutureCallback callback) { + return (Future) CompletableFuture + .completedFuture(new Message( + new BasicHttpResponse(200), null)); + } + }; + ApacheHttp2Transport transport = new ApacheHttp2Transport(mockClient); + + // Test GET. + execute(transport.buildRequest("GET", "http://www.test.url")); + // Test DELETE. + execute(transport.buildRequest("DELETE", "http://www.test.url")); + // Test HEAD. + execute(transport.buildRequest("HEAD", "http://www.test.url")); + // Test PATCH. + execute(transport.buildRequest("PATCH", "http://www.test.url")); + // Test PUT. + execute(transport.buildRequest("PUT", "http://www.test.url")); + // Test POST. + execute(transport.buildRequest("POST", "http://www.test.url")); + // Test PATCH. + execute(transport.buildRequest("PATCH", "http://www.test.url")); + } + + @Test + public void testNormalizedUrl() throws IOException { + final HttpRequestHandler handler = new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + // Extract the request URI and convert to bytes + byte[] responseData = request.getRequestUri().getBytes(StandardCharsets.UTF_8); + + // Set the response headers (status code and content length) + response.setCode(HttpStatus.SC_OK); + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + + // Set the response entity (body) + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new ApacheHttp2Transport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpResponse response = transport.createRequestFactory() + .buildGetRequest(testUrl) + .execute(); + assertEquals(200, response.getStatusCode()); + assertEquals("/foo//bar", response.parseAsString()); + } + } + + @Test + public void testReadErrorStream() throws IOException { + final HttpRequestHandler handler = new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + byte[] responseData = "Forbidden".getBytes(StandardCharsets.UTF_8); + response.setCode(HttpStatus.SC_FORBIDDEN); // 403 Forbidden + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new ApacheHttp2Transport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpRequest getRequest = transport.createRequestFactory() + .buildGetRequest(testUrl); + getRequest.setThrowExceptionOnExecuteError(false); + com.google.api.client.http.HttpResponse response = getRequest.execute(); + assertEquals(403, response.getStatusCode()); + assertEquals("Forbidden", response.parseAsString()); + } + } + + @Test + public void testReadErrorStream_withException() throws IOException { + final HttpRequestHandler handler = new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + byte[] responseData = "Forbidden".getBytes(StandardCharsets.UTF_8); + response.setCode(HttpStatus.SC_FORBIDDEN); // 403 Forbidden + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new ApacheHttp2Transport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpRequest getRequest = transport.createRequestFactory() + .buildGetRequest(testUrl); + try { + getRequest.execute(); + Assert.fail(); + } catch (HttpResponseException ex) { + assertEquals("Forbidden", ex.getContent()); + } + } + } + + private void execute(ApacheHttp2Request request) throws IOException { + byte[] bytes = "abc".getBytes(StandardCharsets.UTF_8); + request.setStreamingContent(new ByteArrayStreamingContent(bytes)); + request.setContentType("text/html"); + request.setContentLength(bytes.length); + request.execute(); + } + + @Test + public void testGzipResponse() throws IOException { + final String originalContent = "hello world"; + final java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); + try (java.util.zip.GZIPOutputStream gzip = new java.util.zip.GZIPOutputStream(baos)) { + gzip.write(originalContent.getBytes(StandardCharsets.UTF_8)); + } + final byte[] gzippedContent = baos.toByteArray(); + + final HttpRequestHandler handler = new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + response.setCode(HttpStatus.SC_OK); + response.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip"); + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(gzippedContent.length)); + response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=UTF-8"); + ByteArrayEntity entity = new ByteArrayEntity(gzippedContent, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + + try (FakeServer server = new FakeServer(handler)) { + ApacheHttp2Transport transport = new ApacheHttp2Transport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo"); + testUrl.setPort(server.getPort()); + + // Execute the low-level request directly to accurately assert metadata + // without Google's parsed HttpHeaders map mangling the payload lengths. + ApacheHttp2Request request = transport.buildRequest("GET", testUrl.build()); + LowLevelHttpResponse response = request.execute(); + + assertEquals(200, response.getStatusCode()); + assertEquals("text/plain; charset=UTF-8", response.getContentType()); + + boolean wasAutoDecompressed = response.getContentEncoding() == null; + if (wasAutoDecompressed) { + assertEquals(originalContent.length(), response.getContentLength()); + } else { + assertEquals("gzip", response.getContentEncoding()); + assertEquals(gzippedContent.length, response.getContentLength()); + } + + // Verify the low-level stream returns the exact expected payload based on + // decompression state + java.io.InputStream stream = response.getContent(); + byte[] resultBytes = com.google.common.io.ByteStreams.toByteArray(stream); + + if (wasAutoDecompressed) { + assertEquals(originalContent, new String(resultBytes, StandardCharsets.UTF_8)); + } else { + org.junit.Assert.assertArrayEquals(gzippedContent, resultBytes); + } + } + } + + @Test + public void testEmptyResponseWithHeaders() throws IOException { + // Tests that a response with no actual body but headers does not throw NPE + // in ApacheHttp2Response due to entity being null. + final HttpRequestHandler handler = new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + response.setCode(HttpStatus.SC_NO_CONTENT); + response.setHeader(HttpHeaders.CONTENT_LENGTH, "0"); + // Explicitly omitting the entity to simulate NO_CONTENT bodyless response + } + }; + + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new ApacheHttp2Transport(); + GenericUrl testUrl = new GenericUrl("http://localhost/empty"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpResponse response = transport.createRequestFactory() + .buildGetRequest(testUrl) + .execute(); + + assertEquals(204, response.getStatusCode()); + assertEquals(0L, response.getHeaders().getContentLength().longValue()); + } + } + + private static class FakeServer implements AutoCloseable { + private final HttpServer server; + + FakeServer(final HttpRequestHandler httpHandler) throws IOException { + HttpRequestMapper mapper = new HttpRequestMapper() { + @Override + public HttpRequestHandler resolve(HttpRequest request, HttpContext context) + throws HttpException { + return httpHandler; + } + }; + server = new HttpServer( + 0, + HttpService.builder() + .withHttpProcessor( + new HttpProcessor() { + @Override + public void process( + HttpRequest request, EntityDetails entity, HttpContext context) + throws HttpException, IOException { + } + + @Override + public void process( + HttpResponse response, EntityDetails entity, HttpContext context) + throws HttpException, IOException { + } + }) + .withHttpServerRequestHandler(new BasicHttpServerRequestHandler(mapper)) + .build(), + null, + null, + null, + null, + null, + null); + server.start(); + } + + public int getPort() { + return server.getLocalPort(); + } + + @Override + public void close() { + server.initiateShutdown(); + } + } +} diff --git a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java index f23c7a34d..56eb14022 100644 --- a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java +++ b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; import com.google.api.client.http.HttpUnsuccessfulResponseHandler; import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; @@ -128,4 +129,12 @@ public void disconnect() throws IOException { assertTrue(lowLevelResponse.isDisconnected()); } + + @Test + public void testVerifyDefaultTransportReused() { + HttpTransport t1 = ApiClientUtils.getDefaultTransport(); + HttpTransport t2 = ApiClientUtils.getDefaultTransport(); + assertEquals(t1, t2); + } + } diff --git a/src/test/java/com/google/firebase/internal/CallableOperationTest.java b/src/test/java/com/google/firebase/internal/CallableOperationTest.java index d38c96776..f9f26cc14 100644 --- a/src/test/java/com/google/firebase/internal/CallableOperationTest.java +++ b/src/test/java/com/google/firebase/internal/CallableOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/internal/CountingLowLevelHttpRequest.java b/src/test/java/com/google/firebase/internal/CountingLowLevelHttpRequest.java index f2a4b9f5a..7c1b02a93 100644 --- a/src/test/java/com/google/firebase/internal/CountingLowLevelHttpRequest.java +++ b/src/test/java/com/google/firebase/internal/CountingLowLevelHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/internal/DateUtilsTest.java b/src/test/java/com/google/firebase/internal/DateUtilsTest.java index 7c2dfd089..d6b2e386c 100644 --- a/src/test/java/com/google/firebase/internal/DateUtilsTest.java +++ b/src/test/java/com/google/firebase/internal/DateUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java b/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java index 8b3e75efd..39d3929d0 100644 --- a/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java +++ b/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. @@ -127,6 +127,7 @@ public void testSuccessfulRequestWithHeadersAndBody() throws FirebaseException, assertEquals("v1", last.getHeaders().get("h1")); assertEquals("v2", last.getHeaders().get("h2")); assertEquals("v3", last.getHeaders().get("h3")); + assertEquals(SdkUtils.getMetricsHeader(), last.getHeaders().get("x-goog-api-client")); ByteArrayOutputStream out = new ByteArrayOutputStream(); last.getContent().writeTo(out); diff --git a/src/test/java/com/google/firebase/internal/FirebaseAppStoreTest.java b/src/test/java/com/google/firebase/internal/FirebaseAppStoreTest.java index b6b502fdc..dba04456f 100644 --- a/src/test/java/com/google/firebase/internal/FirebaseAppStoreTest.java +++ b/src/test/java/com/google/firebase/internal/FirebaseAppStoreTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/internal/FirebaseRequestInitializerTest.java b/src/test/java/com/google/firebase/internal/FirebaseRequestInitializerTest.java index fde2f918b..044cef301 100644 --- a/src/test/java/com/google/firebase/internal/FirebaseRequestInitializerTest.java +++ b/src/test/java/com/google/firebase/internal/FirebaseRequestInitializerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -38,6 +38,7 @@ public class FirebaseRequestInitializerTest { private static final int MAX_RETRIES = 5; private static final int CONNECT_TIMEOUT_MILLIS = 30000; private static final int READ_TIMEOUT_MILLIS = 60000; + private static final int WRITE_TIMEOUT_MILLIS = 90000; @After public void tearDown() { @@ -69,6 +70,7 @@ public void testExplicitTimeouts() throws Exception { .setCredentials(new MockGoogleCredentials("token")) .setConnectTimeout(CONNECT_TIMEOUT_MILLIS) .setReadTimeout(READ_TIMEOUT_MILLIS) + .setWriteTimeout(WRITE_TIMEOUT_MILLIS) .build()); HttpRequest request = TestUtils.createRequest(); @@ -77,6 +79,7 @@ public void testExplicitTimeouts() throws Exception { assertEquals(CONNECT_TIMEOUT_MILLIS, request.getConnectTimeout()); assertEquals(READ_TIMEOUT_MILLIS, request.getReadTimeout()); + assertEquals(WRITE_TIMEOUT_MILLIS, request.getWriteTimeout()); assertEquals("Bearer token", request.getHeaders().getAuthorization()); assertEquals(HttpRequest.DEFAULT_NUMBER_OF_RETRIES, request.getNumberOfRetries()); assertNull(request.getIOExceptionHandler()); diff --git a/src/test/java/com/google/firebase/internal/FirebaseThreadManagersTest.java b/src/test/java/com/google/firebase/internal/FirebaseThreadManagersTest.java index 286cdf09f..f6eef6847 100644 --- a/src/test/java/com/google/firebase/internal/FirebaseThreadManagersTest.java +++ b/src/test/java/com/google/firebase/internal/FirebaseThreadManagersTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/internal/MockApacheHttp2Response.java b/src/test/java/com/google/firebase/internal/MockApacheHttp2Response.java new file mode 100644 index 000000000..d2a859e1e --- /dev/null +++ b/src/test/java/com/google/firebase/internal/MockApacheHttp2Response.java @@ -0,0 +1,188 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.ProtocolVersion; + +public class MockApacheHttp2Response implements HttpResponse { + List

headers = new ArrayList<>(); + int code = 200; + + @Override + public void setVersion(ProtocolVersion version) { + } + + @Override + public ProtocolVersion getVersion() { + return null; + } + + @Override + public void addHeader(Header header) { + headers.add(header); + } + + @Override + public void addHeader(String name, Object value) { + addHeader(newHeader(name, value)); + } + + private Header newHeader(String key, Object value) { + return new Header() { + @Override + public boolean isSensitive() { + return false; + } + + @Override + public String getName() { + return key; + } + + @Override + public String getValue() { + return value.toString(); + } + }; + } + + @Override + public void setHeader(Header header) { + if (headers.contains(header)) { + int index = headers.indexOf(header); + headers.set(index, header); + } else { + addHeader(header); + } + } + + @Override + public void setHeader(String name, Object value) { + setHeader(newHeader(name, value)); + } + + @Override + public void setHeaders(Header... headers) { + for (Header header : headers) { + setHeader(header); + } + } + + @Override + public boolean removeHeader(Header header) { + if (headers.contains(header)) { + headers.remove(headers.indexOf(header)); + return true; + } + return false; + } + + @Override + public boolean removeHeaders(String name) { + int initialSize = headers.size(); + for (Header header : headers.stream().filter(h -> h.getName() == name) + .collect(Collectors.toList())) { + removeHeader(header); + } + return headers.size() < initialSize; + } + + @Override + public boolean containsHeader(String name) { + return headers.stream().anyMatch(h -> h.getName() == name); + } + + @Override + public int countHeaders(String name) { + return headers.size(); + } + + @Override + public Header getFirstHeader(String name) { + return headers.stream().findFirst().orElse(null); + } + + @Override + public Header getHeader(String name) throws ProtocolException { + return headers.stream().filter(h -> h.getName() == name).findFirst().orElse(null); + } + + @Override + public Header[] getHeaders() { + return headers.toArray(new Header[0]); + } + + @Override + public Header[] getHeaders(String name) { + return headers.stream() + .filter(h -> h.getName() == name) + .collect(Collectors.toList()) + .toArray(new Header[0]); + } + + @Override + public Header getLastHeader(String name) { + return headers.isEmpty() ? null : headers.get(headers.size() - 1); + } + + @Override + public Iterator
headerIterator() { + return headers.iterator(); + } + + @Override + public Iterator
headerIterator(String name) { + return headers.stream().filter(h -> h.getName() == name).iterator(); + } + + @Override + public int getCode() { + return this.code; + } + + @Override + public void setCode(int code) { + this.code = code; + } + + @Override + public String getReasonPhrase() { + return null; + } + + @Override + public void setReasonPhrase(String reason) { + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public void setLocale(Locale loc) { + } +} diff --git a/src/test/java/com/google/firebase/internal/RetryConfigTest.java b/src/test/java/com/google/firebase/internal/RetryConfigTest.java index 7fab5075d..c96ae8f78 100644 --- a/src/test/java/com/google/firebase/internal/RetryConfigTest.java +++ b/src/test/java/com/google/firebase/internal/RetryConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/internal/RetryInitializerTest.java b/src/test/java/com/google/firebase/internal/RetryInitializerTest.java index 8732e8bf5..5bc93607e 100644 --- a/src/test/java/com/google/firebase/internal/RetryInitializerTest.java +++ b/src/test/java/com/google/firebase/internal/RetryInitializerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandlerTest.java b/src/test/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandlerTest.java index f0bc2d0b3..dec8a8240 100644 --- a/src/test/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandlerTest.java +++ b/src/test/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/internal/TestApiClientUtils.java b/src/test/java/com/google/firebase/internal/TestApiClientUtils.java index 510e08cd5..a5dc35f60 100644 --- a/src/test/java/com/google/firebase/internal/TestApiClientUtils.java +++ b/src/test/java/com/google/firebase/internal/TestApiClientUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/messaging/BatchResponseTest.java b/src/test/java/com/google/firebase/messaging/BatchResponseTest.java index 441822220..7b562d556 100644 --- a/src/test/java/com/google/firebase/messaging/BatchResponseTest.java +++ b/src/test/java/com/google/firebase/messaging/BatchResponseTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java index 17848cc65..03bfc4327 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -17,7 +17,6 @@ package com.google.firebase.messaging; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -29,7 +28,6 @@ import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpMethods; import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.HttpResponseInterceptor; import com.google.api.client.http.HttpTransport; @@ -73,16 +71,9 @@ public class FirebaseMessagingClientImplTest { private static final String MOCK_RESPONSE = "{\"name\": \"mock-name\"}"; - private static final String MOCK_BATCH_SUCCESS_RESPONSE = TestUtils.loadResource( - "fcm_batch_success.txt"); - - private static final String MOCK_BATCH_FAILURE_RESPONSE = TestUtils.loadResource( - "fcm_batch_failure.txt"); - private static final Message EMPTY_MESSAGE = Message.builder() .setTopic("test-topic") .build(); - private static final List MESSAGE_LIST = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE); private static final boolean DRY_RUN_ENABLED = true; private static final boolean DRY_RUN_DISABLED = false; @@ -320,187 +311,6 @@ public void testSendErrorWithDetailsAndNoCode() { } } - @Test - public void testSendAll() throws Exception { - final TestResponseInterceptor interceptor = new TestResponseInterceptor(); - FirebaseMessagingClient client = initMessagingClientForBatchRequests( - MOCK_BATCH_SUCCESS_RESPONSE, interceptor); - - BatchResponse responses = client.sendAll(MESSAGE_LIST, false); - - assertBatchResponse(responses, interceptor, 2, 0); - } - - @Test - public void testSendAllDryRun() throws Exception { - final TestResponseInterceptor interceptor = new TestResponseInterceptor(); - FirebaseMessagingClient client = initMessagingClientForBatchRequests( - MOCK_BATCH_SUCCESS_RESPONSE, interceptor); - - BatchResponse responses = client.sendAll(MESSAGE_LIST, true); - - assertBatchResponse(responses, interceptor, 2, 0); - } - - @Test - public void testRequestInitializerAppliedToBatchRequests() throws Exception { - TestResponseInterceptor interceptor = new TestResponseInterceptor(); - MockHttpTransport transport = new MockHttpTransport.Builder() - .setLowLevelHttpResponse(getBatchResponse(MOCK_BATCH_SUCCESS_RESPONSE)) - .build(); - HttpRequestInitializer initializer = new HttpRequestInitializer() { - @Override - public void initialize(HttpRequest httpRequest) { - httpRequest.getHeaders().set("x-custom-header", "test-value"); - } - }; - FirebaseMessagingClientImpl client = FirebaseMessagingClientImpl.builder() - .setProjectId("test-project") - .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(transport.createRequestFactory(initializer)) - .setChildRequestFactory(Utils.getDefaultTransport().createRequestFactory()) - .setResponseInterceptor(interceptor) - .build(); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - } finally { - HttpRequest request = interceptor.getLastRequest(); - assertEquals("test-value", request.getHeaders().get("x-custom-header")); - } - } - - @Test - public void testSendAllFailure() throws Exception { - final TestResponseInterceptor interceptor = new TestResponseInterceptor(); - FirebaseMessagingClient client = initMessagingClientForBatchRequests( - MOCK_BATCH_FAILURE_RESPONSE, interceptor); - List messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE, EMPTY_MESSAGE); - - BatchResponse responses = client.sendAll(messages, DRY_RUN_DISABLED); - - assertBatchResponse(responses, interceptor, 1, 2); - } - - @Test - public void testSendAllHttpError() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent("{}"); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, HTTP_2_ERROR.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}"); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllTransportError() { - FirebaseMessagingClient client = initClientWithFaultyTransport(); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals( - "Unknown error while making a remote service call: transport error", error.getMessage()); - assertTrue(error.getCause() instanceof IOException); - assertNull(error.getHttpResponse()); - assertNull(error.getMessagingErrorCode()); - } - } - - @Test - public void testSendAllErrorWithEmptyResponse() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setZeroContent(); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, HTTP_2_ERROR.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull"); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllErrorWithDetails() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent( - "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllErrorWithCanonicalCode() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent( - "{\"error\": {\"status\": \"NOT_FOUND\", \"message\": \"test error\"}}"); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, ErrorCode.NOT_FOUND, null); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllErrorWithFcmError() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent( - "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\", " - + "\"details\":[{\"@type\": \"type.googleapis.com/google.firebase.fcm" - + ".v1.FcmError\", \"errorCode\": \"UNREGISTERED\"}]}}"); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - MessagingErrorCode.UNREGISTERED); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllErrorWithoutMessage() { - final String responseBody = "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"details\":[{\"@type\": \"type.googleapis.com/google.firebase.fcm" - + ".v1.FcmError\", \"errorCode\": \"UNREGISTERED\"}]}}"; - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent(responseBody); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - MessagingErrorCode.UNREGISTERED, - "Unexpected HTTP response with status: " + code + "\n" + responseBody); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } @Test(expected = IllegalArgumentException.class) public void testBuilderNullProjectId() { @@ -558,30 +368,18 @@ private FirebaseMessagingClientImpl initMessagingClient( .setProjectId("test-project") .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) .setRequestFactory(transport.createRequestFactory()) - .setChildRequestFactory(Utils.getDefaultTransport().createRequestFactory()) + .setChildRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()) .setResponseInterceptor(interceptor) .build(); } - private FirebaseMessagingClientImpl initMessagingClientForBatchRequests( - String responsePayload, TestResponseInterceptor interceptor) { - MockLowLevelHttpResponse httpResponse = getBatchResponse(responsePayload); - return initMessagingClient(httpResponse, interceptor); - } - - private MockLowLevelHttpResponse getBatchResponse(String responsePayload) { - return new MockLowLevelHttpResponse() - .setContentType("multipart/mixed; boundary=test_boundary") - .setContent(responsePayload); - } - private FirebaseMessagingClientImpl initClientWithFaultyTransport() { HttpTransport transport = TestUtils.createFaultyHttpTransport(); return FirebaseMessagingClientImpl.builder() .setProjectId("test-project") .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) .setRequestFactory(transport.createRequestFactory()) - .setChildRequestFactory(Utils.getDefaultTransport().createRequestFactory()) + .setChildRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()) .build(); } @@ -591,6 +389,7 @@ private void checkRequestHeader(HttpRequest request) { HttpHeaders headers = request.getHeaders(); assertEquals("2", headers.get("X-GOOG-API-FORMAT-VERSION")); assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), headers.get("X-Goog-Api-Client")); } private void checkRequest( @@ -603,70 +402,12 @@ private void checkRequest( assertEquals(expected, parsed); } - private void assertBatchResponse( - BatchResponse batchResponse, TestResponseInterceptor interceptor, - int successCount, int failureCount) throws IOException { - - assertEquals(successCount, batchResponse.getSuccessCount()); - assertEquals(failureCount, batchResponse.getFailureCount()); - - List responses = batchResponse.getResponses(); - assertEquals(successCount + failureCount, responses.size()); - for (int i = 0; i < successCount; i++) { - SendResponse sendResponse = responses.get(i); - assertTrue(sendResponse.isSuccessful()); - assertEquals("projects/test-project/messages/" + (i + 1), sendResponse.getMessageId()); - assertNull(sendResponse.getException()); - } - - for (int i = successCount; i < failureCount; i++) { - SendResponse sendResponse = responses.get(i); - assertFalse(sendResponse.isSuccessful()); - assertNull(sendResponse.getMessageId()); - - FirebaseMessagingException exception = sendResponse.getException(); - assertNotNull(exception); - assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); - assertNull(exception.getCause()); - assertNull(exception.getHttpResponse()); - assertEquals(MessagingErrorCode.INVALID_ARGUMENT, exception.getMessagingErrorCode()); - } - - checkBatchRequestHeader(interceptor.getLastRequest()); - checkBatchRequest(interceptor.getLastRequest(), successCount + failureCount); - } - - private void checkBatchRequestHeader(HttpRequest request) { - assertEquals("POST", request.getRequestMethod()); - assertEquals("https://fcm.googleapis.com/batch", request.getUrl().toString()); - } - - private void checkBatchRequest(HttpRequest request, int expectedParts) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - request.getContent().writeTo(out); - String[] lines = out.toString().split("\n"); - assertEquals(expectedParts, countLinesWithPrefix(lines, "POST " + TEST_FCM_URL)); - assertEquals(expectedParts, countLinesWithPrefix(lines, "x-goog-api-format-version: 2")); - assertEquals(expectedParts, countLinesWithPrefix( - lines, "x-firebase-client: fire-admin-java/" + SdkUtils.getVersion())); - } - - private int countLinesWithPrefix(String[] lines, String prefix) { - int matchCount = 0; - for (String line : lines) { - if (line.trim().startsWith(prefix)) { - matchCount++; - } - } - return matchCount; - } - private FirebaseMessagingClientImpl.Builder fullyPopulatedBuilder() { return FirebaseMessagingClientImpl.builder() .setProjectId("test-project") .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(Utils.getDefaultTransport().createRequestFactory()) - .setChildRequestFactory(Utils.getDefaultTransport().createRequestFactory()); + .setRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()) + .setChildRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()); } private void checkExceptionFromHttpResponse( diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java index 9ae7bba24..85667f799 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.google.api.client.http.HttpResponseException; import com.google.common.collect.ImmutableList; @@ -63,6 +64,7 @@ public void testSend() throws Exception { .setBody("Body") .build()) .build()) + .setLiveActivityToken("integration-test-live-activity-token") .build()) .setWebpushConfig(WebpushConfig.builder() .putHeader("X-Custom-Val", "Foo") @@ -97,7 +99,29 @@ public void testSendError() throws InterruptedException { } @Test - public void testSendAll() throws Exception { + public void testSendFidError() throws InterruptedException { + FirebaseMessaging messaging = FirebaseMessaging.getInstance(); + Message message = Message.builder() + .setNotification(Notification.builder() + .setTitle("Title") + .setBody("Body") + .build()) + .setFid("not-a-fid") + .build(); + try { + messaging.sendAsync(message, true).get(); + fail("No exception thrown for invalid FID"); + } catch (ExecutionException e) { + FirebaseMessagingException cause = (FirebaseMessagingException) e.getCause(); + assertEquals(ErrorCode.NOT_FOUND, cause.getErrorCode()); + assertEquals(MessagingErrorCode.UNREGISTERED, cause.getMessagingErrorCode()); + assertNotNull(cause.getHttpResponse()); + assertTrue(cause.getCause() instanceof HttpResponseException); + } + } + + @Test + public void testSendEach() throws Exception { List messages = new ArrayList<>(); messages.add( Message.builder() @@ -124,7 +148,7 @@ public void testSendAll() throws Exception { .setToken("not-a-token") .build()); - BatchResponse response = FirebaseMessaging.getInstance().sendAll(messages, true); + BatchResponse response = FirebaseMessaging.getInstance().sendEach(messages, true); assertEquals(2, response.getSuccessCount()); assertEquals(1, response.getFailureCount()); @@ -147,13 +171,13 @@ public void testSendAll() throws Exception { } @Test - public void testSendFiveHundred() throws Exception { + public void testSendFiveHundredWithSendEach() throws Exception { List messages = new ArrayList<>(); for (int i = 0; i < 500; i++) { messages.add(Message.builder().setTopic("foo-bar-" + (i % 10)).build()); } - BatchResponse response = FirebaseMessaging.getInstance().sendAll(messages, true); + BatchResponse response = FirebaseMessaging.getInstance().sendEach(messages, true); assertEquals(500, response.getResponses().size()); assertEquals(500, response.getSuccessCount()); @@ -170,7 +194,7 @@ public void testSendFiveHundred() throws Exception { } @Test - public void testSendMulticast() throws Exception { + public void testSendEachForMulticast() throws Exception { MulticastMessage multicastMessage = MulticastMessage.builder() .setNotification(Notification.builder() .setTitle("Title") @@ -180,7 +204,7 @@ public void testSendMulticast() throws Exception { .addToken("also-not-a-token") .build(); - BatchResponse response = FirebaseMessaging.getInstance().sendMulticast( + BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast( multicastMessage, true); assertEquals(0, response.getSuccessCount()); @@ -193,6 +217,71 @@ public void testSendMulticast() throws Exception { } } + @Test + public void testSendEachForMulticastFidsError() throws Exception { + MulticastMessage multicastMessage = MulticastMessage.builder() + .setNotification(Notification.builder() + .setTitle("Title") + .setBody("Body") + .build()) + .addFid("not-a-fid") + .addFid("also-not-a-fid") + .build(); + + BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast( + multicastMessage, true); + + assertEquals(0, response.getSuccessCount()); + assertEquals(2, response.getFailureCount()); + assertEquals(2, response.getResponses().size()); + for (SendResponse sendResponse : response.getResponses()) { + assertFalse(sendResponse.isSuccessful()); + assertNull(sendResponse.getMessageId()); + assertNotNull(sendResponse.getException()); + assertEquals(ErrorCode.NOT_FOUND, + sendResponse.getException().getErrorCode()); + assertEquals(MessagingErrorCode.UNREGISTERED, + sendResponse.getException().getMessagingErrorCode()); + } + } + + @Test + public void testSendEachForMulticastMixedError() throws Exception { + MulticastMessage multicastMessage = MulticastMessage.builder() + .setNotification(Notification.builder() + .setTitle("Title") + .setBody("Body") + .build()) + .addToken("not-a-token") + .addFid("not-a-fid") + .build(); + + BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast( + multicastMessage, true); + + assertEquals(0, response.getSuccessCount()); + assertEquals(2, response.getFailureCount()); + assertEquals(2, response.getResponses().size()); + + SendResponse response1 = response.getResponses().get(0); + assertFalse(response1.isSuccessful()); + assertNull(response1.getMessageId()); + assertNotNull(response1.getException()); + assertEquals(ErrorCode.INVALID_ARGUMENT, response1.getException().getErrorCode()); + assertEquals( + MessagingErrorCode.INVALID_ARGUMENT, + response1.getException().getMessagingErrorCode()); + + SendResponse response2 = response.getResponses().get(1); + assertFalse(response2.isSuccessful()); + assertNull(response2.getMessageId()); + assertNotNull(response2.getException()); + assertEquals(ErrorCode.NOT_FOUND, response2.getException().getErrorCode()); + assertEquals( + MessagingErrorCode.UNREGISTERED, + response2.getException().getMessagingErrorCode()); + } + @Test public void testSubscribe() throws Exception { FirebaseMessaging messaging = FirebaseMessaging.getInstance(); diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java index 8e8e79e07..42a499b75 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package com.google.firebase.messaging; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -27,13 +30,19 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.internal.Nullable; + +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; + import org.junit.After; import org.junit.Test; @@ -46,6 +55,9 @@ public class FirebaseMessagingTest { private static final Message EMPTY_MESSAGE = Message.builder() .setTopic("test-topic") .build(); + private static final Message EMPTY_MESSAGE_2 = Message.builder() + .setTopic("test-topic2") + .build(); private static final MulticastMessage TEST_MULTICAST_MESSAGE = MulticastMessage.builder() .addToken("test-fcm-token1") .addToken("test-fcm-token2") @@ -263,37 +275,37 @@ public void testSendAsyncFailure() throws InterruptedException { } @Test - public void testSendAllWithNull() throws FirebaseMessagingException { + public void testSendEachWithNull() throws FirebaseMessagingException { MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); try { - messaging.sendAll(null); + messaging.sendEach(null); fail("No error thrown for null message list"); } catch (NullPointerException expected) { // expected } - assertNull(client.lastBatch); + assertNull(client.lastMessage); } @Test - public void testSendAllWithEmptyList() throws FirebaseMessagingException { + public void testSendEachWithEmptyList() throws FirebaseMessagingException { MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); try { - messaging.sendAll(ImmutableList.of()); + messaging.sendEach(ImmutableList.of()); fail("No error thrown for empty message list"); } catch (IllegalArgumentException expected) { // expected } - assertNull(client.lastBatch); + assertNull(client.lastMessage); } @Test - public void testSendAllWithTooManyMessages() throws FirebaseMessagingException { + public void testSendEachWithTooManyMessages() throws FirebaseMessagingException { MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); ImmutableList.Builder listBuilder = ImmutableList.builder(); @@ -302,217 +314,234 @@ public void testSendAllWithTooManyMessages() throws FirebaseMessagingException { } try { - messaging.sendAll(listBuilder.build(), false); + messaging.sendEach(listBuilder.build(), false); fail("No error thrown for too many messages in the list"); } catch (IllegalArgumentException expected) { // expected } - assertNull(client.lastBatch); + assertNull(client.lastMessage); } @Test - public void testSendAll() throws FirebaseMessagingException { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); + public void testSendEach() throws FirebaseMessagingException { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + ImmutableList messageIds = ImmutableList.of("test1", "test2"); + Map messageMap = new HashMap<>(); + for (int i = 0; i < 2; i++) { + messageMap.put(messages.get(i), SendResponse.fromMessageId(messageIds.get(i))); + } + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - BatchResponse response = messaging.sendAll(messages); + BatchResponse response = messaging.sendEach(messages); - assertSame(batchResponse, response); - assertSame(messages, client.lastBatch); + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(messageIds.get(i), response.getResponses().get(i).getMessageId()); + } + assertThat(client.lastMessage, anyOf(is(EMPTY_MESSAGE), is(EMPTY_MESSAGE_2))); assertFalse(client.isLastDryRun); } @Test - public void testSendAllDryRun() throws FirebaseMessagingException { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); + public void testSendEachDryRun() throws FirebaseMessagingException { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + ImmutableList messageIds = ImmutableList.of("test1", "test2"); + Map messageMap = new HashMap<>(); + for (int i = 0; i < 2; i++) { + messageMap.put(messages.get(i), SendResponse.fromMessageId(messageIds.get(i))); + } + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - BatchResponse response = messaging.sendAll(messages, true); + BatchResponse response = messaging.sendEach(messages, true); - assertSame(batchResponse, response); - assertSame(messages, client.lastBatch); + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(messageIds.get(i), response.getResponses().get(i).getMessageId()); + } + assertThat(client.lastMessage, anyOf(is(EMPTY_MESSAGE), is(EMPTY_MESSAGE_2))); assertTrue(client.isLastDryRun); } @Test - public void testSendAllFailure() { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); + public void testSendEachFailure() throws FirebaseMessagingException { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + Map messageMap = new HashMap<>(); + messageMap.put(messages.get(0), SendResponse.fromMessageId("test")); + messageMap.put(messages.get(1), SendResponse.fromException(TEST_EXCEPTION)); + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - try { - messaging.sendAll(messages); - } catch (FirebaseMessagingException e) { - assertSame(TEST_EXCEPTION, e); - } + BatchResponse response = messaging.sendEach(messages); - assertSame(messages, client.lastBatch); + assertEquals(1, response.getFailureCount()); + assertEquals(1, response.getSuccessCount()); + assertEquals("test", response.getResponses().get(0).getMessageId()); + assertEquals(TEST_EXCEPTION, response.getResponses().get(1).getException()); assertFalse(client.isLastDryRun); } @Test - public void testSendAllAsync() throws Exception { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); + public void testSendEachAsync() throws Exception { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + ImmutableList messageIds = ImmutableList.of("test1", "test2"); + Map messageMap = new HashMap<>(); + for (int i = 0; i < 2; i++) { + messageMap.put(messages.get(i), SendResponse.fromMessageId(messageIds.get(i))); + } + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - BatchResponse response = messaging.sendAllAsync(messages).get(); + BatchResponse response = messaging.sendEachAsync(messages).get(); - assertSame(batchResponse, response); - assertSame(messages, client.lastBatch); + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(messageIds.get(i), response.getResponses().get(i).getMessageId()); + } + assertThat(client.lastMessage, anyOf(is(EMPTY_MESSAGE), is(EMPTY_MESSAGE_2))); assertFalse(client.isLastDryRun); } @Test - public void testSendAllAsyncDryRun() throws Exception { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); + public void testSendEachAsyncDryRun() throws Exception { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + ImmutableList messageIds = ImmutableList.of("test1", "test2"); + Map messageMap = new HashMap<>(); + for (int i = 0; i < 2; i++) { + messageMap.put(messages.get(i), SendResponse.fromMessageId(messageIds.get(i))); + } + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - BatchResponse response = messaging.sendAllAsync(messages, true).get(); + BatchResponse response = messaging.sendEachAsync(messages, true).get(); - assertSame(batchResponse, response); - assertSame(messages, client.lastBatch); + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(messageIds.get(i), response.getResponses().get(i).getMessageId()); + } + assertThat(client.lastMessage, anyOf(is(EMPTY_MESSAGE), is(EMPTY_MESSAGE_2))); assertTrue(client.isLastDryRun); } @Test - public void testSendAllAsyncFailure() throws InterruptedException { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); + public void testSendEachAsyncFailure() throws Exception { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + Map messageMap = new HashMap<>(); + messageMap.put(messages.get(0), SendResponse.fromMessageId("test")); + messageMap.put(messages.get(1), SendResponse.fromException(TEST_EXCEPTION)); + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - try { - messaging.sendAllAsync(messages).get(); - } catch (ExecutionException e) { - assertSame(TEST_EXCEPTION, e.getCause()); - } + BatchResponse response = messaging.sendEachAsync(messages).get(); - assertSame(messages, client.lastBatch); + assertEquals(1, response.getFailureCount()); + assertEquals(1, response.getSuccessCount()); + assertEquals("test", response.getResponses().get(0).getMessageId()); + assertEquals(TEST_EXCEPTION, response.getResponses().get(1).getException()); assertFalse(client.isLastDryRun); } @Test - public void testSendMulticastWithNull() throws FirebaseMessagingException { + public void testSendEachForMulticastWithNull() throws FirebaseMessagingException { MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); try { - messaging.sendMulticast(null); + messaging.sendEachForMulticast(null); fail("No error thrown for null multicast message"); } catch (NullPointerException expected) { // expected } - assertNull(client.lastBatch); + assertNull(client.lastMessage); } @Test - public void testSendMulticast() throws FirebaseMessagingException { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); + public void testSendEachForMulticast() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId("test"); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - BatchResponse response = messaging.sendMulticast(TEST_MULTICAST_MESSAGE); + BatchResponse response = messaging.sendEachForMulticast(TEST_MULTICAST_MESSAGE); - assertSame(batchResponse, response); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals("test", response.getResponses().get(i).getMessageId()); + } assertFalse(client.isLastDryRun); } @Test - public void testSendMulticastDryRun() throws FirebaseMessagingException { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); + public void testSendEachForMulticastDryRun() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId("test"); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - BatchResponse response = messaging.sendMulticast(TEST_MULTICAST_MESSAGE, true); + BatchResponse response = messaging.sendEachForMulticast(TEST_MULTICAST_MESSAGE, true); - assertSame(batchResponse, response); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals("test", response.getResponses().get(i).getMessageId()); + } assertTrue(client.isLastDryRun); } @Test - public void testSendMulticastFailure() { + public void testSendEachForMulticastFailure() throws FirebaseMessagingException { MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - try { - messaging.sendMulticast(TEST_MULTICAST_MESSAGE); - } catch (FirebaseMessagingException e) { - assertSame(TEST_EXCEPTION, e); - } + BatchResponse response = messaging.sendEachForMulticast(TEST_MULTICAST_MESSAGE); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); + assertEquals(2, response.getFailureCount()); + assertEquals(0, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(TEST_EXCEPTION, response.getResponses().get(i).getException()); + } assertFalse(client.isLastDryRun); } @Test - public void testSendMulticastAsync() throws Exception { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); + public void testSendEachForMulticastAsync() throws Exception { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId("test"); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - BatchResponse response = messaging.sendMulticastAsync(TEST_MULTICAST_MESSAGE).get(); + BatchResponse response = messaging.sendEachForMulticastAsync(TEST_MULTICAST_MESSAGE).get(); - assertSame(batchResponse, response); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals("test", response.getResponses().get(i).getMessageId()); + } assertFalse(client.isLastDryRun); } @Test - public void testSendMulticastAsyncDryRun() throws Exception { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); + public void testSendEachForMulticastAsyncDryRun() throws Exception { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId("test"); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - BatchResponse response = messaging.sendMulticastAsync(TEST_MULTICAST_MESSAGE, true).get(); + BatchResponse response = messaging.sendEachForMulticastAsync( + TEST_MULTICAST_MESSAGE, true).get(); - assertSame(batchResponse, response); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals("test", response.getResponses().get(i).getMessageId()); + } assertTrue(client.isLastDryRun); } @Test - public void testSendMulticastAsyncFailure() throws InterruptedException { + public void testSendEachForMulticastAsyncFailure() throws Exception { MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - try { - messaging.sendMulticastAsync(TEST_MULTICAST_MESSAGE).get(); - } catch (ExecutionException e) { - assertSame(TEST_EXCEPTION, e.getCause()); - } + BatchResponse response = messaging.sendEachForMulticastAsync(TEST_MULTICAST_MESSAGE).get(); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); + assertEquals(2, response.getFailureCount()); + assertEquals(0, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(TEST_EXCEPTION, response.getResponses().get(i).getException()); + } assertFalse(client.isLastDryRun); } @@ -664,14 +693,6 @@ private FirebaseMessaging getMessagingForTopicManagement( .build(); } - private BatchResponse getBatchResponse(String ...messageIds) { - ImmutableList.Builder listBuilder = ImmutableList.builder(); - for (String messageId : messageIds) { - listBuilder.add(SendResponse.fromMessageId(messageId)); - } - return new BatchResponseImpl(listBuilder.build()); - } - private static class MockFirebaseMessagingClient implements FirebaseMessagingClient { private String messageId; @@ -679,8 +700,8 @@ private static class MockFirebaseMessagingClient implements FirebaseMessagingCli private FirebaseMessagingException exception; private Message lastMessage; - private List lastBatch; private boolean isLastDryRun; + private ImmutableMap messageMap; private MockFirebaseMessagingClient( String messageId, BatchResponse batchResponse, FirebaseMessagingException exception) { @@ -689,12 +710,18 @@ private MockFirebaseMessagingClient( this.exception = exception; } + private MockFirebaseMessagingClient( + Map messageMap, FirebaseMessagingException exception) { + this.messageMap = ImmutableMap.copyOf(messageMap); + this.exception = exception; + } + static MockFirebaseMessagingClient fromMessageId(String messageId) { return new MockFirebaseMessagingClient(messageId, null, null); } - static MockFirebaseMessagingClient fromBatchResponse(BatchResponse batchResponse) { - return new MockFirebaseMessagingClient(null, batchResponse, null); + static MockFirebaseMessagingClient fromMessageMap(Map messageMap) { + return new MockFirebaseMessagingClient(messageMap, null); } static MockFirebaseMessagingClient fromException(FirebaseMessagingException exception) { @@ -702,23 +729,28 @@ static MockFirebaseMessagingClient fromException(FirebaseMessagingException exce } @Override + @Nullable public String send(Message message, boolean dryRun) throws FirebaseMessagingException { lastMessage = message; isLastDryRun = dryRun; if (exception != null) { throw exception; } - return messageId; + if (messageMap == null) { + return messageId; + } + if (!messageMap.containsKey(message)) { + return null; + } + if (messageMap.get(message).getException() != null) { + throw messageMap.get(message).getException(); + } + return messageMap.get(message).getMessageId(); } @Override public BatchResponse sendAll( List messages, boolean dryRun) throws FirebaseMessagingException { - lastBatch = messages; - isLastDryRun = dryRun; - if (exception != null) { - throw exception; - } return batchResponse; } } diff --git a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java index e7222ccb5..39c57a181 100644 --- a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; import java.io.ByteArrayOutputStream; @@ -358,7 +359,7 @@ public void testRequestFactoryIsNull() { @Test(expected = NullPointerException.class) public void testJsonFactoryIsNull() { - new InstanceIdClientImpl(Utils.getDefaultTransport().createRequestFactory(), null); + new InstanceIdClientImpl(ApiClientUtils.getDefaultTransport().createRequestFactory(), null); } @Test @@ -403,7 +404,30 @@ public void testTopicManagementResponseWithEmptyList() { @Test public void testTopicManagementResponseErrorToString() { - GenericJson json = new GenericJson().set("error", "test error"); + GenericJson json = new GenericJson().set("error", "INVALID_ARGUMENT"); + ImmutableList jsonList = ImmutableList.of(json); + + TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); + + String expected = "[Error{index=0, reason=invalid-argument}]"; + assertEquals(expected, topicManagementResponse.getErrors().toString()); + } + + @Test + public void testTopicManagementResponseErrorNotInErrorCodes() { + String myError = "MY_ERROR"; + GenericJson json = new GenericJson().set("error", myError); + ImmutableList jsonList = ImmutableList.of(json); + + TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); + + String expected = "[Error{index=0, reason=my-error}]"; + assertEquals(expected, topicManagementResponse.getErrors().toString()); + } + + @Test + public void testTopicManagementResponseErrorUnknown() { + GenericJson json = new GenericJson().set("error", ""); ImmutableList jsonList = ImmutableList.of(json); TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); @@ -412,6 +436,29 @@ public void testTopicManagementResponseErrorToString() { assertEquals(expected, topicManagementResponse.getErrors().toString()); } + @Test + public void testTopicManagementResponseErrorResourceExhausted() { + GenericJson json = new GenericJson().set("error", "RESOURCE_EXHAUSTED"); + ImmutableList jsonList = ImmutableList.of(json); + + TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); + + String expected = "[Error{index=0, reason=resource-exhausted}]"; + assertEquals(expected, topicManagementResponse.getErrors().toString()); + } + + @Test + public void testTopicManagementResponseErrorTooManyTopics() { + GenericJson json = new GenericJson().set("error", "TOO_MANY_TOPICS"); + ImmutableList jsonList = ImmutableList.of(json); + + TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); + + String expected = "[Error{index=0, reason=too-many-topics}]"; + assertEquals(expected, topicManagementResponse.getErrors().toString()); + } + + private static InstanceIdClientImpl initInstanceIdClient( final MockLowLevelHttpResponse mockResponse, final HttpResponseInterceptor interceptor) { @@ -431,7 +478,7 @@ private void checkTopicManagementRequest( assertEquals(1, result.getFailureCount()); assertEquals(1, result.getErrors().size()); assertEquals(1, result.getErrors().get(0).getIndex()); - assertEquals("unknown-error", result.getErrors().get(0).getReason()); + assertEquals("error-reason", result.getErrors().get(0).getReason()); ByteArrayOutputStream out = new ByteArrayOutputStream(); request.getContent().writeTo(out); @@ -447,6 +494,8 @@ private void checkTopicManagementRequestHeader( HttpRequest request, String expectedUrl) { assertEquals("POST", request.getRequestMethod()); assertEquals(expectedUrl, request.getUrl().toString()); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); + } private void checkExceptionFromHttpResponse( diff --git a/src/test/java/com/google/firebase/messaging/MessageTest.java b/src/test/java/com/google/firebase/messaging/MessageTest.java index 992644b35..0bca10df1 100644 --- a/src/test/java/com/google/firebase/messaging/MessageTest.java +++ b/src/test/java/com/google/firebase/messaging/MessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -20,7 +20,6 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; -import com.google.api.client.googleapis.util.Utils; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonParser; import com.google.common.collect.ImmutableList; @@ -55,6 +54,28 @@ public void testEmptyMessage() throws IOException { Message.builder().setCondition("'foo' in topics").build()); assertJsonEquals(ImmutableMap.of("token", "test-token"), Message.builder().setToken("test-token").build()); + assertJsonEquals(ImmutableMap.of("fid", "test-fid"), + Message.builder().setFid("test-fid").build()); + } + + @Test + public void testMultipleTargets() { + List builders = ImmutableList.of( + Message.builder().setToken("token").setFid("fid"), + Message.builder().setToken("token").setTopic("topic"), + Message.builder().setFid("fid").setCondition("cond"), + Message.builder().setTopic("topic").setCondition("cond"), + Message.builder().setToken("token").setFid("fid").setTopic("topic").setCondition("cond") + ); + for (int i = 0; i < builders.size(); i++) { + try { + builders.get(i).build(); + fail("No error thrown for multiple targets: " + i); + } catch (IllegalArgumentException expected) { + assertEquals("Exactly one of token, fid, topic or condition must be specified", + expected.getMessage()); + } + } } @Test @@ -224,6 +245,52 @@ public void testAndroidMessageWithDirectBootOk() throws IOException { assertJsonEquals(ImmutableMap.of("topic", "test-topic", "android", data), message); } + @Test + public void testAndroidMessageWithBandwidthConstrainedOk() throws IOException { + Message message = Message.builder() + .setAndroidConfig(AndroidConfig.builder() + .setBandwidthConstrainedOk(true) + .setNotification(AndroidNotification.builder() + .setTitle("android-title") + .setBody("android-body") + .build()) + .build()) + .setTopic("test-topic") + .build(); + Map notification = ImmutableMap.builder() + .put("title", "android-title") + .put("body", "android-body") + .build(); + Map data = ImmutableMap.of( + "bandwidth_constrained_ok", true, + "notification", notification + ); + assertJsonEquals(ImmutableMap.of("topic", "test-topic", "android", data), message); + } + + @Test + public void testAndroidMessageWithRestrictedSatelliteOk() throws IOException { + Message message = Message.builder() + .setAndroidConfig(AndroidConfig.builder() + .setRestrictedSatelliteOk(true) + .setNotification(AndroidNotification.builder() + .setTitle("android-title") + .setBody("android-body") + .build()) + .build()) + .setTopic("test-topic") + .build(); + Map notification = ImmutableMap.builder() + .put("title", "android-title") + .put("body", "android-body") + .build(); + Map data = ImmutableMap.of( + "restricted_satellite_ok", true, + "notification", notification + ); + assertJsonEquals(ImmutableMap.of("topic", "test-topic", "android", data), message); + } + @Test(expected = IllegalArgumentException.class) public void testAndroidNotificationWithNegativeCount() throws IllegalArgumentException { AndroidNotification.builder().setNotificationCount(-1).build(); @@ -411,6 +478,7 @@ public void testApnsMessageWithPayload() throws IOException { .putCustomData("cd1", "cd-v1") .putAllCustomData(ImmutableMap.of("cd2", "cd-v2", "cd3", true)) .setAps(Aps.builder().build()) + .setLiveActivityToken("test-live-activity-token") .build()) .setTopic("test-topic") .build(); @@ -421,10 +489,11 @@ public void testApnsMessageWithPayload() throws IOException { .put("cd3", true) .put("aps", ImmutableMap.of()) .build(); - Map data = ImmutableMap.of( - "headers", ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3"), - "payload", payload - ); + Map data = ImmutableMap.builder() + .put("headers", ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3")) + .put("payload", payload) + .put("live_activity_token", "test-live-activity-token") + .build(); assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", data), message); } @@ -442,6 +511,7 @@ public void testApnsMessageWithPayloadAndAps() throws IOException { .setSound("test-sound") .setThreadId("test-thread-id") .build()) + .setLiveActivityToken("test-live-activity-token-aps") .build()) .setTopic("test-topic") .build(); @@ -459,7 +529,10 @@ public void testApnsMessageWithPayloadAndAps() throws IOException { assertJsonEquals( ImmutableMap.of( "topic", "test-topic", - "apns", ImmutableMap.of("payload", payload)), + "apns", ImmutableMap.builder() + .put("payload", payload) + .put("live_activity_token", "test-live-activity-token-aps") + .build()), message); message = Message.builder() @@ -825,6 +898,7 @@ public void testImageInApnsNotification() throws IOException { .setApnsConfig( ApnsConfig.builder().setAps(Aps.builder().build()) .setFcmOptions(ApnsFcmOptions.builder().setImage(TEST_IMAGE_URL_APNS).build()) + .setLiveActivityToken("test-live-activity-token-image") .build()).build(); ImmutableMap notification = @@ -837,6 +911,7 @@ public void testImageInApnsNotification() throws IOException { ImmutableMap.builder() .put("fcm_options", ImmutableMap.of("image", TEST_IMAGE_URL_APNS)) .put("payload", ImmutableMap.of("aps", ImmutableMap.of())) + .put("live_activity_token", "test-live-activity-token-image") .build(); ImmutableMap expected = ImmutableMap.builder() @@ -847,6 +922,34 @@ public void testImageInApnsNotification() throws IOException { assertJsonEquals(expected, message); } + @Test + public void testApnsMessageWithOnlyLiveActivityToken() throws IOException { + Message message = Message.builder() + .setApnsConfig(ApnsConfig.builder() + .setAps(Aps.builder().build()) + .setLiveActivityToken("only-live-activity") + .build()) + .setTopic("test-topic") + .build(); + Map expectedApns = ImmutableMap.builder() + .put("live_activity_token", "only-live-activity") + .put("payload", ImmutableMap.of("aps", ImmutableMap.of())) + .build(); + assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", expectedApns), message); + + // Test without live activity token + message = Message.builder() + .setApnsConfig(ApnsConfig.builder() + .setAps(Aps.builder().build()) + .build()) + .setTopic("test-topic") + .build(); + expectedApns = ImmutableMap.builder() + .put("payload", ImmutableMap.of("aps", ImmutableMap.of())) + .build(); + assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", expectedApns), message); + } + @Test public void testInvalidColorInAndroidNotificationLightSettings() { try { @@ -890,6 +993,7 @@ public void testExtendedAndroidNotificationParameters() throws IOException { .setDefaultLightSettings(false) .setVisibility(AndroidNotification.Visibility.PUBLIC) .setNotificationCount(10) + .setProxy(AndroidNotification.Proxy.DENY) .build()) .build()) .setTopic("test-topic") @@ -923,6 +1027,7 @@ public void testExtendedAndroidNotificationParameters() throws IOException { .put("default_light_settings", false) .put("visibility", "public") .put("notification_count", new BigDecimal(10)) + .put("proxy", "DENY") .build()) .build(); assertJsonEquals(ImmutableMap.of( @@ -930,7 +1035,7 @@ public void testExtendedAndroidNotificationParameters() throws IOException { } private static void assertJsonEquals( - Map expected, Object actual) throws IOException { + Map expected, Object actual) throws IOException { assertEquals(expected, toMap(actual)); } diff --git a/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java b/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java index f26d0816f..481ccccc9 100644 --- a/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java +++ b/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,6 +86,21 @@ public void testTooManyTokens() { } } + @Test + public void testTooManyFids() { + MulticastMessage.Builder builder = MulticastMessage.builder(); + for (int i = 0; i < 501; i++) { + builder.addFid("fid" + i); + } + try { + builder.build(); + fail("No error thrown for more than 500 fids"); + } catch (IllegalArgumentException expected) { + assertEquals("no more than 500 tokens and fids combined can be specified", + expected.getMessage()); + } + } + @Test(expected = NullPointerException.class) public void testNullToken() { MulticastMessage.builder().addToken(null).build(); @@ -96,6 +111,92 @@ public void testEmptyToken() { MulticastMessage.builder().addToken("").build(); } + @Test + public void testMulticastMessageFids() { + MulticastMessage multicastMessage = MulticastMessage.builder() + .setAndroidConfig(ANDROID) + .setApnsConfig(APNS) + .setWebpushConfig(WEBPUSH) + .setNotification(NOTIFICATION) + .setFcmOptions(FCM_OPTIONS) + .putData("key1", "value1") + .putAllData(ImmutableMap.of("key2", "value2")) + .addFid("fid1") + .addAllFids(ImmutableList.of("fid2", "fid3")) + .build(); + + List messages = multicastMessage.getMessageList(); + + assertEquals(3, messages.size()); + for (int i = 0; i < 3; i++) { + Message message = messages.get(i); + assertMessageFid(message, "fid" + (i + 1)); + } + } + + @Test + public void testMulticastMessageMixed() { + MulticastMessage multicastMessage = MulticastMessage.builder() + .setAndroidConfig(ANDROID) + .setApnsConfig(APNS) + .setWebpushConfig(WEBPUSH) + .setNotification(NOTIFICATION) + .setFcmOptions(FCM_OPTIONS) + .putData("key1", "value1") + .putAllData(ImmutableMap.of("key2", "value2")) + .addToken("token1") + .addFid("fid1") + .addToken("token2") + .addFid("fid2") + .build(); + + List messages = multicastMessage.getMessageList(); + + assertEquals(4, messages.size()); + assertMessage(messages.get(0), "token1"); + assertMessage(messages.get(1), "token2"); + assertMessageFid(messages.get(2), "fid1"); + assertMessageFid(messages.get(3), "fid2"); + } + + @Test + public void testTooManyTargetsCombined() { + MulticastMessage.Builder builder = MulticastMessage.builder(); + for (int i = 0; i < 250; i++) { + builder.addToken("token" + i); + } + for (int i = 0; i < 251; i++) { + builder.addFid("fid" + i); + } + try { + builder.build(); + fail("No error thrown for more than 500 combined targets"); + } catch (IllegalArgumentException expected) { + assertEquals("no more than 500 tokens and fids combined can be specified", + expected.getMessage()); + } + } + + @Test(expected = NullPointerException.class) + public void testNullFid() { + MulticastMessage.builder().addFid(null).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmptyFid() { + MulticastMessage.builder().addFid("").build(); + } + + private void assertMessageFid(Message message, String expectedFid) { + assertSame(ANDROID, message.getAndroidConfig()); + assertSame(APNS, message.getApnsConfig()); + assertSame(WEBPUSH, message.getWebpushConfig()); + assertSame(NOTIFICATION, message.getNotification()); + assertSame(FCM_OPTIONS, message.getFcmOptions()); + assertEquals(ImmutableMap.of("key1", "value1", "key2", "value2"), message.getData()); + assertEquals(expectedFid, message.getFid()); + } + private void assertMessage(Message message, String expectedToken) { assertSame(ANDROID, message.getAndroidConfig()); assertSame(APNS, message.getApnsConfig()); diff --git a/src/test/java/com/google/firebase/messaging/SendResponseTest.java b/src/test/java/com/google/firebase/messaging/SendResponseTest.java index d90f1e6b6..eac9e4c81 100644 --- a/src/test/java/com/google/firebase/messaging/SendResponseTest.java +++ b/src/test/java/com/google/firebase/messaging/SendResponseTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationErrorCodeTest.java b/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationErrorCodeTest.java new file mode 100644 index 000000000..c9f02ac23 --- /dev/null +++ b/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationErrorCodeTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2026 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.phonenumberverification; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +public class FirebasePhoneNumberVerificationErrorCodeTest { + @Test + public void testEnum() { + assertNotNull(FirebasePhoneNumberVerificationErrorCode.valueOf("INVALID_ARGUMENT")); + assertNotNull(FirebasePhoneNumberVerificationErrorCode.valueOf("INVALID_TOKEN")); + assertNotNull(FirebasePhoneNumberVerificationErrorCode.valueOf("TOKEN_EXPIRED")); + assertNotNull(FirebasePhoneNumberVerificationErrorCode.valueOf("INTERNAL_ERROR")); + assertNotNull(FirebasePhoneNumberVerificationErrorCode.valueOf("SERVICE_ERROR")); + } +} diff --git a/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationTest.java b/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationTest.java new file mode 100644 index 000000000..e0a2a4f85 --- /dev/null +++ b/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 2026 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.phonenumberverification; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.TestOnlyImplFirebaseTrampolines; +import com.google.firebase.internal.FirebaseProcessEnvironment; +import com.google.firebase.phonenumberverification.internal.FirebasePhoneNumberVerificationTokenVerifier; +import com.google.firebase.testing.ServiceAccount; +import com.google.firebase.testing.TestUtils; +import java.lang.reflect.Field; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class FirebasePhoneNumberVerificationTest { + private static final FirebaseOptions firebaseOptions = FirebaseOptions.builder() + .setCredentials(TestUtils.getCertCredential(ServiceAccount.OWNER.asStream())) + .build(); + + @Mock + private FirebasePhoneNumberVerificationTokenVerifier mockVerifier; + + private FirebasePhoneNumberVerification firebasePhoneNumberVerification; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + + FirebaseApp.initializeApp(firebaseOptions); + firebasePhoneNumberVerification = FirebasePhoneNumberVerification.getInstance(); + + Field verifierField = FirebasePhoneNumberVerification.class.getDeclaredField("tokenVerifier"); + verifierField.setAccessible(true); + verifierField.set(firebasePhoneNumberVerification, mockVerifier); + } + + @After + public void tearDown() { + FirebaseProcessEnvironment.clearCache(); + TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); + } + + @Test + public void testGetInstance() { + FirebasePhoneNumberVerification instance = FirebasePhoneNumberVerification.getInstance(); + assertNotNull(instance); + assertSame(instance, FirebasePhoneNumberVerification.getInstance()); + } + + @Test + public void testGetInstanceForApp() { + FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testGetInstanceForApp"); + FirebasePhoneNumberVerification instance = FirebasePhoneNumberVerification.getInstance(app); + assertNotNull(instance); + assertSame(instance, FirebasePhoneNumberVerification.getInstance(app)); + } + + @Test + public void testVerifyToken_DelegatesToVerifier() + throws FirebasePhoneNumberVerificationException { + String testToken = "test.token"; + FirebasePhoneNumberVerificationToken expectedToken = + mock(FirebasePhoneNumberVerificationToken.class); + + when(mockVerifier.verifyToken(testToken)).thenReturn(expectedToken); + + FirebasePhoneNumberVerificationToken result = + firebasePhoneNumberVerification.verifyToken(testToken); + + assertEquals(expectedToken, result); + verify(mockVerifier, times(1)).verifyToken(testToken); + } + + @Test + public void testVerifyToken_PropagatesException() + throws FirebasePhoneNumberVerificationException { + String testToken = "bad.token"; + FirebasePhoneNumberVerificationException error = + new FirebasePhoneNumberVerificationException( + ErrorCode.UNAUTHENTICATED, + "Bad token", + null, + null, + FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN + ); + + when(mockVerifier.verifyToken(testToken)).thenThrow(error); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + FirebasePhoneNumberVerification.getInstance().verifyToken(testToken) + ); + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + e.getPhoneNumberVerificationErrorCode()); + } + + @Test + public void testVerifyToken_PropagatesException_Service_Error() + throws FirebasePhoneNumberVerificationException { + String testToken = "SERVICE_ERROR"; + FirebasePhoneNumberVerificationException error = + new FirebasePhoneNumberVerificationException( + ErrorCode.UNAVAILABLE, + "SERVICE_ERROR", + null, + null, + FirebasePhoneNumberVerificationErrorCode.SERVICE_ERROR + ); + + when(mockVerifier.verifyToken(testToken)).thenThrow(error); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + FirebasePhoneNumberVerification.getInstance().verifyToken(testToken) + ); + assertEquals(FirebasePhoneNumberVerificationErrorCode.SERVICE_ERROR, + e.getPhoneNumberVerificationErrorCode()); + } + + @Test + public void testVerifyToken_PropagatesException_Internal_Error() + throws FirebasePhoneNumberVerificationException { + String testToken = "INTERNAL"; + FirebasePhoneNumberVerificationException error = + new FirebasePhoneNumberVerificationException( + ErrorCode.INTERNAL, + "INTERNAL", + null, + null, + null + ); + + when(mockVerifier.verifyToken(testToken)).thenThrow(error); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + FirebasePhoneNumberVerification.getInstance().verifyToken(testToken) + ); + assertNull(e.getPhoneNumberVerificationErrorCode()); + } +} diff --git a/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationTokenTest.java b/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationTokenTest.java new file mode 100644 index 000000000..3b6ad77b8 --- /dev/null +++ b/src/test/java/com/google/firebase/phonenumberverification/FirebasePhoneNumberVerificationTokenTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2026 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.phonenumberverification; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.TestOnlyImplFirebaseTrampolines; +import com.google.firebase.internal.FirebaseProcessEnvironment; +import com.nimbusds.jwt.JWTClaimsSet; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.junit.After; +import org.junit.Test; + +public class FirebasePhoneNumberVerificationTokenTest { + private static final String PROJECT_ID = "mock-project-id-1"; + private static final String ISSUER = "https://fpnv.googleapis.com/projects/" + PROJECT_ID; + private final String subject = "+15551234567"; + + @After + public void tearDown() { + FirebaseProcessEnvironment.clearCache(); + TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); + } + + @Test + public void test_Audience_Empty() { + JWTClaimsSet claims = new JWTClaimsSet.Builder() + .issuer(ISSUER) + .subject(subject) + .expirationTime(new Date(System.currentTimeMillis() + 10000)) + .build(); + + FirebasePhoneNumberVerificationToken token = + new FirebasePhoneNumberVerificationToken(claims.getClaims()); + + assertNotNull(token); + assertEquals(ImmutableList.of(), token.getAudience()); + } + + @Test + public void test_Audience_List() { + JWTClaimsSet claims = new JWTClaimsSet.Builder() + .issuer(ISSUER) + .subject(subject) + .audience(ImmutableList.of()) + .expirationTime(new Date(System.currentTimeMillis() + 10000)) + .build(); + + FirebasePhoneNumberVerificationToken token = + new FirebasePhoneNumberVerificationToken(claims.getClaims()); + + assertNotNull(token); + assertEquals(ImmutableList.of(), token.getAudience()); + } + + @Test + public void test_Audience_String() { + Map claims = new HashMap<>(); + claims.put("sub", subject); + claims.put("aud", ISSUER); + + FirebasePhoneNumberVerificationToken token = new FirebasePhoneNumberVerificationToken(claims); + + assertNotNull(token); + assertEquals(ImmutableList.of(ISSUER), token.getAudience()); + } + + @Test + public void test_No_Sub() { + Map claims = new HashMap<>(); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> + new FirebasePhoneNumberVerificationToken(claims) + ); + assertTrue(e.getMessage().contains("Claims map must contain sub")); + } + + @Test + public void test_Null_Sub() { + NullPointerException e = assertThrows(NullPointerException.class, () -> + new FirebasePhoneNumberVerificationToken(null) + ); + assertEquals("Claims map must not be null", e.getMessage()); + } +} diff --git a/src/test/java/com/google/firebase/phonenumberverification/internal/FirebasePhoneNumberVerificationTokenVerifierTest.java b/src/test/java/com/google/firebase/phonenumberverification/internal/FirebasePhoneNumberVerificationTokenVerifierTest.java new file mode 100644 index 000000000..fa4ca8116 --- /dev/null +++ b/src/test/java/com/google/firebase/phonenumberverification/internal/FirebasePhoneNumberVerificationTokenVerifierTest.java @@ -0,0 +1,456 @@ +/* + * Copyright 2026 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.phonenumberverification.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.TestOnlyImplFirebaseTrampolines; +import com.google.firebase.internal.FirebaseProcessEnvironment; +import com.google.firebase.phonenumberverification.FirebasePhoneNumberVerificationErrorCode; +import com.google.firebase.phonenumberverification.FirebasePhoneNumberVerificationException; +import com.google.firebase.phonenumberverification.FirebasePhoneNumberVerificationToken; +import com.google.firebase.testing.ServiceAccount; +import com.google.firebase.testing.TestUtils; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.crypto.MACSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.gen.ECKeyGenerator; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import com.nimbusds.jwt.proc.DefaultJWTProcessor; +import com.nimbusds.jwt.proc.ExpiredJWTException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class FirebasePhoneNumberVerificationTokenVerifierTest { + private static final String PROJECT_ID = "mock-project-id-2"; + private static final FirebaseOptions firebaseOptions = FirebaseOptions.builder() + .setProjectId(PROJECT_ID) + .setCredentials(TestUtils.getCertCredential(ServiceAccount.OWNER.asStream())) + .build(); + private static final String ISSUER = "https://fpnv.googleapis.com/projects/" + PROJECT_ID; + private static final String[] AUD = new String[]{ + ISSUER, + "https://google.com/projects/" + }; + + @Mock + private DefaultJWTProcessor mockJwtProcessor; + + private FirebasePhoneNumberVerificationTokenVerifier verifier; + private KeyPair rsaKeyPair; + private ECKey ecKey; + private JWSHeader header; + private JWTClaimsSet claims; + private final String subject = "+15551234567"; + private final Date issueTime = new Date(); + private final Date expirationTime = new Date(System.currentTimeMillis() + 10000); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(2048); + rsaKeyPair = gen.generateKeyPair(); + + ecKey = new ECKeyGenerator(Curve.P_256).keyID("ec-key-id").generate(); + + FirebaseApp firebaseApp = FirebaseApp.initializeApp(firebaseOptions); + verifier = new FirebasePhoneNumberVerificationTokenVerifier(firebaseApp); + + Field processorField = FirebasePhoneNumberVerificationTokenVerifier.class + .getDeclaredField("jwtProcessor"); + processorField.setAccessible(true); + processorField.set(verifier, mockJwtProcessor); + + header = new JWSHeader.Builder(JWSAlgorithm.ES256) + .keyID(ecKey.getKeyID()) + .type(JOSEObjectType.JWT) + .build(); + + claims = new JWTClaimsSet.Builder() + .issuer(ISSUER) + .audience(Arrays.asList(AUD)) + .subject(subject) + .issueTime(issueTime) + .expirationTime(expirationTime) + .build(); + } + + @After + public void tearDown() { + FirebaseProcessEnvironment.clearCache(); + TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); + } + + private String createToken(JWSHeader header, JWTClaimsSet claims) throws Exception { + SignedJWT jwt = new SignedJWT(header, claims); + + if (JWSAlgorithm.RS256.equals(header.getAlgorithm())) { + jwt.sign(new RSASSASigner(rsaKeyPair.getPrivate())); + } else if (JWSAlgorithm.HS256.equals(header.getAlgorithm())) { + jwt.sign(new MACSigner("12345678901234567890123456789012")); + } else if (JWSAlgorithm.ES256.equals(header.getAlgorithm())) { + jwt.sign(new ECDSASigner(ecKey.toECPrivateKey())); + } + + return jwt.serialize(); + } + + @Test + public void testVerifyToken_NullOrEmptyToken() { + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> verifier.verifyToken("")); + assertTrue(e.getMessage().contains( + "Firebase Phone Number Verification token must not be null")); + } + + @Test + public void testVerifyToken_Success() throws Exception { + String tokenString = createToken(header, claims); + + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenReturn(claims); + + FirebasePhoneNumberVerificationToken result = verifier.verifyToken(tokenString); + + assertNotNull(result); + assertEquals(subject, result.getPhoneNumber()); + assertEquals(issueTime.getTime() / 1000L, result.getIssuedAt()); + assertEquals(expirationTime.getTime() / 1000L, result.getExpirationTime()); + assertEquals(Arrays.asList(AUD), result.getAudience()); + assertEquals(ISSUER, result.getIssuer()); + assertEquals(ISSUER, result.getClaims().get("iss")); + } + + @Test + public void testVerifyToken_Header_WrongAlgorithm() throws Exception { + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256).build(); + JWTClaimsSet claims = new JWTClaimsSet.Builder().build(); + + String tokenString = createToken(header, claims); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_ARGUMENT, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains("algorithm")); + } + + @Test + public void testVerifyToken_Header_WrongTyp() throws Exception { + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256) + .keyID(ecKey.getKeyID()) + .type(JOSEObjectType.JOSE) + .build(); + JWTClaimsSet claims = new JWTClaimsSet.Builder().build(); + + String tokenString = createToken(header, claims); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_ARGUMENT, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains("has incorrect 'typ'")); + } + + @Test + public void testVerifyToken_Header_MissingKeyId() throws Exception { + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256).build(); + JWTClaimsSet claims = new JWTClaimsSet.Builder().build(); + + String tokenString = createToken(header, claims); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_ARGUMENT, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains( + "Firebase Phone Number Verification token has no 'kid' claim")); + } + + @Test + public void testVerifyToken_Claims_Null() throws Exception { + JWTClaimsSet noSubClaims = new JWTClaimsSet.Builder().build(); + + String tokenString = createToken(header, noSubClaims); + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenReturn(null); + + NullPointerException e = assertThrows(NullPointerException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertTrue(e.getMessage().contains("JWTClaimsSet claims must not be null")); + } + + @Test + public void testVerifyToken_Claims_NoIssuer() throws Exception { + JWTClaimsSet claims = new JWTClaimsSet.Builder() + .audience(ISSUER) + .expirationTime(new Date(System.currentTimeMillis() + 10000)) + .build(); + + String tokenString = createToken(header, claims); + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenReturn(claims); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_ARGUMENT, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains( + "Firebase Phone Number Verification token has no 'iss' (issuer) claim.")); + } + + @Test + public void testVerifyToken_Claims_Expired() throws Exception { + JWTClaimsSet claims = new JWTClaimsSet.Builder() + .issuer(ISSUER) + .audience(ISSUER) + .subject("+1555") + .expirationTime(new Date(System.currentTimeMillis() + 10000)) + .build(); + + String tokenString = createToken(header, claims); + ExpiredJWTException error = new ExpiredJWTException("Bad token"); + + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenThrow(error); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.TOKEN_EXPIRED, + e.getPhoneNumberVerificationErrorCode()); + } + + @Test + public void testVerifyToken_Claims_WrongAudience() throws Exception { + JWTClaimsSet badClaims = new JWTClaimsSet.Builder() + .issuer(ISSUER) + .audience("https://wrong-audience.com") + .subject(subject) + .expirationTime(new Date(System.currentTimeMillis() + 10000)) + .build(); + + String tokenString = createToken(header, badClaims); + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenReturn(badClaims); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains("Invalid audience.")); + } + + @Test + public void testVerifyToken_Claims_EmptyAudience() throws Exception { + JWTClaimsSet badClaims = new JWTClaimsSet.Builder() + .issuer(ISSUER) + .audience(Collections.emptyList()) + .subject(subject) + .expirationTime(new Date(System.currentTimeMillis() + 10000)) + .build(); + + String tokenString = createToken(header, badClaims); + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenReturn(badClaims); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains("Invalid audience. Expected to contain: ")); + } + + @Test + public void testVerifyToken_Claims_NoSubject() throws Exception { + JWTClaimsSet noSubClaims = new JWTClaimsSet.Builder() + .issuer(ISSUER) + .audience(ISSUER) + .expirationTime(new Date(System.currentTimeMillis() + 10000)) + .build(); + + String tokenString = createToken(header, noSubClaims); + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenReturn(noSubClaims); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains("Token has an empty 'sub' (phone number)")); + } + + @Test + public void testVerifyToken_ParseException() { + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(" ") + ); + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains("Failed to parse JWT token")); + } + + @Test + public void testVerifyToken_BadJOSEException() throws Exception { + String tokenString = createToken(header, claims); + String errorMessage = "BadJOSEException"; + BadJOSEException error = new BadJOSEException(errorMessage); + + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenThrow(error); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains("Firebase Phone Number Verification token is invalid:")); + } + + @Test + public void testVerifyToken_JOSEException() throws Exception { + String tokenString = createToken(header, claims); + String errorMessage = "JOSEException"; + JOSEException error = new JOSEException(errorMessage); + + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenThrow(error); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INTERNAL_ERROR, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains( + "Failed to verify Firebase Phone Number Verification token signature:")); + } + + @Test + public void testVerifierWithoutProjectId() { + FirebaseOptions localFirebaseOptions = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.create(null)) + .build(); + + FirebaseApp firebaseApp = FirebaseApp.initializeApp(localFirebaseOptions, "second"); + + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> + new FirebasePhoneNumberVerificationTokenVerifier(firebaseApp) + ); + + assertEquals("Project ID is required in FirebaseOptions.", e.getMessage()); + } + + @Test + public void testCreateJwtProcessor_HandlesException() throws Exception { + FirebaseApp firebaseApp = FirebaseApp.initializeApp(firebaseOptions, "third"); + FirebasePhoneNumberVerificationTokenVerifier original = + new FirebasePhoneNumberVerificationTokenVerifier(firebaseApp); + FirebasePhoneNumberVerificationTokenVerifier spyClass = spy(original); + + doThrow(new MalformedURLException("Simulated bad URL")) + .when(spyClass).createKeySource(); + + Method method = FirebasePhoneNumberVerificationTokenVerifier.class + .getDeclaredMethod("createJwtProcessor"); + method.setAccessible(true); + + try { + method.invoke(spyClass); + } catch (Exception e) { + Throwable cause = e.getCause(); + assertEquals(IllegalStateException.class, cause.getClass()); + assertEquals("Invalid JWKS URL", cause.getMessage()); + assertTrue(cause.getCause() instanceof MalformedURLException); + } + } + + @Test + public void testVerifyToken_Claims_InvalidIssuerProject() throws Exception { + JWTClaimsSet badIssuerClaims = new JWTClaimsSet.Builder() + .issuer("https://fpnv.googleapis.com/projects/attacker-project-id") + .audience("https://fpnv.googleapis.com/projects/attacker-project-id") + .subject(subject) + .expirationTime(new Date(System.currentTimeMillis() + 10000)) + .build(); + + String tokenString = createToken(header, badIssuerClaims); + when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenReturn(badIssuerClaims); + + FirebasePhoneNumberVerificationException e = + assertThrows(FirebasePhoneNumberVerificationException.class, () -> + verifier.verifyToken(tokenString) + ); + + assertEquals(FirebasePhoneNumberVerificationErrorCode.INVALID_TOKEN, + e.getPhoneNumberVerificationErrorCode()); + assertTrue(e.getMessage().contains("incorrect 'iss' (issuer) claim")); + } +} diff --git a/src/test/java/com/google/firebase/projectmanagement/AndroidAppTest.java b/src/test/java/com/google/firebase/projectmanagement/AndroidAppTest.java index 892fd3dcb..8f83782ea 100644 --- a/src/test/java/com/google/firebase/projectmanagement/AndroidAppTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/AndroidAppTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementIT.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementIT.java index 76da98797..ccba81bed 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementIT.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index 853074e65..971b80369 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -1124,6 +1124,7 @@ private void checkRequestHeader(int index, String expectedUrl, HttpMethod httpMe assertEquals(expectedUrl, request.getUrl().toString()); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); assertEquals(CLIENT_VERSION, request.getHeaders().get("X-Client-Version")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } private void checkRequestPayload(Map expected) throws IOException { diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementTest.java index 30fcc3344..eb17066ed 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/projectmanagement/IosAppTest.java b/src/test/java/com/google/firebase/projectmanagement/IosAppTest.java index 02d50279b..832fe21c8 100644 --- a/src/test/java/com/google/firebase/projectmanagement/IosAppTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/IosAppTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/projectmanagement/ShaCertificateTest.java b/src/test/java/com/google/firebase/projectmanagement/ShaCertificateTest.java index 1b7c9c97b..73da71086 100644 --- a/src/test/java/com/google/firebase/projectmanagement/ShaCertificateTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/ShaCertificateTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java b/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java new file mode 100644 index 000000000..0cd6e4528 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java @@ -0,0 +1,832 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; + +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; + +import org.junit.Test; + +public class ConditionEvaluatorTest { + + private final ConditionEvaluator conditionEvaluator = new ConditionEvaluator(); + + @Test + public void testEvaluateConditionsEmptyOrConditionThrowsException() { + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> createOneOfOrCondition(null)); + assertEquals("List of conditions for OR operation must not be empty.", error.getMessage()); + } + + @Test + public void testEvaluateConditionsEmptyOrAndConditionThrowsException() { + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> createOneOfAndCondition(null)); + assertEquals("List of conditions for AND operation must not be empty.", error.getMessage()); + } + + @Test + public void testEvaluateConditionsOrAndTrueToTrue() { + OneOfCondition oneOfConditionTrue = createOneOfTrueCondition(); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionTrue); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues context = new KeysAndValues.Builder().build(); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsOrAndFalseToFalse() { + OneOfCondition oneOfConditionFalse = createOneOfFalseCondition(); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionFalse); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues context = new KeysAndValues.Builder().build(); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsNonOrTopConditionToTrue() { + OneOfCondition oneOfConditionTrue = createOneOfTrueCondition(); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionTrue); + KeysAndValues context = new KeysAndValues.Builder().build(); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionWithInvalidOperatorToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.UNSPECIFIED, "seed"); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionPercent); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "abc"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionLessOrEqualMaxToTrue() { + OneOfCondition oneOfConditionPercent = createPercentCondition(10_000_0000, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionLessOrEqualMinToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionUndefinedMicroPercentToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(null, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedPercentRange() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(null, null, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedUpperBound() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(0, null, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedLowerBound() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(null, 10_000_0000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsGreaterThanMinToTrue() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.GREATER_THAN, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsGreaterThanMaxToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(10_000_0000, + PercentConditionOperator.GREATER_THAN, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsBetweenMinAndMaxToTrue() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(0, 10_000_0000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsBetweenEqualBoundsToFalse() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(5_000_000, + 5_000_000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsLessOrEqualToApprox() { + OneOfCondition oneOfConditionPerCondition = createPercentCondition(10_000_000, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + // 284 is 3 standard deviations for 100k trials with 10% probability. + int tolerance = 284; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate less than or equal 10% to approx 10% + assertTrue(truthyAssignments >= 10_000 - tolerance); + assertTrue(truthyAssignments <= 10_000 + tolerance); + } + + @Test + public void testEvaluateConditionsBetweenApproximateToTrue() { + // Micropercent range is 40% to 60%. + OneOfCondition oneOfConditionPerCondition = createBetweenPercentCondition(40_000_000, + 60_000_000, "seed"); + // 379 is 3 standard deviations for 100k trials with 20% probability. + int tolerance = 379; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate between 40% to 60% to approx 20% + assertTrue(truthyAssignments >= 20_000 - tolerance); + assertTrue(truthyAssignments <= 20_000 + tolerance); + } + + @Test + public void testEvaluateConditionsInterquartileToFiftyPercent() { + // Micropercent range is 25% to 75%. + OneOfCondition oneOfConditionPerCondition = createBetweenPercentCondition(25_000_000, + 75_000_000, "seed"); + // 474 is 3 standard deviations for 100k trials with 50% probability. + int tolerance = 474; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate between 25% to 75 to approx 50% + assertTrue(truthyAssignments >= 50_000 - tolerance); + assertTrue(truthyAssignments <= 50_000 + tolerance); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessThanToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessThanToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalInvalidValueNumericOperationToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("non-numeric")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_EQUAL, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_EQUAL, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-49.9"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericEqualsToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_EQUAL, ImmutableList.of("50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericEqualsToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_EQUAL, ImmutableList.of("50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.000001"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericNotEqualsToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_NOT_EQUAL, ImmutableList.of("50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericNotEqualsToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_NOT_EQUAL, ImmutableList.of("50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.000001"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_GREATER_EQUAL, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_GREATER_EQUAL, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericgreaterThanToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_GREATER_THAN, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterThanToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_GREATER_THAN, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-49.09"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS, ImmutableList.of("One", "hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS, ImmutableList.of("One", "hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hudred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringDoesNotContainToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_DOES_NOT_CONTAIN, ImmutableList.of("One", "hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hudred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringDoesNotContainToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_DOES_NOT_CONTAIN, ImmutableList.of("One", "hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringExactlyMatchesToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_EXACTLY_MATCHES, ImmutableList.of("hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringExactlyMatchesToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_EXACTLY_MATCHES, ImmutableList.of("hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsRegexToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of(".*hund.*")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsRegexToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of("$hund.*")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two ahundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsInvalidRegexToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of("abc)")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two ahundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.0.1"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanInvalidVersionToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.-20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.0.1"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL, ImmutableList.of("50.0.20.0.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL, ImmutableList.of("50.0.2")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.1.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterThanToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN, ImmutableList.of("50.0.2")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.1"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterThanToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL, ImmutableList.of("50.0.20.1")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_EQUAL, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_EQUAL, ImmutableList.of("50.0.20.1")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticNotEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL, ImmutableList.of("50.0.20.1")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticNotEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + private ServerCondition createCustomSignalServerCondition( + CustomSignalOperator operator, + ImmutableList targetCustomSignalValues) { + CustomSignalCondition condition = new CustomSignalCondition("signal_key", operator, + targetCustomSignalValues); + OneOfCondition oneOfConditionCustomSignal = new OneOfCondition(); + oneOfConditionCustomSignal.setCustomSignal(condition); + return new ServerCondition("signal_key", oneOfConditionCustomSignal); + } + + private int evaluateRandomAssignments(OneOfCondition percentCondition, int numOfAssignments) { + int evalTrueCount = 0; + ServerCondition condition = new ServerCondition("is_enabled", percentCondition); + for (int i = 0; i < numOfAssignments; i++) { + UUID randomizationId = UUID.randomUUID(); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", randomizationId.toString()); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + if (result.get("is_enabled")) { + evalTrueCount++; + } + } + return evalTrueCount; + } + + private OneOfCondition createPercentCondition(Integer microPercent, + PercentConditionOperator operator, String seed) { + PercentCondition percentCondition = new PercentCondition(microPercent, operator, seed); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setPercent(percentCondition); + return oneOfCondition; + } + + private OneOfCondition createBetweenPercentCondition(Integer lowerBound, Integer upperBound, + String seed) { + MicroPercentRange microPercentRange = new MicroPercentRange(lowerBound, upperBound); + PercentCondition percentCondition = new PercentCondition(microPercentRange, + PercentConditionOperator.BETWEEN, seed); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setPercent(percentCondition); + return oneOfCondition; + } + + private OneOfCondition createOneOfOrCondition(OneOfCondition condition) { + OrCondition orCondition = condition != null ? new OrCondition(ImmutableList.of(condition)) + : new OrCondition(ImmutableList.of()); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setOrCondition(orCondition); + return oneOfCondition; + } + + private OneOfCondition createOneOfAndCondition(OneOfCondition condition) { + AndCondition andCondition = condition != null ? new AndCondition(ImmutableList.of(condition)) + : new AndCondition(ImmutableList.of()); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setAndCondition(andCondition); + return oneOfCondition; + } + + private OneOfCondition createOneOfTrueCondition() { + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setTrue(); + return oneOfCondition; + } + + private OneOfCondition createOneOfFalseCondition() { + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setFalse(); + return oneOfCondition; + } +} \ No newline at end of file diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index f2d5c8126..78a5c276a 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -25,7 +25,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import com.google.api.client.googleapis.util.Utils; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpMethods; @@ -48,82 +47,165 @@ import com.google.firebase.remoteconfig.internal.TemplateResponse; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; - +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URLDecoder; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - import org.junit.Before; import org.junit.Test; public class FirebaseRemoteConfigClientImplTest { private static final String TEST_REMOTE_CONFIG_URL = - "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; + private static final String TEST_SERVER_REMOTE_CONFIG_URL = + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/namespaces/firebase-server/serverRemoteConfig"; private static final List HTTP_STATUS_CODES = ImmutableList.of(401, 404, 500); - private static final Map HTTP_STATUS_TO_ERROR_CODE = ImmutableMap.of( + private static final Map HTTP_STATUS_TO_ERROR_CODE = + ImmutableMap.of( 401, ErrorCode.UNAUTHENTICATED, 404, ErrorCode.NOT_FOUND, 500, ErrorCode.INTERNAL); - private static final String MOCK_TEMPLATE_RESPONSE = TestUtils - .loadResource("getRemoteConfig.json"); + private static final String MOCK_TEMPLATE_RESPONSE = + TestUtils.loadResource("getRemoteConfig.json"); + + private static final String MOCK_SERVER_TEMPLATE_RESPONSE = + TestUtils.loadResource("getServerRemoteConfig.json"); - private static final String MOCK_LIST_VERSIONS_RESPONSE = TestUtils - .loadResource("listRemoteConfigVersions.json"); + private static final String MOCK_LIST_VERSIONS_RESPONSE = + TestUtils.loadResource("listRemoteConfigVersions.json"); private static final String TEST_ETAG = "etag-123456789012-1"; - private static final Map EXPECTED_PARAMETERS = ImmutableMap.of( - "welcome_message_text", new Parameter() - .setDefaultValue(ParameterValue.of("welcome to app")) - .setConditionalValues(ImmutableMap.of( - "ios_en", ParameterValue.of("welcome to app en") - )) - .setDescription("text for welcome message!") - .setValueType(ParameterValueType.STRING), - "header_text", new Parameter() - .setDefaultValue(ParameterValue.inAppDefault()) - .setValueType(ParameterValueType.STRING) - ); - - private static final Map EXPECTED_PARAMETER_GROUPS = ImmutableMap.of( - "new menu", new ParameterGroup() - .setDescription("New Menu") - .setParameters(ImmutableMap.of( - "pumpkin_spice_season", new Parameter() - .setDefaultValue(ParameterValue.of("true")) - .setDescription("Whether it's currently pumpkin spice season.") - .setValueType(ParameterValueType.BOOLEAN) - ) - ) - ); - - private static final List EXPECTED_CONDITIONS = ImmutableList.of( + private static final Map EXPECTED_PARAMETERS = + ImmutableMap.of( + "welcome_message_text", + new Parameter() + .setDefaultValue(ParameterValue.of("welcome to app")) + .setConditionalValues( + ImmutableMap.of( + "ios_en", ParameterValue.of("welcome to app en"))) + .setDescription("text for welcome message!") + .setValueType(ParameterValueType.STRING), + "header_text", + new Parameter() + .setDefaultValue(ParameterValue.inAppDefault()) + .setValueType(ParameterValueType.STRING)); + + private static final Map EXPECTED_PARAMETER_GROUPS = + ImmutableMap.of( + "new menu", + new ParameterGroup() + .setDescription("New Menu") + .setParameters( + ImmutableMap.of( + "pumpkin_spice_season", + new Parameter() + .setDefaultValue(ParameterValue.of("true")) + .setDescription("Whether it's currently pumpkin spice season.") + .setValueType(ParameterValueType.BOOLEAN)))); + + private static final List EXPECTED_CONDITIONS = + ImmutableList.of( new Condition("ios_en", "device.os == 'ios' && device.country in ['us', 'uk']") - .setTagColor(TagColor.INDIGO), - new Condition("android_en", - "device.os == 'android' && device.country in ['us', 'uk']") - ); - - private static final Version EXPECTED_VERSION = new Version(new TemplateResponse.VersionResponse() - .setVersionNumber("17") - .setUpdateOrigin("ADMIN_SDK_NODE") - .setUpdateType("INCREMENTAL_UPDATE") - .setUpdateUser(new TemplateResponse.UserResponse() - .setEmail("firebase-user@account.com") - .setName("dev-admin") - .setImageUrl("http://image.jpg")) - .setUpdateTime("2020-11-15T06:57:26.342763941Z") - .setDescription("promo config") - ); - - private static final Template EXPECTED_TEMPLATE = new Template() + .setTagColor(TagColor.INDIGO), + new Condition("android_en", "device.os == 'android' && device.country in ['us', 'uk']")); + + private static final List EXPECTED_SERVER_CONDITIONS = + ImmutableList.of( + new ServerCondition("custom_signal", null) + .setServerCondition( + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator + .NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100"))))))))))), + new ServerCondition("percent", null) + .setServerCondition( + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setPercent( + new PercentCondition( + new MicroPercentRange( + 12000000, 100000000), + PercentConditionOperator.BETWEEN, + "3maarirs9xzs"))))))))), + new ServerCondition("chained_conditions", null) + .setServerCondition( + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator + .NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))), + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "premium users", + CustomSignalOperator + .NUMERIC_GREATER_THAN, + new ArrayList<>( + ImmutableList.of("20")))), + new OneOfCondition() + .setPercent( + new PercentCondition( + new MicroPercentRange( + 25000000, 100000000), + PercentConditionOperator.BETWEEN, + "cla24qoibb61")))))))))); + + private static final Version EXPECTED_VERSION = + new Version( + new TemplateResponse.VersionResponse() + .setVersionNumber("17") + .setUpdateOrigin("ADMIN_SDK_NODE") + .setUpdateType("INCREMENTAL_UPDATE") + .setUpdateUser( + new TemplateResponse.UserResponse() + .setEmail("firebase-user@account.com") + .setName("dev-admin") + .setImageUrl("http://image.jpg")) + .setUpdateTime("2020-11-15T06:57:26.342763941Z") + .setDescription("promo config")); + + private static final Template EXPECTED_TEMPLATE = + new Template() .setETag(TEST_ETAG) .setParameters(EXPECTED_PARAMETERS) .setConditions(EXPECTED_CONDITIONS) @@ -158,16 +240,19 @@ public void testGetTemplate() throws Exception { @Test public void testGetTemplateWithTimestampUpToNanosecondPrecision() throws Exception { - List timestamps = ImmutableList.of( + List timestamps = + ImmutableList.of( "2020-11-15T06:57:26.342Z", "2020-11-15T06:57:26.342763Z", - "2020-11-15T06:57:26.342763941Z" - ); + "2020-11-15T06:57:26.342763941Z"); for (String timestamp : timestamps) { response.addHeader("etag", TEST_ETAG); - String templateResponse = "{\"version\": {" + String templateResponse = + "{\"version\": {" + " \"versionNumber\": \"17\"," - + " \"updateTime\": \"" + timestamp + "\"" + + " \"updateTime\": \"" + + timestamp + + "\"" + " }}"; response.setContent(templateResponse); @@ -221,8 +306,12 @@ public void testGetTemplateHttpError() { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -237,8 +326,8 @@ public void testGetTemplateTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -271,8 +360,12 @@ public void testGetTemplateErrorWithZeroContentResponse() { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -287,8 +380,12 @@ public void testGetTemplateErrorWithMalformedResponse() { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -297,15 +394,17 @@ public void testGetTemplateErrorWithMalformedResponse() { @Test public void testGetTemplateErrorWithDetails() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -314,17 +413,22 @@ public void testGetTemplateErrorWithDetails() { @Test public void testGetTemplateErrorWithRcError() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -339,8 +443,9 @@ public void testGetTemplateAtVersionWithNullString() throws Exception { @Test public void testGetTemplateAtVersionWithInvalidString() throws Exception { - List invalidVersionStrings = ImmutableList - .of("", " ", "abc", "t123", "123t", "t123t", "12t3", "#$*&^", "-123", "+123", "123.4"); + List invalidVersionStrings = + ImmutableList.of( + "", " ", "abc", "t123", "123t", "t123t", "12t3", "#$*&^", "-123", "+123", "123.4"); for (String version : invalidVersionStrings) { try { @@ -407,8 +512,12 @@ public void testGetTemplateAtVersionHttpError() { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -423,8 +532,8 @@ public void testGetTemplateAtVersionTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -457,8 +566,12 @@ public void testGetTemplateAtVersionErrorWithZeroContentResponse() { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -473,8 +586,12 @@ public void testGetTemplateAtVersionErrorWithMalformedResponse() { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -483,15 +600,17 @@ public void testGetTemplateAtVersionErrorWithMalformedResponse() { @Test public void testGetTemplateAtVersionErrorWithDetails() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -500,17 +619,22 @@ public void testGetTemplateAtVersionErrorWithDetails() { @Test public void testGetTemplateAtVersionErrorWithRcError() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -553,7 +677,8 @@ public void testPublishTemplateWithValidTemplateAndForceTrue() throws Exception public void testPublishTemplateWithValidTemplateAndValidateOnlyTrue() throws Exception { response.addHeader("etag", TEST_ETAG); response.setContent(MOCK_TEMPLATE_RESPONSE); - Template expectedTemplate = new Template() + Template expectedTemplate = + new Template() .setETag("etag-123456789012-45") .setParameters(EXPECTED_PARAMETERS) .setConditions(EXPECTED_CONDITIONS) @@ -562,12 +687,13 @@ public void testPublishTemplateWithValidTemplateAndValidateOnlyTrue() throws Exc Template validatedTemplate = client.publishTemplate(expectedTemplate, true, false); - // check if the etag matches the input template's etag and not the etag from the server response + // check if the etag matches the input template's etag and not the etag from the + // server response assertNotEquals(TEST_ETAG, validatedTemplate.getETag()); assertEquals("etag-123456789012-45", validatedTemplate.getETag()); assertEquals(expectedTemplate, validatedTemplate); - checkPutRequestHeader(interceptor.getLastRequest(), "?validateOnly=true", - "etag-123456789012-45"); + checkPutRequestHeader( + interceptor.getLastRequest(), "?validateOnly=true", "etag-123456789012-45"); } @Test @@ -611,8 +737,12 @@ public void testPublishTemplateHttpError() { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -627,8 +757,8 @@ public void testPublishTemplateTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -661,8 +791,12 @@ public void testPublishTemplateErrorWithZeroContentResponse() { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -677,8 +811,12 @@ public void testPublishTemplateErrorWithMalformedResponse() { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -687,15 +825,17 @@ public void testPublishTemplateErrorWithMalformedResponse() { @Test public void testPublishTemplateErrorWithDetails() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -704,17 +844,22 @@ public void testPublishTemplateErrorWithDetails() { @Test public void testPublishTemplateErrorWithRcError() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -729,8 +874,9 @@ public void testRollbackWithNullString() throws Exception { @Test public void testRollbackWithInvalidString() throws Exception { - List invalidVersionStrings = ImmutableList - .of("", " ", "abc", "t123", "123t", "t123t", "12t3", "#$*&^", "-123", "+123", "123.4"); + List invalidVersionStrings = + ImmutableList.of( + "", " ", "abc", "t123", "123t", "t123t", "12t3", "#$*&^", "-123", "+123", "123.4"); for (String version : invalidVersionStrings) { try { @@ -754,8 +900,8 @@ public void testRollbackWithValidString() throws Exception { assertEquals(EXPECTED_TEMPLATE, rolledBackTemplate); assertEquals(1605423446000L, rolledBackTemplate.getVersion().getUpdateTime()); checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } @Test @@ -771,8 +917,8 @@ public void testRollbackWithEmptyTemplateResponse() throws Exception { assertEquals(0, template.getParameterGroups().size()); assertNull(template.getVersion()); checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } @Test(expected = IllegalStateException.class) @@ -801,12 +947,16 @@ public void testRollbackHttpError() throws IOException { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.POST); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @@ -819,8 +969,8 @@ public void testRollbackTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -842,8 +992,8 @@ public void testRollbackSuccessResponseWithUnexpectedPayload() throws IOExceptio assertNull(error.getRemoteConfigErrorCode()); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } @Test @@ -855,12 +1005,16 @@ public void testRollbackErrorWithZeroContentResponse() throws IOException { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.POST); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @@ -873,52 +1027,63 @@ public void testRollbackErrorWithMalformedResponse() throws IOException { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.POST); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @Test public void testRollbackErrorWithDetails() throws IOException { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.POST); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @Test public void testRollbackErrorWithRcError() throws IOException { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.POST); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @@ -940,33 +1105,36 @@ public void testListVersionsWithNullOptions() throws Exception { public void testListVersionsWithOptions() throws Exception { response.setContent(MOCK_LIST_VERSIONS_RESPONSE); - TemplateResponse.ListVersionsResponse versionsList = client.listVersions( + TemplateResponse.ListVersionsResponse versionsList = + client.listVersions( ListVersionsOptions.builder() - .setPageSize(10) - .setPageToken("token") - .setStartTimeMillis(1605219122000L) - .setEndTimeMillis(1606245035000L) - .setEndVersionNumber("29").build()); + .setPageSize(10) + .setPageToken("token") + .setStartTimeMillis(1605219122000L) + .setEndTimeMillis(1606245035000L) + .setEndVersionNumber("29") + .build()); assertTrue(versionsList.hasVersions()); HttpRequest request = interceptor.getLastRequest(); - String urlWithoutParameters = request.getUrl().toString() - .substring(0, request.getUrl().toString().lastIndexOf('?')); - final Map expectedQuery = ImmutableMap.of( + String urlWithoutParameters = + request.getUrl().toString().substring(0, request.getUrl().toString().lastIndexOf('?')); + final Map expectedQuery = + ImmutableMap.of( "endVersionNumber", "29", "pageSize", "10", "pageToken", "token", "startTime", "2020-11-12T22:12:02.000000000Z", - "endTime", "2020-11-24T19:10:35.000000000Z" - ); + "endTime", "2020-11-24T19:10:35.000000000Z"); Map actualQuery = new HashMap<>(); String query = request.getUrl().toURI().getQuery(); String[] pairs = query.split("&"); for (String pair : pairs) { int idx = pair.indexOf("="); - actualQuery.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), - URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); + actualQuery.put( + URLDecoder.decode(pair.substring(0, idx), "UTF-8"), + URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); } assertEquals("GET", request.getRequestMethod()); @@ -998,8 +1166,12 @@ public void testListVersionsHttpError() { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1014,8 +1186,8 @@ public void testListVersionsTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -1048,8 +1220,12 @@ public void testListVersionsErrorWithZeroContentResponse() { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1064,8 +1240,12 @@ public void testListVersionsErrorWithMalformedResponse() { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1074,15 +1254,17 @@ public void testListVersionsErrorWithMalformedResponse() { @Test public void testListVersionsErrorWithDetails() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1091,17 +1273,22 @@ public void testListVersionsErrorWithDetails() { @Test public void testListVersionsErrorWithRcError() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1126,7 +1313,8 @@ public void testBuilderNullRequestFactory() { @Test public void testFromApp() throws IOException { - FirebaseOptions options = FirebaseOptions.builder() + FirebaseOptions options = + FirebaseOptions.builder() .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId("test-project") .build(); @@ -1138,8 +1326,8 @@ public void testFromApp() throws IOException { assertEquals(TEST_REMOTE_CONFIG_URL, client.getRemoteConfigUrl()); assertSame(options.getJsonFactory(), client.getJsonFactory()); - HttpRequest request = client.getRequestFactory().buildGetRequest( - new GenericUrl("https://example.com")); + HttpRequest request = + client.getRequestFactory().buildGetRequest(new GenericUrl("https://example.com")); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); } finally { app.delete(); @@ -1147,33 +1335,32 @@ public void testFromApp() throws IOException { } private FirebaseRemoteConfigClientImpl initRemoteConfigClient( - MockLowLevelHttpResponse mockResponse, HttpResponseInterceptor interceptor) { - MockHttpTransport transport = new MockHttpTransport.Builder() - .setLowLevelHttpResponse(mockResponse) - .build(); + MockLowLevelHttpResponse mockResponse, HttpResponseInterceptor interceptor) { + MockHttpTransport transport = + new MockHttpTransport.Builder().setLowLevelHttpResponse(mockResponse).build(); return FirebaseRemoteConfigClientImpl.builder() - .setProjectId("test-project") - .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(transport.createRequestFactory()) - .setResponseInterceptor(interceptor) - .build(); + .setProjectId("test-project") + .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) + .setRequestFactory(transport.createRequestFactory()) + .setResponseInterceptor(interceptor) + .build(); } private FirebaseRemoteConfigClientImpl initClientWithFaultyTransport() { HttpTransport transport = TestUtils.createFaultyHttpTransport(); return FirebaseRemoteConfigClientImpl.builder() - .setProjectId("test-project") - .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(transport.createRequestFactory()) - .build(); + .setProjectId("test-project") + .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) + .setRequestFactory(transport.createRequestFactory()) + .build(); } private FirebaseRemoteConfigClientImpl.Builder fullyPopulatedBuilder() { return FirebaseRemoteConfigClientImpl.builder() - .setProjectId("test-project") - .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(Utils.getDefaultTransport().createRequestFactory()); + .setProjectId("test-project") + .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) + .setRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()); } private void checkGetRequestHeader(HttpRequest request) { @@ -1185,6 +1372,20 @@ private void checkGetRequestHeader(HttpRequest request, String urlSuffix) { assertEquals(TEST_REMOTE_CONFIG_URL + urlSuffix, request.getUrl().toString()); HttpHeaders headers = request.getHeaders(); assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); + assertEquals("gzip", headers.getAcceptEncoding()); + } + + private void checkGetRequestHeaderForServer(HttpRequest request) { + checkGetRequestHeaderForServer(request, ""); + } + + private void checkGetRequestHeaderForServer(HttpRequest request, String urlSuffix) { + assertEquals("GET", request.getRequestMethod()); + assertEquals(TEST_SERVER_REMOTE_CONFIG_URL + urlSuffix, request.getUrl().toString()); + HttpHeaders headers = request.getHeaders(); + assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); assertEquals("gzip", headers.getAcceptEncoding()); } @@ -1197,6 +1398,7 @@ private void checkPutRequestHeader(HttpRequest request, String urlSuffix, String assertEquals(TEST_REMOTE_CONFIG_URL + urlSuffix, request.getUrl().toString()); HttpHeaders headers = request.getHeaders(); assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); assertEquals("gzip", headers.getAcceptEncoding()); assertEquals(ifMatch, headers.getIfMatch()); } @@ -1206,11 +1408,12 @@ private void checkPostRequestHeader(HttpRequest request, String urlSuffix) { assertEquals(TEST_REMOTE_CONFIG_URL + urlSuffix, request.getUrl().toString()); HttpHeaders headers = request.getHeaders(); assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); assertEquals("gzip", headers.getAcceptEncoding()); } - private void checkRequestContent( - HttpRequest request, Map expected) throws IOException { + private void checkRequestContent(HttpRequest request, Map expected) + throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); request.getContent().writeTo(out); JsonParser parser = ApiClientUtils.getDefaultJsonFactory().createJsonParser(out.toString()); @@ -1220,11 +1423,11 @@ private void checkRequestContent( } private void checkExceptionFromHttpResponse( - FirebaseRemoteConfigException error, - ErrorCode expectedCode, - RemoteConfigErrorCode expectedRemoteConfigCode, - String expectedMessage, - String httpMethod) { + FirebaseRemoteConfigException error, + ErrorCode expectedCode, + RemoteConfigErrorCode expectedRemoteConfigCode, + String expectedMessage, + String httpMethod) { assertEquals(expectedCode, error.getErrorCode()); assertEquals(expectedMessage, error.getMessage()); assertTrue(error.getCause() instanceof HttpResponseException); @@ -1235,4 +1438,225 @@ private void checkExceptionFromHttpResponse( assertEquals(httpMethod, request.getMethod()); assertTrue(request.getUrl().startsWith("https://firebaseremoteconfig.googleapis.com")); } + + // Get server template tests + + @Test + public void testGetServerTemplate() throws Exception { + response.addHeader("etag", TEST_ETAG); + response.setContent(MOCK_SERVER_TEMPLATE_RESPONSE); + + String receivedTemplate = client.getServerTemplate(); + ServerTemplateData serverTemplateData = ServerTemplateData.fromJSON(receivedTemplate); + + assertEquals(EXPECTED_PARAMETERS, serverTemplateData.getParameters()); + assertEquals(TEST_ETAG, serverTemplateData.getETag()); + assertEquals( + convertObjectToString(EXPECTED_SERVER_CONDITIONS), + convertObjectToString(serverTemplateData.getServerConditions())); + assertEquals(1605423446000L, serverTemplateData.getVersion().getUpdateTime()); + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + + @Test + public void testGetServerTemplateWithTimestampUpToNanosecondPrecision() throws Exception { + List timestamps = + ImmutableList.of( + "2020-11-15T06:57:26.342Z", + "2020-11-15T06:57:26.342763Z", + "2020-11-15T06:57:26.342763941Z"); + for (String timestamp : timestamps) { + response.addHeader("etag", TEST_ETAG); + String templateResponse = + "{\"version\": {" + + " \"versionNumber\": \"17\"," + + " \"updateTime\": \"" + + timestamp + + "\"" + + " }}"; + response.setContent(templateResponse); + + String receivedTemplate = client.getServerTemplate(); + ServerTemplateData serverTemplateData = ServerTemplateData.fromJSON(receivedTemplate); + assertEquals(TEST_ETAG, serverTemplateData.getETag()); + assertEquals("17", serverTemplateData.getVersion().getVersionNumber()); + assertEquals(1605423446000L, serverTemplateData.getVersion().getUpdateTime()); + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateWithEmptyTemplateResponse() throws Exception { + response.addHeader("etag", TEST_ETAG); + response.setContent("{}"); + + String receivedTemplate = client.getServerTemplate(); + ServerTemplateData serverTemplateData = ServerTemplateData.fromJSON(receivedTemplate); + + assertEquals(TEST_ETAG, serverTemplateData.getETag()); + assertEquals(0, serverTemplateData.getParameters().size()); + assertEquals(0, serverTemplateData.getServerConditions().size()); + assertEquals(0, serverTemplateData.getParameterGroups().size()); + assertNull(serverTemplateData.getVersion()); + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + + @Test(expected = IllegalStateException.class) + public void testGetServerTemplateWithNoEtag() throws FirebaseRemoteConfigException { + // ETag does not exist + response.setContent(MOCK_SERVER_TEMPLATE_RESPONSE); + + client.getServerTemplate(); + } + + @Test(expected = IllegalStateException.class) + public void testGetServerTemplateWithEmptyEtag() throws FirebaseRemoteConfigException { + // Empty ETag + response.addHeader("etag", ""); + response.setContent(MOCK_SERVER_TEMPLATE_RESPONSE); + + client.getServerTemplate(); + } + + @Test + public void testGetServerTemplateHttpError() { + for (int code : HTTP_STATUS_CODES) { + response.setStatusCode(code).setContent("{}"); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateTransportError() { + client = initClientWithFaultyTransport(); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); + assertTrue(error.getCause() instanceof IOException); + assertNull(error.getHttpResponse()); + assertNull(error.getRemoteConfigErrorCode()); + } + } + + @Test + public void testGetServerTemplateSuccessResponseWithUnexpectedPayload() { + response.setContent("not valid json"); + + try { + client.getServerTemplate(); + fail("No error thrown for malformed response"); + } catch (FirebaseRemoteConfigException error) { + assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); + assertTrue(error.getMessage().startsWith("Error while parsing HTTP response: ")); + assertNotNull(error.getCause()); + assertNotNull(error.getHttpResponse()); + assertNull(error.getRemoteConfigErrorCode()); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + + @Test + public void testGetServerTemplateErrorWithZeroContentResponse() { + for (int code : HTTP_STATUS_CODES) { + response.setStatusCode(code).setZeroContent(); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateErrorWithMalformedResponse() { + for (int code : HTTP_STATUS_CODES) { + response.setStatusCode(code).setContent("not json"); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateErrorWithDetails() { + for (int code : HTTP_STATUS_CODES) { + response + .setStatusCode(code) + .setContent( + "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateErrorWithRcError() { + for (int code : HTTP_STATUS_CODES) { + response + .setStatusCode(code) + .setContent( + "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + public static String convertObjectToString(Object object) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); // Optional: pretty printing + return gson.toJson(object); + } } diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java index d3e7fbff2..7c02b3c2f 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java @@ -22,27 +22,32 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableMap; import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; - import com.google.firebase.remoteconfig.internal.TemplateResponse; - +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import java.util.concurrent.ExecutionException; - import org.junit.After; import org.junit.Test; + +/** Tests +* for {@link FirebaseRemoteConfig}. +* */ public class FirebaseRemoteConfigTest { - private static final FirebaseOptions TEST_OPTIONS = FirebaseOptions.builder() + private static final FirebaseOptions TEST_OPTIONS = + FirebaseOptions.builder() .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId("test-project") .build(); private static final FirebaseRemoteConfigException TEST_EXCEPTION = - new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message"); + new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message"); @After public void tearDown() { @@ -76,10 +81,25 @@ public void testDefaultRemoteConfigClient() { assertTrue(client instanceof FirebaseRemoteConfigClientImpl); assertSame(client, remoteConfig.getRemoteConfigClient()); - String expectedUrl = "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; + String expectedUrl = + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; assertEquals(expectedUrl, ((FirebaseRemoteConfigClientImpl) client).getRemoteConfigUrl()); } + @Test + public void testDefaultServerRemoteConfigClient() { + FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app"); + FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance(app); + + FirebaseRemoteConfigClient client = remoteConfig.getRemoteConfigClient(); + + assertTrue(client instanceof FirebaseRemoteConfigClientImpl); + assertSame(client, remoteConfig.getRemoteConfigClient()); + String expectedUrl = + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/namespaces/firebase-server/serverRemoteConfig"; + assertEquals(expectedUrl, ((FirebaseRemoteConfigClientImpl) client).getServerRemoteConfigUrl()); + } + @Test public void testAppDelete() { FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app"); @@ -98,16 +118,16 @@ public void testAppDelete() { @Test public void testRemoteConfigClientWithoutProjectId() { - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(new MockGoogleCredentials("test-token")) - .build(); + FirebaseOptions options = + FirebaseOptions.builder().setCredentials(new MockGoogleCredentials("test-token")).build(); FirebaseApp.initializeApp(options); try { FirebaseRemoteConfig.getInstance(); fail("No error thrown for missing project ID"); } catch (IllegalArgumentException expected) { - String message = "Project ID is required to access Remote Config service. Use a service " + String message = + "Project ID is required to access Remote Config service. Use a service " + "account credential or set the project ID explicitly via FirebaseOptions. " + "Alternatively you can also set the project ID via the GOOGLE_CLOUD_PROJECT " + "environment variable."; @@ -121,8 +141,8 @@ public void testRemoteConfigClientWithoutProjectId() { @Test public void testGetTemplate() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplate(); @@ -130,6 +150,35 @@ public void testGetTemplate() throws FirebaseRemoteConfigException { assertEquals(TEST_ETAG, template.getETag()); } + @Test + public void testGetTemplateWithManagedValues() throws FirebaseRemoteConfigException { + Template template = new Template() + .setETag(TEST_ETAG) + .setParameters(ImmutableMap.of( + "p1", new Parameter().setConditionalValues(ImmutableMap.of( + "c1", ParameterValue.ofRollout("rollout_1", "value_1", 10.0))), + "p2", new Parameter().setConditionalValues(ImmutableMap.of( + "c2", ParameterValue.ofPersonalization("personalization_1"))) + )); + MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate(template); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + Template fetchedTemplate = remoteConfig.getTemplate(); + + assertEquals(TEST_ETAG, fetchedTemplate.getETag()); + ParameterValue.RolloutValue rolloutValue = + (ParameterValue.RolloutValue) fetchedTemplate.getParameters().get("p1") + .getConditionalValues().get("c1"); + assertEquals("rollout_1", rolloutValue.getRolloutId()); + assertEquals("value_1", rolloutValue.getValue()); + assertEquals(10.0, rolloutValue.getPercent(), 0.0); + + ParameterValue.PersonalizationValue personalizationValue = + (ParameterValue.PersonalizationValue) fetchedTemplate.getParameters().get("p2") + .getConditionalValues().get("c2"); + assertEquals("personalization_1", personalizationValue.getPersonalizationId()); + } + @Test public void testGetTemplateFailure() { MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION); @@ -144,8 +193,8 @@ public void testGetTemplateFailure() { @Test public void testGetTemplateAsync() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAsync().get(); @@ -169,8 +218,8 @@ public void testGetTemplateAsyncFailure() throws InterruptedException { @Test public void testGetTemplateAtVersionWithStringValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersion("64"); @@ -192,8 +241,8 @@ public void testGetTemplateAtVersionWithStringValueFailure() { @Test public void testGetTemplateAtVersionAsyncWithStringValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersionAsync("55").get(); @@ -215,8 +264,8 @@ public void testGetTemplateAtVersionAsyncWithStringValueFailure() throws Interru @Test public void testGetTemplateAtVersionWithLongValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersion(64L); @@ -238,8 +287,8 @@ public void testGetTemplateAtVersionWithLongValueFailure() { @Test public void testGetTemplateAtVersionAsyncWithLongValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersionAsync(55L).get(); @@ -407,8 +456,8 @@ public void testForcePublishTemplateAsyncFailure() throws InterruptedException { @Test public void testRollbackWithStringValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollback("64"); @@ -430,8 +479,8 @@ public void testRollbackWithStringValueFailure() { @Test public void testRollbackAsyncWithStringValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollbackAsync("55").get(); @@ -453,8 +502,8 @@ public void testRollbackAsyncWithStringValueFailure() throws InterruptedExceptio @Test public void testRollbackWithLongValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollback(64L); @@ -476,8 +525,8 @@ public void testRollbackWithLongValueFailure() { @Test public void testRollbackAsyncWithLongValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollbackAsync(55L).get(); @@ -501,7 +550,8 @@ public void testRollbackAsyncWithLongValueFailure() throws InterruptedException @Test public void testListVersionsWithNoOptions() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); @@ -524,7 +574,8 @@ public void testListVersionsWithNoOptionsFailure() { @Test public void testListVersionsAsyncWithNoOptions() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); @@ -547,12 +598,13 @@ public void testListVersionsAsyncWithNoOptionsFailure() throws InterruptedExcept @Test public void testListVersionsWithOptions() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); - ListVersionsPage listVersionsPage = remoteConfig.listVersions( - ListVersionsOptions.builder().build()); + ListVersionsPage listVersionsPage = + remoteConfig.listVersions(ListVersionsOptions.builder().build()); assertEquals("token", listVersionsPage.getNextPageToken()); } @@ -571,12 +623,13 @@ public void testListVersionsWithOptionsFailure() { @Test public void testListVersionsAsyncWithOptions() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); - ListVersionsPage listVersionsPage = remoteConfig.listVersionsAsync( - ListVersionsOptions.builder().build()).get(); + ListVersionsPage listVersionsPage = + remoteConfig.listVersionsAsync(ListVersionsOptions.builder().build()).get(); assertEquals("token", listVersionsPage.getNextPageToken()); } @@ -597,4 +650,62 @@ private FirebaseRemoteConfig getRemoteConfig(FirebaseRemoteConfigClient client) FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS); return new FirebaseRemoteConfig(app, client); } + + // Get Server template tests + + @Test + public void testGetServerTemplate() throws FirebaseRemoteConfigException { + MockRemoteConfigClient client = + MockRemoteConfigClient.fromServerTemplate( + new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + ServerTemplate template = remoteConfig.getServerTemplate(); + String templateData = template.toJson(); + JsonElement expectedJson = + JsonParser.parseString(new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + JsonElement actualJson = JsonParser.parseString(templateData); + + assertEquals(expectedJson, actualJson); + } + + @Test + public void testGetServerTemplateFailure() { + MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + try { + remoteConfig.getServerTemplate(); + } catch (FirebaseRemoteConfigException e) { + assertSame(TEST_EXCEPTION, e); + } + } + + @Test + public void testGetServerTemplateAsync() throws Exception { + MockRemoteConfigClient client = + MockRemoteConfigClient.fromServerTemplate( + new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + ServerTemplate template = remoteConfig.getServerTemplateAsync().get(); + String templateData = template.toJson(); + JsonElement expectedJson = + JsonParser.parseString(new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + JsonElement actualJson = JsonParser.parseString(templateData); + + assertEquals(expectedJson, actualJson); + } + + @Test + public void testGetServerTemplateAsyncFailure() throws InterruptedException { + MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + try { + remoteConfig.getServerTemplateAsync().get(); + } catch (ExecutionException e) { + assertSame(TEST_EXCEPTION, e.getCause()); + } + } } diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index 9ca58508d..3ac7f6b1c 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -21,28 +21,35 @@ public class MockRemoteConfigClient implements FirebaseRemoteConfigClient{ private final Template resultTemplate; + private final String resultServerTemplate; private final FirebaseRemoteConfigException exception; private final ListVersionsResponse listVersionsResponse; private MockRemoteConfigClient(Template resultTemplate, - ListVersionsResponse listVersionsResponse, - FirebaseRemoteConfigException exception) { + String resultServerTemplate, + ListVersionsResponse listVersionsResponse, + FirebaseRemoteConfigException exception) { this.resultTemplate = resultTemplate; + this.resultServerTemplate = resultServerTemplate; this.listVersionsResponse = listVersionsResponse; this.exception = exception; } static MockRemoteConfigClient fromTemplate(Template resultTemplate) { - return new MockRemoteConfigClient(resultTemplate, null, null); + return new MockRemoteConfigClient(resultTemplate,null, null, null); + } + + static MockRemoteConfigClient fromServerTemplate(String resultServerTemplate) { + return new MockRemoteConfigClient(null, resultServerTemplate,null, null); } static MockRemoteConfigClient fromListVersionsResponse( ListVersionsResponse listVersionsResponse) { - return new MockRemoteConfigClient(null, listVersionsResponse, null); + return new MockRemoteConfigClient(null,null, listVersionsResponse, null); } static MockRemoteConfigClient fromException(FirebaseRemoteConfigException exception) { - return new MockRemoteConfigClient(null, null, exception); + return new MockRemoteConfigClient(null,null, null, exception); } @Override @@ -53,6 +60,14 @@ public Template getTemplate() throws FirebaseRemoteConfigException { return resultTemplate; } + @Override + public String getServerTemplate() throws FirebaseRemoteConfigException { + if (exception != null) { + throw exception; + } + return resultServerTemplate; + } + @Override public Template getTemplateAtVersion(String versionNumber) throws FirebaseRemoteConfigException { if (exception != null) { diff --git a/src/test/java/com/google/firebase/remoteconfig/ParameterTest.java b/src/test/java/com/google/firebase/remoteconfig/ParameterTest.java index 7fc6ee555..598b0008c 100644 --- a/src/test/java/com/google/firebase/remoteconfig/ParameterTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/ParameterTest.java @@ -104,4 +104,22 @@ public void testEquality() { assertNotEquals(parameterThree, parameterFive); assertNotEquals(parameterThree, parameterSeven); } + + @Test + public void testEqualityWithManagedValues() { + final Parameter parameterOne = new Parameter() + .setDefaultValue(ParameterValue.ofRollout("rollout_1", "value_1", 10.0)); + final Parameter parameterTwo = new Parameter() + .setDefaultValue(ParameterValue.ofRollout("rollout_1", "value_1", 10.0)); + + assertEquals(parameterOne, parameterTwo); + + final Parameter parameterThree = new Parameter() + .setDefaultValue(ParameterValue.ofPersonalization("personalization_1")); + final Parameter parameterFour = new Parameter() + .setDefaultValue(ParameterValue.ofPersonalization("personalization_1")); + + assertEquals(parameterThree, parameterFour); + assertNotEquals(parameterOne, parameterThree); + } } diff --git a/src/test/java/com/google/firebase/remoteconfig/ParameterValueTest.java b/src/test/java/com/google/firebase/remoteconfig/ParameterValueTest.java index 842fd808f..46db5c44e 100644 --- a/src/test/java/com/google/firebase/remoteconfig/ParameterValueTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/ParameterValueTest.java @@ -18,6 +18,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.remoteconfig.ParameterValue.ExperimentVariantValue; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse; import org.junit.Test; @@ -37,6 +42,50 @@ public void testCreateInAppDefault() { assertEquals(ParameterValue.InAppDefault.class, parameterValue.getClass()); } + @Test + public void testCreateRolloutValue() { + final ParameterValue.RolloutValue parameterValue = + ParameterValue.ofRollout("rollout_1", "value_1", 10.0); + + assertEquals("rollout_1", parameterValue.getRolloutId()); + assertEquals("value_1", parameterValue.getValue()); + assertEquals(10.0, parameterValue.getPercent(), 0.0); + } + + @Test + public void testCreatePersonalizationValue() { + final ParameterValue.PersonalizationValue parameterValue = + ParameterValue.ofPersonalization("personalization_1"); + + assertEquals("personalization_1", parameterValue.getPersonalizationId()); + } + + @Test + public void testCreateExperimentValue() { + final ParameterValue.ExperimentValue parameterValue = + ParameterValue.ofExperiment("experiment_1", ImmutableList.of( + ExperimentVariantValue.of("variant_1", "value_1"), + ExperimentVariantValue.ofNoChange("variant_2") + ), 10.0); + + assertEquals("experiment_1", parameterValue.getExperimentId()); + assertEquals(2, parameterValue.getExperimentVariantValues().size()); + assertEquals(10.0, parameterValue.getExposurePercent(), 0.0); + + assertEquals("experiment_1", parameterValue.getExperimentId()); + assertEquals(2, parameterValue.getExperimentVariantValues().size()); + ExperimentVariantValue variant1 = parameterValue.getExperimentVariantValues().get(0); + assertEquals("variant_1", variant1.getVariantId()); + assertEquals("value_1", variant1.getValue()); + assertEquals(false, variant1.isNoChange()); + assertEquals(null, variant1.getNoChange()); + ExperimentVariantValue variant2 = parameterValue.getExperimentVariantValues().get(1); + assertEquals("variant_2", variant2.getVariantId()); + assertEquals(null, variant2.getValue()); + assertEquals(true, variant2.isNoChange()); + + } + @Test public void testEquality() { ParameterValue.Explicit parameterValueOne = ParameterValue.of("value"); @@ -50,5 +99,61 @@ public void testEquality() { ParameterValue.InAppDefault parameterValueFive = ParameterValue.inAppDefault(); assertEquals(parameterValueFour, parameterValueFive); + + ParameterValue.RolloutValue rolloutValueOne = + ParameterValue.ofRollout("rollout_1", "value_1", 10.0); + ParameterValue.RolloutValue rolloutValueTwo = + ParameterValue.ofRollout("rollout_1", "value_1", 10.0); + ParameterValue.RolloutValue rolloutValueThree = + ParameterValue.ofRollout("rollout_2", "value_1", 10.0); + + assertEquals(rolloutValueOne, rolloutValueTwo); + assertNotEquals(rolloutValueOne, rolloutValueThree); + + ParameterValue.PersonalizationValue personalizationValueOne = + ParameterValue.ofPersonalization("personalization_1"); + ParameterValue.PersonalizationValue personalizationValueTwo = + ParameterValue.ofPersonalization("personalization_1"); + ParameterValue.PersonalizationValue personalizationValueThree = + ParameterValue.ofPersonalization("personalization_2"); + + assertEquals(personalizationValueOne, personalizationValueTwo); + assertNotEquals(personalizationValueOne, personalizationValueThree); + + ParameterValue.ExperimentValue experimentValueOne = + ParameterValue.ofExperiment("experiment_1", ImmutableList.of( + ExperimentVariantValue.of("variant_1", "value_1") + ), 10.0); + ParameterValue.ExperimentValue experimentValueTwo = + ParameterValue.ofExperiment("experiment_1", ImmutableList.of( + ExperimentVariantValue.of("variant_1", "value_1") + ), 10.0); + ParameterValue.ExperimentValue experimentValueThree = + ParameterValue.ofExperiment("experiment_2", ImmutableList.of( + ExperimentVariantValue.of("variant_1", "value_1") + ), 10.0); + ParameterValue.ExperimentValue experimentValueFour = + ParameterValue.ofExperiment("experiment_1", ImmutableList.of( + ExperimentVariantValue.of("variant_2", "value_2") + ), 20.0); + assertEquals(experimentValueOne, experimentValueTwo); + assertNotEquals(experimentValueOne, experimentValueThree); + assertNotEquals(experimentValueOne, experimentValueFour); + } + + @Test + public void testExperimentValueWithZeroExposure() { + ParameterValue.ExperimentValue value = ParameterValue.ofExperiment( + "exp_0", ImmutableList.of(ExperimentVariantValue.of("v1", "foo")), 0.0); + + // Test Serialization + ParameterValueResponse response = value.toParameterValueResponse(); + assertEquals(0.0, response.getExperimentValue().getExposurePercent(), 0.0); + + // Test Deserialization + ParameterValue fromResponse = ParameterValue.fromParameterValueResponse(response); + assertTrue(fromResponse instanceof ParameterValue.ExperimentValue); + assertEquals(0.0, ((ParameterValue.ExperimentValue) fromResponse).getExposurePercent(), 0.0); } + } diff --git a/src/test/java/com/google/firebase/remoteconfig/ServerConditionTest.java b/src/test/java/com/google/firebase/remoteconfig/ServerConditionTest.java new file mode 100644 index 000000000..6cbece1c0 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ServerConditionTest.java @@ -0,0 +1,218 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.CustomSignalConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.ServerConditionResponse; +import java.util.ArrayList; +import org.junit.Test; + +/** Tests +* for {@link ServerCondition}. +* */ +public class ServerConditionTest { + + @Test + public void testConstructor() { + OneOfCondition conditions = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + ServerCondition serverCondition = new ServerCondition("ios_en_1", conditions); + + assertEquals("ios_en_1", serverCondition.getName()); + assertEquals(conditions, serverCondition.getCondition()); + } + + @Test + public void testConstructorWithResponse() { + CustomSignalConditionResponse customResponse = + new CustomSignalConditionResponse() + .setKey("test_key") + .setOperator("NUMERIC_EQUAL") + .setTargetValues(ImmutableList.of("1")); + OneOfConditionResponse conditionResponse = + new OneOfConditionResponse().setCustomSignalCondition(customResponse); + ServerConditionResponse response = + new ServerConditionResponse().setName("ios_en_2").setServerCondition(conditionResponse); + ServerCondition serverCondition = new ServerCondition(response); + + assertEquals("ios_en_2", serverCondition.getName()); + assertEquals("test_key", serverCondition.getCondition().getCustomSignal().getCustomSignalKey()); + } + + @Test + public void testIllegalConstructor() { + IllegalArgumentException error = + assertThrows(IllegalArgumentException.class, () -> new ServerCondition(null, null)); + + assertEquals("condition name must not be null or empty", error.getMessage()); + } + + @Test + public void testConstructorWithNullServerConditionResponse() { + assertThrows(NullPointerException.class, () -> new ServerCondition(null)); + } + + @Test + public void testSetNullName() { + OneOfCondition conditions = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + ServerCondition condition = new ServerCondition("ios", conditions); + + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> condition.setName(null)); + + assertEquals("condition name must not be null or empty", error.getMessage()); + } + + @Test + public void testSetEmptyName() { + OneOfCondition conditions = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + ServerCondition condition = new ServerCondition("ios", conditions); + + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> condition.setName("")); + + assertEquals("condition name must not be null or empty", error.getMessage()); + } + + @Test + public void testSetNullServerCondition() { + OneOfCondition conditions = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + ServerCondition condition = new ServerCondition("ios", conditions); + + assertThrows(NullPointerException.class, () -> condition.setServerCondition(null)); + } + + @Test + public void testEquality() { + OneOfCondition conditionOne = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + OneOfCondition conditionTwo = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>(ImmutableList.of("100")))), + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator + .NUMERIC_GREATER_THAN, + new ArrayList<>(ImmutableList.of("20")))), + new OneOfCondition() + .setPercent( + new PercentCondition( + new MicroPercentRange(25000000, 100000000), + PercentConditionOperator.BETWEEN, + "cla24qoibb61")))))))); + + final ServerCondition serverConditionOne = new ServerCondition("ios", conditionOne); + final ServerCondition serverConditionTwo = new ServerCondition("ios", conditionOne); + final ServerCondition serverConditionThree = new ServerCondition("android", conditionTwo); + final ServerCondition serverConditionFour = new ServerCondition("android", conditionTwo); + + assertEquals(serverConditionOne, serverConditionTwo); + assertEquals(serverConditionThree, serverConditionFour); + } +} diff --git a/src/test/java/com/google/firebase/remoteconfig/ServerTemplateImplTest.java b/src/test/java/com/google/firebase/remoteconfig/ServerTemplateImplTest.java new file mode 100644 index 000000000..d7b761141 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ServerTemplateImplTest.java @@ -0,0 +1,419 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.api.core.ApiFuture; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.testing.TestUtils; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import org.junit.BeforeClass; +import org.junit.Test; + +/** Tests +* for {@link ServerTemplateImpl}. +* */ +public class ServerTemplateImplTest { + + private static final FirebaseOptions TEST_OPTIONS = + FirebaseOptions.builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId("test-project") + .build(); + + private static String cacheTemplate; + + @BeforeClass + public static void setUpClass() { + cacheTemplate = TestUtils.loadResource("getServerTemplateData.json"); + } + + @Test + public void testServerTemplateWithoutCacheValueThrowsException() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + + IllegalArgumentException error = + assertThrows( + IllegalArgumentException.class, + () -> new ServerTemplateImpl.Builder(null).defaultConfig(defaultConfig).build()); + + assertEquals("JSON String must not be null or empty.", error.getMessage()); + } + + @Test + public void testEvaluateWithoutContextReturnsDefaultValue() throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(); + + assertEquals("Default value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluateCustomSignalReturnsDefaultValue() throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("users", "100").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluateCustomSignalReturnsConditionalValue() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("users", "99").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Conditional value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluateCustomSignalWithoutContextReturnsDefaultValue() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluateCustomSignalWithInvalidContextReturnsDefaultValue() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("users", "abc").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluatePercentWithoutRandomizationIdReturnsDefaultValue() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Percent")); + } + + @Test + public void testEvaluatePercentReturnsConditionalValue() throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("randomizationId", "user").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Conditional value", evaluatedConfig.getString("Percent")); + } + + @Test + public void testEvaluateWithoutDefaultValueReturnsEmptyString() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("", evaluatedConfig.getString("Unset default value")); + } + + @Test + public void testEvaluateWithInvalidCacheValueThrowsException() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + String invalidJsonString = "abc"; + IllegalArgumentException error = assertThrows( + IllegalArgumentException.class, + () -> new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(invalidJsonString) + .build()); + + assertEquals("Unable to parse JSON string.", error.getMessage()); + // Verify the cause is the original FirebaseRemoteConfigException + assertTrue(error.getCause() instanceof FirebaseRemoteConfigException); + } + + @Test + public void testEvaluateWithInAppDefaultReturnsEmptyString() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("", evaluatedConfig.getString("In-app default")); + } + + @Test + public void testEvaluateWithDerivedInAppDefaultReturnsDefaultValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Derived in-app default")); + } + + @Test + public void testEvaluateWithMultipleConditionReturnsConditionalValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("users", "99").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Conditional value 1", evaluatedConfig.getString("Multiple conditions")); + } + + @Test + public void testEvaluateWithChainedAndConditionReturnsDefaultValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = + new KeysAndValues.Builder() + .put("users", "100") + .put("premium users", 20) + .put("randomizationId", "user") + .build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Chained conditions")); + } + + @Test + public void testEvaluateWithChainedAndConditionReturnsConditionalValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = + new KeysAndValues.Builder().put("users", "99").put("premium users", "30").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Conditional value", evaluatedConfig.getString("Chained conditions")); + } + + @Test + public void testGetEvaluateConfigOnInvalidTypeReturnsDefaultValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("randomizationId", "user").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals(0L, evaluatedConfig.getLong("Percent")); + } + + @Test + public void testGetEvaluateConfigInvalidKeyReturnsStaticValueSource() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals(ValueSource.STATIC, evaluatedConfig.getValueSource("invalid")); + } + + @Test + public void testGetEvaluateConfigInAppDefaultConfigReturnsDefaultValueSource() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().put("In-app default", "abc").build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals(ValueSource.DEFAULT, evaluatedConfig.getValueSource("In-app default")); + } + + @Test + public void testGetEvaluateConfigUnsetDefaultConfigReturnsDefaultValueSource() throws Exception { + KeysAndValues defaultConfig = + new KeysAndValues.Builder().put("Unset default config", "abc").build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals(ValueSource.DEFAULT, evaluatedConfig.getValueSource("Unset default config")); + } + + private static final String TEST_ETAG = "etag-123456789012-1"; + + private FirebaseRemoteConfig getRemoteConfig(FirebaseRemoteConfigClient client) { + FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "test-app"); + return new FirebaseRemoteConfig(app, client); + } + + @Test + public void testLoad() throws Exception { + // 1. Define the template data that the mock client will return. + // This is the EXPECTED state after `load()` is called. + final String expectedTemplateJsonAfterLoad = + new ServerTemplateData().setETag(TEST_ETAG).toJSON(); + + // 2. Mock the HTTP client to return the predefined response. + MockRemoteConfigClient client = + MockRemoteConfigClient.fromServerTemplate(expectedTemplateJsonAfterLoad); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + // 3. Build the template instance. + // It's initialized with a complex `cacheTemplate` to ensure `load()` properly + // overwrites it. + KeysAndValues defaultConfig = + new KeysAndValues.Builder().put("Unset default config", "abc").build(); + ServerTemplate template = + remoteConfig + .serverTemplateBuilder() + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) // This is the initial state before `load()` + .build(); + + // 4. Call the load method, which fetches the new template from the mock client. + ApiFuture loadFuture = template.load(); + loadFuture.get(); // Wait for the async operation to complete. + + // 5. Get the ACTUAL state of the template after `load()` has executed. + String actualJsonAfterLoad = template.toJson(); + + // 6. Assert that the template's state has been updated to match what the mock + // client returned. + // Parsing to JsonElement performs a deep, order-insensitive comparison. + JsonElement expectedJson = JsonParser.parseString(expectedTemplateJsonAfterLoad); + JsonElement actualJson = JsonParser.parseString(actualJsonAfterLoad); + + assertEquals(expectedJson, actualJson); + } + + @Test + public void testBuilderParsesCachedTemplateCorrectly() throws FirebaseRemoteConfigException { + // Arrange: + // 1. Create a canonical JSON string by parsing the input file and then + // re-serializing it. This gives us the precise expected output format, + // accounting for any formatting or default value differences. + ServerTemplateData canonicalData = ServerTemplateData.fromJSON(cacheTemplate); + String expectedJsonString = canonicalData.toJSON(); + + // Act: + // 2. Build a ServerTemplate instance from the original cached JSON string, + // which triggers the parsing logic we want to test. + ServerTemplate template = + new ServerTemplateImpl.Builder(null).cachedTemplate(cacheTemplate).build(); + + // Assert: + // 3. Compare the JSON from the newly built template against the canonical + // version. + // This verifies that the internal state was parsed and stored correctly. + // Using JsonElement ensures the comparison is not affected by key order. + JsonElement expectedJsonTree = JsonParser.parseString(expectedJsonString); + JsonElement actualJsonTree = JsonParser.parseString(template.toJson()); + + assertEquals(expectedJsonTree, actualJsonTree); + } +} diff --git a/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java b/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java index a3ea3e878..92abb850c 100644 --- a/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java @@ -107,7 +107,7 @@ public void testConstructorWithETag() { } @Test(expected = NullPointerException.class) - public void testConstructorWithNullTemplateResponse() { + public void testConstructorWithNullTemplateResponse() throws FirebaseRemoteConfigException { new Template((TemplateResponse) null); } diff --git a/src/test/java/com/google/firebase/remoteconfig/ValueTest.java b/src/test/java/com/google/firebase/remoteconfig/ValueTest.java new file mode 100644 index 000000000..c1822b063 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ValueTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.remoteconfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class ValueTest { + @Test + public void testGetSourceReturnsValueSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.getSource(), ValueSource.STATIC); + } + + @Test + public void testAsStringReturnsValueAsString() { + Value value = new Value(ValueSource.STATIC, "sample-string"); + assertEquals(value.asString(), "sample-string"); + } + + @Test + public void testAsStringReturnsDefaultEmptyString() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asString(), ""); + } + + @Test + public void testAsLongReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asLong(), 0L); + } + + @Test + public void testAsLongReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertEquals(value.asLong(), 0L); + } + + @Test + public void testAsLongReturnsSourceValueAsLong() { + Value value = new Value(ValueSource.REMOTE, "123"); + assertEquals(value.asLong(), 123L); + } + + @Test + public void testAsDoubleReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asDouble(), 0, 0); + } + + @Test + public void testAsDoubleReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertEquals(value.asDouble(), 0, 0); + } + + @Test + public void testAsDoubleReturnsSourceValueAsDouble() { + Value value = new Value(ValueSource.REMOTE, "123.34"); + assertEquals(value.asDouble(), 123.34, 0); + } + + @Test + public void testAsBooleanReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertFalse(value.asBoolean()); + } + + @Test + public void testAsBooleanReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertFalse(value.asBoolean()); + } + + @Test + public void testAsBooleanReturnsSourceValueAsBoolean() { + Value value = new Value(ValueSource.REMOTE, "1"); + assertTrue(value.asBoolean()); + } + + @Test + public void testAsBooleanReturnsSourceValueYesAsBoolean() { + Value value = new Value(ValueSource.REMOTE, "YeS"); + assertTrue(value.asBoolean()); + } +} + \ No newline at end of file diff --git a/src/test/java/com/google/firebase/remoteconfig/VersionTest.java b/src/test/java/com/google/firebase/remoteconfig/VersionTest.java index 515629660..1e524195f 100644 --- a/src/test/java/com/google/firebase/remoteconfig/VersionTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/VersionTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import com.google.firebase.remoteconfig.internal.TemplateResponse; import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; @@ -27,15 +28,16 @@ public class VersionTest { - @Test(expected = NullPointerException.class) + @Test public void testConstructorWithNullVersionResponse() { - new Version(null); + assertThrows(NullPointerException.class, () -> new Version(null)); } - @Test(expected = IllegalStateException.class) + @Test public void testConstructorWithInvalidUpdateTime() { - new Version(new VersionResponse() - .setUpdateTime("sunday,26th")); + assertThrows(IllegalStateException.class, () -> + new Version(new VersionResponse().setUpdateTime("sunday,26th"))); + } @Test diff --git a/src/test/java/com/google/firebase/snippets/FirebaseAppSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseAppSnippets.java index bf3d4ca6b..d7cc11813 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseAppSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseAppSnippets.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java index f40c00da5..d081c671c 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/snippets/FirebaseDatabaseSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseDatabaseSnippets.java index 079946649..6fa187087 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseDatabaseSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseDatabaseSnippets.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. diff --git a/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java index 4a4a25472..6b62fe839 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -149,6 +149,36 @@ public void sendAll() throws FirebaseMessagingException { // [END send_all] } + public void sendEach() throws FirebaseMessagingException { + String registrationToken = "YOUR_REGISTRATION_TOKEN"; + + // [START send_each] + // Create a list containing up to 500 messages. + List messages = Arrays.asList( + Message.builder() + .setNotification(Notification.builder() + .setTitle("Price drop") + .setBody("5% off all electronics") + .build()) + .setToken(registrationToken) + .build(), + // ... + Message.builder() + .setNotification(Notification.builder() + .setTitle("Price drop") + .setBody("2% off all books") + .build()) + .setTopic("readers-club") + .build() + ); + + BatchResponse response = FirebaseMessaging.getInstance().sendEach(messages); + // See the BatchResponse reference documentation + // for the contents of response. + System.out.println(response.getSuccessCount() + " messages were sent successfully"); + // [END send_each] + } + public void sendMulticast() throws FirebaseMessagingException { // [START send_multicast] // Create a list containing up to 500 registration tokens. @@ -201,6 +231,36 @@ public void sendMulticastAndHandleErrors() throws FirebaseMessagingException { // [END send_multicast_error] } + public void sendEachForMulticastAndHandleErrors() throws FirebaseMessagingException { + // [START send_each_for_multicast_error] + // These registration tokens come from the client FCM SDKs. + List registrationTokens = Arrays.asList( + "YOUR_REGISTRATION_TOKEN_1", + // ... + "YOUR_REGISTRATION_TOKEN_n" + ); + + MulticastMessage message = MulticastMessage.builder() + .putData("score", "850") + .putData("time", "2:45") + .addAllTokens(registrationTokens) + .build(); + BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast(message); + if (response.getFailureCount() > 0) { + List responses = response.getResponses(); + List failedTokens = new ArrayList<>(); + for (int i = 0; i < responses.size(); i++) { + if (!responses.get(i).isSuccessful()) { + // The order of responses corresponds to the order of the registration tokens. + failedTokens.add(registrationTokens.get(i)); + } + } + + System.out.println("List of tokens that caused failures: " + failedTokens); + } + // [END send_each_for_multicast_error] + } + public Message androidMessage() { // [START android_message] Message message = Message.builder() diff --git a/src/test/java/com/google/firebase/snippets/FirebaseStorageSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseStorageSnippets.java index 706cc1b16..739cc4707 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseStorageSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseStorageSnippets.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google Inc. + * 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. @@ -42,7 +42,7 @@ public void initializeAppForStorage() throws IOException { Bucket bucket = StorageClient.getInstance().bucket(); // 'bucket' is an object defined in the google-cloud-storage Java library. - // See http://googlecloudplatform.github.io/google-cloud-java/latest/apidocs/com/google/cloud/storage/Bucket.html + // See https://cloud.google.com/java/docs/reference/google-cloud-storage/latest/com.google.cloud.storage.Bucket // for more details. // [END init_admin_sdk_for_storage] System.out.println("Retrieved bucket: " + bucket.getName()); diff --git a/src/test/java/com/google/firebase/testing/FirebaseAppRule.java b/src/test/java/com/google/firebase/testing/FirebaseAppRule.java index 1049a1f2f..5d719b99a 100644 --- a/src/test/java/com/google/firebase/testing/FirebaseAppRule.java +++ b/src/test/java/com/google/firebase/testing/FirebaseAppRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/testing/GenericFunction.java b/src/test/java/com/google/firebase/testing/GenericFunction.java index b7dc8a84a..639c2eb7c 100644 --- a/src/test/java/com/google/firebase/testing/GenericFunction.java +++ b/src/test/java/com/google/firebase/testing/GenericFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java index fcfda7460..b5c3864b1 100644 --- a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java +++ b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/testing/MockitoTestRule.java b/src/test/java/com/google/firebase/testing/MockitoTestRule.java index abe4e2806..99edaed42 100644 --- a/src/test/java/com/google/firebase/testing/MockitoTestRule.java +++ b/src/test/java/com/google/firebase/testing/MockitoTestRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/testing/MultiRequestMockHttpTransport.java b/src/test/java/com/google/firebase/testing/MultiRequestMockHttpTransport.java index 53c34df8e..449be9517 100644 --- a/src/test/java/com/google/firebase/testing/MultiRequestMockHttpTransport.java +++ b/src/test/java/com/google/firebase/testing/MultiRequestMockHttpTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/testing/ServiceAccount.java b/src/test/java/com/google/firebase/testing/ServiceAccount.java index cb447a517..2c5c3f402 100644 --- a/src/test/java/com/google/firebase/testing/ServiceAccount.java +++ b/src/test/java/com/google/firebase/testing/ServiceAccount.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/testing/TestResponseInterceptor.java b/src/test/java/com/google/firebase/testing/TestResponseInterceptor.java index 7bc3a7ec6..48f26d662 100644 --- a/src/test/java/com/google/firebase/testing/TestResponseInterceptor.java +++ b/src/test/java/com/google/firebase/testing/TestResponseInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/google/firebase/testing/TestUtils.java b/src/test/java/com/google/firebase/testing/TestUtils.java index 59f6b7195..b230a94aa 100644 --- a/src/test/java/com/google/firebase/testing/TestUtils.java +++ b/src/test/java/com/google/firebase/testing/TestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/resources/getServerRemoteConfig.json b/src/test/resources/getServerRemoteConfig.json new file mode 100644 index 000000000..e5cb1a9eb --- /dev/null +++ b/src/test/resources/getServerRemoteConfig.json @@ -0,0 +1,142 @@ +{ + "conditions": [ + { + "name": "custom_signal", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "customSignal": { + "customSignalOperator": "NUMERIC_LESS_THAN", + "customSignalKey": "users", + "targetCustomSignalValues": [ + "100" + ] + } + } + ] + } + } + ] + } + } + }, + { + "name": "percent", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "percent": { + "percentOperator": "BETWEEN", + "seed": "3maarirs9xzs", + "microPercentRange": { + "microPercentLowerBound": 12000000, + "microPercentUpperBound": 100000000 + } + } + } + ] + } + } + ] + } + } + }, + { + "name": "chained_conditions", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "customSignal": { + "customSignalOperator": "NUMERIC_LESS_THAN", + "customSignalKey": "users", + "targetCustomSignalValues": [ + "100" + ] + } + }, + { + "customSignal": { + "customSignalOperator": "NUMERIC_GREATER_THAN", + "customSignalKey": "premium users", + "targetCustomSignalValues": [ + "20" + ] + } + }, + { + "percent": { + "percentOperator": "BETWEEN", + "seed": "cla24qoibb61", + "microPercentRange": { + "microPercentLowerBound": 25000000, + "microPercentUpperBound": 100000000 + } + } + } + ] + } + } + ] + } + } + } + ], + "parameters": { + "welcome_message_text": { + "defaultValue": { + "value": "welcome to app" + }, + "conditionalValues": { + "ios_en": { + "value": "welcome to app en" + } + }, + "description": "text for welcome message!", + "valueType": "STRING" + }, + "header_text": { + "defaultValue": { + "useInAppDefault": true + }, + "valueType": "STRING" + } + }, + "parameterGroups": { + "new menu": { + "description": "New Menu", + "parameters": { + "pumpkin_spice_season": { + "defaultValue": { + "value": "true" + }, + "description": "Whether it's currently pumpkin spice season.", + "valueType": "BOOLEAN" + } + } + } + }, + "version": { + "versionNumber": "17", + "updateOrigin": "ADMIN_SDK_NODE", + "updateType": "INCREMENTAL_UPDATE", + "updateUser": { + "email": "firebase-user@account.com", + "name": "dev-admin", + "imageUrl": "http://image.jpg" + }, + "updateTime": "2020-11-15T06:57:26.342763941Z", + "description": "promo config" + } +} \ No newline at end of file diff --git a/src/test/resources/getServerTemplateData.json b/src/test/resources/getServerTemplateData.json new file mode 100644 index 000000000..c26f74185 --- /dev/null +++ b/src/test/resources/getServerTemplateData.json @@ -0,0 +1,181 @@ +{ + "conditions": [ + { + "name": "custom_signal", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "customSignal": { + "customSignalOperator": "NUMERIC_LESS_THAN", + "customSignalKey": "users", + "targetCustomSignalValues": [ + "100" + ] + } + } + ] + } + } + ] + } + } + }, + { + "name": "percent", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "percent": { + "percentOperator": "BETWEEN", + "seed": "3maarirs9xzs", + "microPercentRange": { + "microPercentLowerBound": 12000000, + "microPercentUpperBound": 100000000 + } + } + } + ] + } + } + ] + } + } + }, + { + "name": "chained_conditions", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "customSignal": { + "customSignalOperator": "NUMERIC_LESS_THAN", + "customSignalKey": "users", + "targetCustomSignalValues": [ + "100" + ] + } + }, + { + "customSignal": { + "customSignalOperator": "NUMERIC_GREATER_THAN", + "customSignalKey": "premium users", + "targetCustomSignalValues": [ + "20" + ] + }, + "percent": { + "percentOperator": "BETWEEN", + "seed": "cla24qoibb61", + "microPercentRange": { + "microPercentLowerBound": 25000000, + "microPercentUpperBound": 100000000 + } + } + } + ] + } + } + ] + } + } + } + ], + "parameters": { + "Percent": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "percent": { + "value": "Conditional value" + } + } + }, + "Custom": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "custom_signal": { + "value": "Conditional value" + } + } + }, + "Welcome Message": { + "defaultValue": { + "value": "Welcome" + } + }, + "Unset default value": { + "defaultValue": { + "value": "" + } + }, + "In-app default": { + "defaultValue": { + "useInAppDefault": true + }, + "conditionalValues": { + "percent": { + "value": "Conditional value" + } + } + }, + "Derived in-app default": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "percent": { + "useInAppDefault": true + } + } + }, + "Multiple conditions": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "custom_signal": { + "value": "Conditional value 1" + }, + "percent": { + "value": "Conditional value 2" + } + } + }, + "Chained conditions": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "chained_conditions": { + "value": "Conditional value" + } + } + } + }, + "version": { + "versionNumber": "17", + "updateOrigin": "ADMIN_SDK_NODE", + "updateType": "INCREMENTAL_UPDATE", + "updateUser": { + "email": "firebase-user@account.com", + "name": "dev-admin", + "imageUrl": "http://image.jpg" + }, + "updateTime": "2020-11-15T06:57:26.342763941Z", + "description": "promo config" + } +} \ No newline at end of file