[
  {
    "path": ".editorconfig",
    "content": "[*.{java,kt}]\nmax_line_length = 120\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: 🐞 Bug in the Firebase SDK\n    url: https://github.com/firebase/firebase-android-sdk/issues/new/choose\n    about: Do you think you have found a bug in the Firebase Android SDK?\n  - name: 🤖 MLKit On-Device Issues\n    url: https://github.com/googlesamples/mlkit\n    about: The MLKit on-device SDK has moved out of Firebase and the best place to get support is on their samples repository.\n  - name: 🔥 Firebase Support\n    url: https://firebase.google.com/support/\n    about: If you have an urgent issue with your app, please contact support.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/quickstart_issue.md",
    "content": "---\nname: ⚠️ Issue with the quickstart code\nabout:\n  Are you having issues running the code in this repository?\n---\n\n<!-- DO NOT DELETE \nvalidate_template=true\ntemplate_path=.github/ISSUE_TEMPLATE/quickstart_issue.md\n-->\n\n<!--\n\nAre you in the right place?\n\n  * If you think you have found a **bug in the Firebase Android SDK** please file the issue here:\n    https://github.com/firebase/firebase-android-sdk\n\n    * If you are filing an issue about **FCM in the background** make sure to read [#4](https://github.com/firebase/quickstart-android/issues/4) and [#89](https://github.com/firebase/quickstart-android/issues/89) first!\n  \n-->\n\n### Step 1: Describe your environment\n\n  * Android device: _____\n  * Android OS version: _____\n  * Google Play Services version: _____\n  * Firebase/Play Services SDK version: _____\n  \n### Step 2: Describe the problem:\n\n#### Steps to reproduce:\n\n  1. _____\n  2. _____\n  3. _____\n  \n#### Observed Results:\n\n  * What happened?  This could be a description, `logcat` output, etc.\n  \n#### Expected Results:\n\n  * What did you expect to happen?\n  \n#### Relevant Code:\n\n  ```\n  // TODO(you): code here to reproduce the problem\n  ```\n"
  },
  {
    "path": ".github/workflows/android.yml",
    "content": "name: Android CI\n\non:\n  pull_request:\n  push:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number  || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up JDK 17\n      uses: actions/setup-java@v3\n      with:\n        distribution: 'zulu'\n        java-version: 17\n    - name: Install Node to use the Firebase CLI\n      uses: actions/setup-node@v6\n      with:\n        node-version: 24\n    - name: Setup Gradle\n      uses: gradle/gradle-build-action@v2\n    - name: Check Snippets\n      run: python scripts/checksnippets.py\n    - name: Copy mock google_services.json\n      run: ./copy_mock_google_services_json.sh\n    - name: Build with Gradle (Pull Request)\n      run: ./build_pull_request.sh\n      if: github.event_name == 'pull_request'\n    - name: Build with Gradle (Push)\n      run: ./gradlew clean ktlint assemble\n      if: github.event_name != 'pull_request'"
  },
  {
    "path": ".gitignore",
    "content": ".gradle\nlocal.properties\n.idea\nbuild/\n.DS_Store\n*.iml\n*.apk\n*.aar\n*.zip\ngoogle-services.json\n\n.project\n.settings\n.classpath\n.vscode\n"
  },
  {
    "path": ".google/packaging.yaml",
    "content": "# GOOGLE SAMPLE PACKAGING DATA\n#\n# This file is used by Google as part of our samples packaging process.\n# End users may safely ignore this file. It has no relevance to other systems.\n---\nstatus:       PUBLISHED\ntechnologies: [Android, Firebase]\ncategories:   [Getting Started]\nlanguages:    [Java]\nsolutions:    [Mobile, Monetization, Startup, Enterprise]\ngithub:       firebase/quickstart-android\nbranch:       master\nlevel:        BEGINNER\nicon:         .google/icon.png\nlicense:      apache2\n"
  },
  {
    "path": ".opensource/project.json",
    "content": "\n{\n    \"name\": \"Firebase Quickstarts for Android\",\n    \"parent\": \"quickstarts\",\n    \"type\": \"sample\",\n\n    \"platforms\": [\n      \"Android\"\n    ],\n\n    \"content\": \"README.md\",\n    \n    \"pages\" : {\n      \"admob/README.md\": \"Admob\",\n      \"analytics/README.md\": \"Analytics\",\n      \"appdistribution/README.md\": \"App Distribution\",\n      \"app-indexing/README.md\": \"App Indexing\",\n      \"auth/README.md\": \"Authentication\",\n      \"config/README.md\": \"Remote Config\",\n      \"crash/README.md\": \"Crashlytics\",\n      \"database/README.md\": \"Realtime Database\",\n      \"dynamiclinks/README.md\": \"Dynamic Links\",\n      \"firestore/README.md\": \"Firestore\",\n      \"functions/README.md\": \"Cloud Functions\",\n      \"inappmessaging/README.md\": \"In App Messaging\",\n      \"messaging/README.md\": \"Cloud Messaging\",\n      \"mlkit/README.md\": \"ML Kit\",\n      \"perf/README.md\": \"Performance Monitoring\",\n      \"storage/README.md\": \"Cloud Storage\"\n    },\n\n    \"related\": [\n      \"firebase/quickstart-ios\",\n      \"firebase/quickstart-js\"\n    ],\n\n    \"tags\": []\n  }\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to the Firebase Android Quickstarts\n\nWe'd love for you to contribute to our source code and to make the Firebase Android Quickstarts even better than it is today! Here are the guidelines we'd like you to follow:\n\n - [Code of Conduct](#coc)\n - [Question or Problem?](#question)\n - [Issues and Bugs](#issue)\n - [Feature Requests](#feature)\n - [Submission Guidelines](#submit)\n - [Coding Rules](#rules)\n - [Signing the CLA](#cla)\n\n## <a name=\"coc\"></a> Code of Conduct\n\nAs contributors and maintainers of the Firebase Android Quickstarts project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.\n\nCommunication through any of Firebase's channels (GitHub, StackOverflow, Google+, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.\n\nWe promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the project to do the same.\n\nIf any member of the community violates this code of conduct, the maintainers of the Firebase  Android Quickstarts project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate.\n\nIf you are subject to or witness unacceptable behavior, or have any other concerns, please drop us a line at nivco@google.com.\n\n## <a name=\"question\"></a> Got a Question or Problem?\n\nIf you have questions about how to use the Firebase Android Quickstarts, please direct these to [StackOverflow][stackoverflow] and use the `firebase` tag. We are also available on GitHub issues.\n\nIf you feel that we're missing an important bit of documentation, feel free to\nfile an issue so we can help. Here's an example to get you started:\n\n```\nWhat are you trying to do or find out more about?\n\nWhere have you looked?\n\nWhere did you expect to find this information?\n```\n\n## <a name=\"issue\"></a> Found an Issue?\nIf you find a bug in the source code or a mistake in the documentation, you can help us by\nsubmitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request\nwith a fix.\n\nSee [below](#submit) for some guidelines.\n\n## <a name=\"submit\"></a> Submission Guidelines\n\n### Submitting an Issue\nBefore you submit your issue search the archive, maybe your question was already answered.\n\nIf your issue appears to be a bug, and hasn't been reported, open a new issue.  Please fill out\nall information in the issue template to maximize the chance that we can help you.\n\n**If you get help, help others. Good karma rulez!**\n\n### Submitting a Pull Request\nBefore you submit your pull request consider the following guidelines:\n\n* Search [GitHub](https://github.com/firebase/firebase-quickstart-web/pulls) for an open or closed Pull Request\n  that relates to your submission. You don't want to duplicate effort.\n* Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull\n  requests. We cannot accept code without this.\n* Make your changes in a new git branch:\n\n     ```shell\n     $ git checkout -b my-fix-branch master\n     ```\n\n* Create your patch, **including appropriate test cases**.\n* Follow our [Coding Rules](#rules).\n* Avoid checking in files that shouldn't be tracked (e.g `*.class`, `.idea`, `.tmp`). We recommend using a [global](#global-gitignore) gitignore for this.\n* Commit your changes using a descriptive commit message.\n\n     ```shell\n     $ git commit -a\n     ```\n  Note: the optional commit `-a` command line option will automatically \"add\" and \"rm\" edited files.\n\n* Build your changes locally to ensure all the tests pass:\n\n    ```shell\n   $ ./gradlew build\n    ```\n\n* Push your branch to GitHub:\n\n    ```shell\n    $ git push origin my-fix-branch\n    ```\n\n* In GitHub, send a pull request to `firebase-quickstart-android:master`.\n* If we suggest changes then:\n  * Make the required updates.\n  * Rebase your branch and force push to your GitHub repository (this will update your Pull Request):\n\n    ```shell\n    $ git rebase master -i\n    $ git push origin my-fix-branch -f\n    ```\n\nThat's it! Thank you for your contribution!\n\n#### After your pull request is merged\n\nAfter your pull request is merged, you can safely delete your branch and pull the changes\nfrom the main (upstream) repository:\n\n* Delete the remote branch on GitHub either through the GitHub Android UI or your local shell as follows:\n\n    ```shell\n    $ git push origin --delete my-fix-branch\n    ```\n\n* Check out the master branch:\n\n    ```shell\n    $ git checkout master -f\n    ```\n\n* Delete the local branch:\n\n    ```shell\n    $ git branch -D my-fix-branch\n    ```\n\n* Update your master with the latest upstream version:\n\n    ```shell\n    $ git pull --ff upstream master\n    ```\n\n## <a name=\"rules\"></a> Coding Rules\n\nTry to follow the same code style you see in the repository.\n\n## <a name=\"cla\"></a> Signing the CLA\n\nPlease sign our [Contributor License Agreement][google-cla] (CLA) before sending pull requests. For any code\nchanges to be accepted, the CLA must be signed. It's a quick process, we promise!\n\n*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).*\n\n[github]: https://github.com/firebase/quickstart-android\n[google-cla]: https://cla.developers.google.com\n[stackoverflow]: http://stackoverflow.com/questions/tagged/firebase\n[global-gitignore]: https://help.github.com/articles/ignoring-files/#create-a-global-gitignore\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2017 Google Inc\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n   All code in any directories or sub-directories that end with *.html or\n   *.css is licensed under the Creative Commons Attribution International\n   4.0 License, which full text can be found here:\n   https://creativecommons.org/licenses/by/4.0/legalcode.\n\n   As an exception to this license, all html or css that is generated by\n   the software at the direction of the user is copyright the user. The\n   user has full ownership and control over such content, including\n   whether and how they wish to license it.\n"
  },
  {
    "path": "README.md",
    "content": "# Firebase Quickstarts for Android\n\nA collection of quickstart samples demonstrating the Firebase APIs on Android. For more information, see https://firebase.google.com.\n\n## Samples\n\nYou can open each of the following samples as an Android Studio project, and run\nthem on a mobile device or a virtual device (AVD). When doing so you need to\nadd each sample app you wish to try to a Firebase project on the [Firebase\nconsole](https://console.firebase.google.com). You can add multiple sample apps\nto the same Firebase project. There's no need to create separate projects for\neach app.\n\nTo add a sample app to a Firebase project, use the `applicationId` value specified\nin the `app/build.gradle` file of the app as the Android package name. Download\nthe generated `google-services.json` file, and copy it to the `app/` directory of\nthe sample you wish to run.\n\n- [Admob](admob/README.md)\n- [Firebase AI Logic](firebase-ai/README.md)\n- [Analytics](analytics/README.md)\n- [App Distribution](appdistribution/README.md)\n- [Auth](auth/README.md)\n- [Remote Config](config/README.md)\n- [Crashlytics](crash/README.md)\n- [Realtime Database](database/README.md)\n- [Data Connect](dataconnect/README.md)\n- [Firestore](firestore/README.md)\n- [Cloud Functions for Firebase](functions/README.md)\n- [In-App Messaging](inappmessaging/README.md)\n- [Cloud Messaging](messaging/README.md)\n- [Performance Monitoring](perf/README.md)\n- [Cloud Storage for Firebase](storage/README.md)\n\n## How to make contributions?\nPlease read and follow the steps in the [CONTRIBUTING.md](CONTRIBUTING.md)\n\n[![Actions Status][gh-actions-badge]][gh-actions]\n[![SAM Score][sam-score-badge]][sam-score]\n\n[gh-actions]: https://github.com/firebase/quickstart-android/actions\n[gh-actions-badge]: https://github.com/firebase/quickstart-android/actions/workflows/android.yml/badge.svg?branch=master&event=push\n[sam-score]: https://ossbot.computer/samscore.html\n[sam-score-badge]: https://ossbot.computer/samscorebadge?org=firebase&repo=quickstart-android\n"
  },
  {
    "path": "admob/.gitignore",
    "content": ".gradle\n/local.properties\n.DS_Store\nbuild/\ngoogle-services.json\n\n# Android Studio\n.idea\n*.iml\n"
  },
  {
    "path": "admob/.google/packaging.yaml",
    "content": "# GOOGLE SAMPLE PACKAGING DATA\n#\n# This file is used by Google as part of our samples packaging process.\n# End users may safely ignore this file. It has no relevance to other systems.\n---\n# Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED}\nstatus:       DRAFT\n\n# Optional, put additional explanation here for DEPRECATED or SUPERCEDED.\n# statusNote:\n\n# See http://go/sample-categories\ntechnologies: [Android, Google Play Services, Google AdMob]\ncategories:   [Getting Started]\nlanguages:    [Java]\nsolutions:    [Mobile]\n\n# May be omitted if unpublished\n# github:       google/actionbar-basics\n\n# Values: BEGINNER | INTERMEDIATE | ADVANCED | EXPERT\nlevel:        BEGINNER\n\n# Dimensions: 512x512, PNG fomrat\nicon: app/src/main/res/drawable/ic_launcher_big.png\n\n# List of APIs that this sample should be listed under. Use authoritive,\n# names that are unique for the product in question. Examples:\n#\n# Android -    android:<class-name>\n# App Engine - gae-java:<class name>\n#              gae-python:<package>\n# Web Services - ws:<name of API from Cloud Console>\napiRefs:\n    - android:com.google.android.gms.ads\n\n# Default: apache2. May be omitted for most samples.\n# Alternatives: apache2-android (for AOSP)\nlicense: apache2\n"
  },
  {
    "path": "admob/README.md",
    "content": "AdMob by Google Quickstart\n=======================\n\nThe AdMob by Google Android quickstart demonstrates how to display an interstitial ad and\na banner ad.  AdRequest and AdView are used to display a banner ad\nand InterstitialAd is used to display the interstitial ad.\n\nIntroduction\n------------\n\n- [Read more about AdMob by Google](https://firebase.google.com/docs/admob/)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n- Configure your AdMob app id:\n  - In `src/main/res/values/strings.xml` change the `admob_app_id` string to your AdMob app id.\n  - Note that this ID is used in two places: `AndroidManifest.xml` and `MainActivity`\n- Run the sample on your Android device or emulator.\n- The running sample displays a test banner ad and a test interstitial ad.\n\nResult\n-----------\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/admob)\n- [Developer Forum](https://groups.google.com/group/google-admob-ads-sdk)\n- [Firebase Support](https://firebase.google.com/support/)\n\nLicense\n-------\n\nCopyright 2016 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "admob/app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.services)\n}\n\ntasks {\n    check.dependsOn(\"assembleDebugAndroidTest\")\n}\n\nandroid {\n    namespace = \"com.google.samples.quickstart.admobexample\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.samples.quickstart.admobexample\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n    packaging {\n        resources.excludes += \"LICENSE.txt\"\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n    buildFeatures {\n        viewBinding = true\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n    implementation(\"androidx.appcompat:appcompat:1.7.1\")\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.browser:browser:1.5.0\")\n    implementation(\"androidx.navigation:navigation-fragment-ktx:2.9.6\")\n    implementation(\"androidx.navigation:navigation-ui-ktx:2.9.6\")\n\n    implementation(\"com.google.android.gms:play-services-ads:23.3.0\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // For an optimal experience using AdMob, add the Firebase SDK\n    // for Google Analytics. This is recommended, but not required.\n    implementation(\"com.google.firebase:firebase-analytics\")\n\n    debugImplementation(\"androidx.fragment:fragment-testing:1.8.9\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test.ext:junit:1.3.0\")\n}\n"
  },
  {
    "path": "admob/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n"
  },
  {
    "path": "admob/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <!-- [SNIPPET modify_app_permissions]\n        Include required permissions for Google Mobile Ads to run.\n        [START modify_app_permissions] -->\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n\n    <!-- [END modify_app_permissions] -->\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\"\n        tools:ignore=\"AllowBackup,GoogleAppIndexingWarning\">\n\n        <!-- workaround for https://github.com/firebase/firebase-cpp-sdk/issues/1545 -->\n        <property\n            android:name=\"android.adservices.AD_SERVICES_CONFIG\"\n            android:resource=\"@xml/gma_ad_services_config\"\n            tools:replace=\"android:resource\" />\n\n        <!--\n            This metadata is required or your app will crash!\n        -->\n        <meta-data\n            android:name=\"com.google.android.gms.ads.APPLICATION_ID\"\n            android:value=\"@string/admob_app_id\" />\n\n        <activity\n            android:name=\".EntryChoiceActivity\"\n            android:label=\"@string/app_name\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity android:name=\".java.MainActivity\" />\n        <activity android:name=\".kotlin.MainActivity\" />\n        <!-- [SNIPPET add_activity_config_changes]\n            Include the AdActivity configChanges and theme.\n            [START add_activity_config_changes] -->\n        <activity\n            android:name=\"com.google.android.gms.ads.AdActivity\"\n            android:configChanges=\"keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize\"\n            android:theme=\"@android:style/Theme.Translucent\" />\n        <!-- [END add_activity_config_changes] -->\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "admob/app/src/main/java/com/google/samples/quickstart/admobexample/EntryChoiceActivity.kt",
    "content": "package com.google.samples.quickstart.admobexample\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Admob quickstart written in Java.\",\n                Intent(this, com.google.samples.quickstart.admobexample.java.MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Admob quickstart written in Kotlin.\",\n                Intent(this, com.google.samples.quickstart.admobexample.kotlin.MainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/FirstFragment.java",
    "content": "package com.google.samples.quickstart.admobexample.java;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport androidx.fragment.app.Fragment;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.android.gms.ads.AdRequest;\nimport com.google.android.gms.ads.AdView;\nimport com.google.android.gms.ads.FullScreenContentCallback;\nimport com.google.android.gms.ads.LoadAdError;\nimport com.google.android.gms.ads.MobileAds;\nimport com.google.android.gms.ads.interstitial.InterstitialAd;\nimport com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;\nimport com.google.samples.quickstart.admobexample.R;\nimport com.google.samples.quickstart.admobexample.databinding.FragmentFirstBinding;\n\nclass FirstFragment extends Fragment {\n\n    private static final String TAG = \"MainActivity\";\n    private static final String TEST_APP_ID = \"ca-app-pub-3940256099942544~3347511713\";\n\n    private AdView mAdView;\n    private InterstitialAd mInterstitialAd;\n    private Button mLoadInterstitialButton;\n    private FragmentFirstBinding binding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        binding = FragmentFirstBinding.inflate(inflater, container, false);\n        return binding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        checkIds();\n\n        // Initialize the Google Mobile Ads SDK\n        MobileAds.initialize(getContext());\n\n        mAdView = binding.adView;\n\n        requestNewInterstitial();\n\n        mLoadInterstitialButton = binding.loadInterstitialButton;\n        mLoadInterstitialButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mInterstitialAd != null) {\n                    mInterstitialAd.show(getActivity());\n                } else {\n                    beginSecondActivity();\n                }\n            }\n        });\n\n        // Disable button if an interstitial ad is not loaded yet.\n        mLoadInterstitialButton.setEnabled(mInterstitialAd != null);\n    }\n\n    /**\n     * Load a new interstitial ad asynchronously.\n     */\n    private void requestNewInterstitial() {\n        AdRequest adRequest = new AdRequest.Builder().build();\n        mAdView.loadAd(adRequest);\n\n        // AdMob ad unit IDs are not currently stored inside the google-services.json file.\n        // Developers using AdMob can store them as custom values in a string resource file or\n        // simply use constants. Note that the ad units used here are configured to return only test\n        // ads, and should not be used outside this sample.\n        InterstitialAd.load(getContext(), getString(R.string.interstitial_ad_unit_id), adRequest, new InterstitialAdLoadCallback() {\n            @Override\n            public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {\n                super.onAdLoaded(interstitialAd);\n                mInterstitialAd = interstitialAd;\n\n                // Ad received, ready to display\n                if (mLoadInterstitialButton != null) {\n                    mLoadInterstitialButton.setEnabled(true);\n                }\n\n                mInterstitialAd.setFullScreenContentCallback(new FullScreenContentCallback() {\n                    @Override\n                    public void onAdDismissedFullScreenContent() {\n                        super.onAdDismissedFullScreenContent();\n                        requestNewInterstitial();\n                        beginSecondActivity();\n                    }\n                });\n            }\n\n            @Override\n            public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {\n                super.onAdFailedToLoad(loadAdError);\n                mInterstitialAd = null;\n                Log.w(TAG, \"onAdFailedToLoad:\" + loadAdError.getMessage());\n            }\n        });\n    }\n\n    private void beginSecondActivity() {\n        NavHostFragment.findNavController(this).navigate(R.id.action_FirstFragment_to_SecondFragment);\n    }\n\n    /** Called when leaving the activity */\n    @Override\n    public void onPause() {\n        if (mAdView != null) {\n            mAdView.pause();\n        }\n        super.onPause();\n    }\n\n    /** Called when returning to the activity */\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (mAdView != null) {\n            mAdView.resume();\n        }\n        if (mInterstitialAd == null) {\n            requestNewInterstitial();\n        }\n    }\n\n    /** Called before the activity is destroyed */\n    @Override\n    public void onDestroy() {\n        if (mAdView != null) {\n            mAdView.destroy();\n        }\n        super.onDestroy();\n    }\n\n    @VisibleForTesting\n    public AdView getAdView() {\n        return mAdView;\n    }\n\n    private void checkIds() {\n        if (TEST_APP_ID.equals(getString(R.string.admob_app_id))) {\n            Log.w(TAG, \"Your admob_app_id is not configured correctly, please see the README\");\n        }\n    }\n}\n"
  },
  {
    "path": "admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/MainActivity.java",
    "content": "/**\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.samples.quickstart.admobexample.java;\n\nimport android.os.Bundle;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.navigation.Navigation;\n\nimport com.google.samples.quickstart.admobexample.R;\n\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(R.navigation.nav_graph_java);\n    }\n\n}\n"
  },
  {
    "path": "admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/SecondFragment.java",
    "content": "package com.google.samples.quickstart.admobexample.java;\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport androidx.fragment.app.Fragment;\nimport com.google.samples.quickstart.admobexample.R;\n\nclass SecondFragment extends Fragment {\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        return inflater.inflate(R.layout.fragment_second, container, false);\n    }\n\n}\n"
  },
  {
    "path": "admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/FirstFragment.kt",
    "content": "package com.google.samples.quickstart.admobexample.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Button\nimport androidx.fragment.app.Fragment\nimport androidx.navigation.fragment.findNavController\nimport com.google.android.gms.ads.AdRequest\nimport com.google.android.gms.ads.AdView\nimport com.google.android.gms.ads.FullScreenContentCallback\nimport com.google.android.gms.ads.LoadAdError\nimport com.google.android.gms.ads.MobileAds\nimport com.google.android.gms.ads.interstitial.InterstitialAd\nimport com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback\nimport com.google.samples.quickstart.admobexample.R\nimport com.google.samples.quickstart.admobexample.databinding.FragmentFirstBinding\n\nclass FirstFragment : Fragment() {\n\n    private var _binding: FragmentFirstBinding? = null\n    private val binding get() = _binding!!\n    private var interstitialAd: InterstitialAd? = null\n    private lateinit var adView: AdView\n    private lateinit var loadInterstitialButton: Button\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View {\n        _binding = FragmentFirstBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        adView = binding.adView\n        loadInterstitialButton = binding.loadInterstitialButton\n\n        checkIds()\n\n        // Initialize the Google Mobile Ads SDK\n        MobileAds.initialize(requireContext())\n\n        requestNewInterstitial()\n\n        loadInterstitialButton.setOnClickListener {\n            if (interstitialAd != null) {\n                interstitialAd?.show(requireActivity())\n            } else {\n                goToNextFragment()\n            }\n        }\n\n        // Disable button if an interstitial ad is not loaded yet.\n        loadInterstitialButton.isEnabled = interstitialAd != null\n    }\n\n    /**\n     * Load a new interstitial ad asynchronously.\n     */\n    private fun requestNewInterstitial() {\n        // AdMob ad unit IDs are not currently stored inside the google-services.json file.\n        // Developers using AdMob can store them as custom values in a string resource file or\n        // simply use constants. Note that the ad units used here are configured to return only test\n        // ads, and should not be used outside this sample.\n        val adRequest = AdRequest.Builder().build()\n\n        adView.loadAd(adRequest)\n\n        InterstitialAd.load(\n            requireContext(),\n            getString(R.string.interstitial_ad_unit_id),\n            adRequest,\n            object : InterstitialAdLoadCallback() {\n                override fun onAdLoaded(ad: InterstitialAd) {\n                    super.onAdLoaded(ad)\n                    interstitialAd = ad\n                    // Ad received, ready to display\n                    loadInterstitialButton.isEnabled = true\n\n                    interstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback() {\n                        override fun onAdDismissedFullScreenContent() {\n                            super.onAdDismissedFullScreenContent()\n                            goToNextFragment()\n                        }\n                    }\n                }\n\n                override fun onAdFailedToLoad(error: LoadAdError) {\n                    super.onAdFailedToLoad(error)\n                    interstitialAd = null\n                    Log.w(TAG, \"onAdFailedToLoad:${error.message}\")\n                }\n            },\n        )\n    }\n\n    private fun goToNextFragment() {\n        findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)\n    }\n\n    /** Called when leaving the activity  */\n    override fun onPause() {\n        adView.pause()\n        super.onPause()\n    }\n\n    /** Called when returning to the activity  */\n    override fun onResume() {\n        super.onResume()\n        adView.resume()\n        if (interstitialAd == null) {\n            requestNewInterstitial()\n        }\n    }\n\n    /** Called before the activity is destroyed  */\n    override fun onDestroy() {\n        adView.destroy()\n        super.onDestroy()\n    }\n\n    private fun checkIds() {\n        if (TEST_APP_ID == getString(R.string.admob_app_id)) {\n            Log.w(TAG, \"Your admob_app_id is not configured correctly, please see the README\")\n        }\n    }\n\n    companion object {\n        private const val TAG = \"FirstFragment\"\n        private const val TEST_APP_ID = \"ca-app-pub-3940256099942544~3347511713\"\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n}\n"
  },
  {
    "path": "admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/MainActivity.kt",
    "content": "package com.google.samples.quickstart.admobexample.kotlin\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.navigation.findNavController\nimport com.google.samples.quickstart.admobexample.R\n\nclass MainActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        findNavController(R.id.nav_host_fragment).setGraph(R.navigation.nav_graph_kotlin)\n    }\n}\n"
  },
  {
    "path": "admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/SecondFragment.kt",
    "content": "package com.google.samples.quickstart.admobexample.kotlin\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport com.google.samples.quickstart.admobexample.R\n\nclass SecondFragment : Fragment() {\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        return inflater.inflate(R.layout.fragment_second, container, false)\n    }\n}\n"
  },
  {
    "path": "admob/app/src/main/res/layout/activity_main.xml",
    "content": "<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n    tools:viewBindingIgnore=\"true\">\n\n    <fragment\n        android:id=\"@+id/nav_host_fragment\"\n        android:name=\"androidx.navigation.fragment.NavHostFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:defaultNavHost=\"true\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "admob/app/src/main/res/layout/fragment_first.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:ads=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <Button\n        android:id=\"@+id/loadInterstitialButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/interstitial_button_text\"\n        app:layout_constraintBottom_toTopOf=\"@+id/guideline\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/guideline\" />\n\n    <com.google.android.gms.ads.AdView\n        android:id=\"@+id/adView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        ads:adSize=\"BANNER\"\n        ads:adUnitId=\"@string/banner_ad_unit_id\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n\n    <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/guideline\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintGuide_percent=\"0.5\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "admob/app/src/main/res/layout/fragment_second.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:viewBindingIgnore=\"true\">\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerHorizontal=\"true\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        app:layout_constraintBottom_toTopOf=\"@+id/guideline\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/icon\"\n        android:layout_gravity=\"center_horizontal\"\n        android:text=\"@string/second_fragment_content\"\n        android:textAlignment=\"center\"\n        app:layout_constraintEnd_toEndOf=\"@+id/icon\"\n        app:layout_constraintStart_toStartOf=\"@+id/icon\"\n        app:layout_constraintTop_toBottomOf=\"@+id/icon\" />\n\n    <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/guideline\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintGuide_percent=\"0.5\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "admob/app/src/main/res/navigation/nav_graph_java.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/nav_graph_java\"\n    app:startDestination=\"@id/FirstFragment\">\n\n    <fragment\n        android:id=\"@+id/FirstFragment\"\n        android:name=\"com.google.samples.quickstart.admobexample.java.FirstFragment\"\n        android:label=\"@string/first_fragment_title\"\n        tools:layout=\"@layout/fragment_first\">\n\n        <action\n            android:id=\"@+id/action_FirstFragment_to_SecondFragment\"\n            app:destination=\"@id/SecondFragment\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/SecondFragment\"\n        android:name=\"com.google.samples.quickstart.admobexample.java.SecondFragment\"\n        android:label=\"@string/second_fragment_title\"\n        tools:layout=\"@layout/fragment_second\">\n    </fragment>\n</navigation>"
  },
  {
    "path": "admob/app/src/main/res/navigation/nav_graph_kotlin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/nav_graph_kotlin\"\n    app:startDestination=\"@id/FirstFragment\">\n\n    <fragment\n        android:id=\"@+id/FirstFragment\"\n        android:name=\"com.google.samples.quickstart.admobexample.kotlin.FirstFragment\"\n        android:label=\"@string/first_fragment_title\"\n        tools:layout=\"@layout/fragment_first\">\n\n        <action\n            android:id=\"@+id/action_FirstFragment_to_SecondFragment\"\n            app:destination=\"@id/SecondFragment\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/SecondFragment\"\n        android:name=\"com.google.samples.quickstart.admobexample.kotlin.SecondFragment\"\n        android:label=\"@string/second_fragment_title\"\n        tools:layout=\"@layout/fragment_second\">\n    </fragment>\n</navigation>"
  },
  {
    "path": "admob/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n</resources>\n"
  },
  {
    "path": "admob/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "admob/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AdMob Quickstart</string>\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"interstitial_button_text\">Show Interstitial Ad</string>\n    <string name=\"first_fragment_title\">FirstFragment</string>\n    <string name=\"second_fragment_title\">SecondFragment</string>\n    <string name=\"second_fragment_content\">This is the second fragment.</string>\n\n    <!--\n        TODO: Replace this with your own Admob App ID! This value is only a sample.\n    -->\n    <string name=\"admob_app_id\">ca-app-pub-3940256099942544~3347511713</string>\n\n    <string name=\"banner_ad_unit_id\">ca-app-pub-3940256099942544/6300978111</string>\n    <string name=\"interstitial_ad_unit_id\">ca-app-pub-3940256099942544/1033173712</string>\n</resources>\n"
  },
  {
    "path": "admob/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "admob/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "admob/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n"
  },
  {
    "path": "admob/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "admob/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.useAndroidX=true\n"
  },
  {
    "path": "admob/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "admob/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "admob/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "analytics/.gitignore",
    "content": ".gradle\n/local.properties\n.DS_Store\nbuild/\ngoogle-services.json\n\n# Android Studio\n.idea\n*.iml\n"
  },
  {
    "path": "analytics/README.md",
    "content": "Google Analytics for Firebase Quickstart\n========================================\n\nIntroduction\n------------\n\n- [Read more about Google Analytics for Firebase](https://firebase.google.com/docs/analytics)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n- Run the sample on Android device or emulator.\n\nResult\n-----------\nAfter running the app you should see a screen like this:\n\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nThe first time you run the app you will be asked what your\nfavorite food is. This choice will be logged to Firebase\nAnalytics as a [User Property][user-props].\n\nAs you swipe between tabs in the app, `SELECT_CONTENT` events \nare logged to Analytics. You can see these events in\nreal time using [Debug View][debug-view].\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-analytics)\n- [Firebase Support](https://firebase.google.com/support/)\n\nLicense\n-------\n\nCopyright 2016 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n\n[user-props]: https://firebase.google.com/docs/analytics/android/properties\n[debug-view]: https://firebase.google.com/docs/analytics/debugview\n"
  },
  {
    "path": "analytics/app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.services)\n}\n\ntasks {\n    check.dependsOn(\"assembleDebugAndroidTest\")\n}\n\nandroid {\n    namespace = \"com.google.firebase.quickstart.analytics\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.quickstart.analytics\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        multiDexEnabled = true\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.appcompat:appcompat:1.7.1\")\n    implementation(\"androidx.preference:preference-ktx:1.2.1\")\n    // Needed to override the version used by preference-ktx\n    implementation(\"androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Firebase Analytics\n    implementation(\"com.google.firebase:firebase-analytics\")\n\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test.ext:junit:1.3.0\")\n}\n"
  },
  {
    "path": "analytics/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n"
  },
  {
    "path": "analytics/app/src/androidTest/java/com/google/firebase/quickstart/analytics/MainActivityTest.java",
    "content": "package com.google.firebase.quickstart.analytics;\n\n\nimport androidx.annotation.StringRes;\nimport androidx.test.espresso.NoMatchingViewException;\nimport androidx.test.espresso.ViewInteraction;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.filters.LargeTest;\n\nimport com.google.firebase.quickstart.analytics.java.MainActivity;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.action.ViewActions.swipeLeft;\nimport static androidx.test.espresso.action.ViewActions.swipeRight;\nimport static androidx.test.espresso.assertion.ViewAssertions.matches;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static androidx.test.espresso.matcher.ViewMatchers.withId;\nimport static androidx.test.espresso.matcher.ViewMatchers.withParent;\nimport static androidx.test.espresso.matcher.ViewMatchers.withText;\nimport static org.hamcrest.Matchers.allOf;\n\n@LargeTest\n@RunWith(AndroidJUnit4.class)\npublic class MainActivityTest {\n\n    @Rule\n    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);\n\n    @Test\n    public void mainActivityTest() {\n        // Select favorite food if the dialog appears\n        selectFavoriteFoodIfPossible(\"Hot Dogs\");\n\n        // Initial title\n        checkTitleText(R.string.pattern1_title);\n\n        // Swipe, make sure we see the right titles\n        ViewInteraction viewPager = onView(withId(R.id.viewPager));\n        viewPager.check(matches(isDisplayed()));\n\n        // Swipe left\n        viewPager.perform(swipeLeft());\n        checkTitleText(R.string.pattern2_title);\n\n        // Swipe left\n        viewPager.perform(swipeLeft());\n        checkTitleText(R.string.pattern3_title);\n\n        // Swipe back right\n        viewPager.perform(swipeRight());\n        checkTitleText(R.string.pattern2_title);\n    }\n\n    private void checkTitleText(@StringRes int resId) {\n        onView(withText(resId))\n                .check(matches(isDisplayed()));\n    }\n\n    private void selectFavoriteFoodIfPossible(String food) {\n        try{\n            ViewInteraction appCompatTextView = onView(\n                    allOf(withId(android.R.id.text1), withText(food),\n                            withParent(allOf(withId(R.id.select_dialog_listview),\n                                    withParent(withId(R.id.contentPanel)))),\n                            isDisplayed()));\n            appCompatTextView.perform(click());\n        } catch (NoMatchingViewException e) {\n            // This is ok\n        }\n    }\n}\n"
  },
  {
    "path": "analytics/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity\n            android:name=\"com.google.firebase.quickstart.analytics.java.MainActivity\"/>\n\n        <activity\n            android:name=\"com.google.firebase.quickstart.analytics.kotlin.MainActivity\"/>\n\n        <activity\n            android:name=\"com.google.firebase.quickstart.analytics.EntryChoiceActivity\"\n            android:label=\"@string/app_name\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n\n    </application>\n</manifest>\n"
  },
  {
    "path": "analytics/app/src/main/java/com/google/firebase/quickstart/analytics/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.quickstart.analytics\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Analytics quickstart written in Java.\",\n                Intent(\n                    this,\n                    com.google.firebase.quickstart.analytics.java.MainActivity::class.java,\n                ),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Analytics quickstart written in Kotlin.\",\n                Intent(\n                    this,\n                    com.google.firebase.quickstart.analytics.kotlin.MainActivity::class.java,\n                ),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "analytics/app/src/main/java/com/google/firebase/quickstart/analytics/java/ImageFragment.java",
    "content": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.analytics.java;\n\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport androidx.fragment.app.Fragment;\n\nimport com.google.firebase.quickstart.analytics.R;\n\n\n/**\n * This fragment displays a featured, specified image.\n */\npublic class ImageFragment extends Fragment {\n    private static final String ARG_PATTERN = \"pattern\";\n\n    private int resId;\n\n    /**\n     * Create a {@link ImageFragment} displaying the given image.\n     *\n     * @param resId to display as the featured image\n     * @return a new instance of {@link ImageFragment}\n     */\n    public static ImageFragment newInstance(int resId) {\n        ImageFragment fragment = new ImageFragment();\n        Bundle args = new Bundle();\n        args.putInt(ARG_PATTERN, resId);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    public ImageFragment() {\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (getArguments() != null) {\n            resId = getArguments().getInt(ARG_PATTERN);\n        }\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater,\n                             ViewGroup container,\n                             Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.fragment_main, null);\n        ImageView imageView = view.findViewById(R.id.imageView);\n        imageView.setImageResource(resId);\n\n        return view;\n    }\n\n}\n"
  },
  {
    "path": "analytics/app/src/main/java/com/google/firebase/quickstart/analytics/java/ImageInfo.java",
    "content": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.analytics.java;\n\n/**\n * Pair of resource IDs representing an image and its title.\n */\npublic class ImageInfo {\n\n    public final int image;\n    public final int title;\n    public final int id;\n\n    /**\n     * Create a new ImageInfo.\n     *\n     * @param image resource of image\n     * @param title resource of title\n     * @param id resource of id\n     */\n    public ImageInfo(int image, int title, int id) {\n        this.image = image;\n        this.title = title;\n        this.id = id;\n    }\n\n}\n"
  },
  {
    "path": "analytics/app/src/main/java/com/google/firebase/quickstart/analytics/java/MainActivity.java",
    "content": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * For more information on setting up and running this sample code, see\n * https://firebase.google.com/docs/analytics/android\n */\n\npackage com.google.firebase.quickstart.analytics.java;\n\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.lifecycle.Lifecycle;\nimport androidx.preference.PreferenceManager;\nimport androidx.viewpager.widget.ViewPager;\nimport androidx.viewpager2.adapter.FragmentStateAdapter;\nimport androidx.viewpager2.widget.ViewPager2;\nimport com.google.android.material.tabs.TabLayout;\nimport com.google.android.material.tabs.TabLayoutMediator;\nimport com.google.firebase.analytics.FirebaseAnalytics;\nimport com.google.firebase.quickstart.analytics.R;\nimport com.google.firebase.quickstart.analytics.databinding.ActivityMainBinding;\nimport java.util.Locale;\n\n/**\n * Activity which displays numerous background images that may be viewed. These background images\n * are shown via {@link ImageFragment}.\n */\npublic class MainActivity extends AppCompatActivity {\n    private static final String TAG = \"MainActivity\";\n    private static final String KEY_FAVORITE_FOOD = \"favorite_food\";\n\n    private static final ImageInfo[] IMAGE_INFOS = {\n            new ImageInfo(R.drawable.favorite, R.string.pattern1_title, R.string.pattern1_id),\n            new ImageInfo(R.drawable.flash, R.string.pattern2_title, R.string.pattern2_id),\n            new ImageInfo(R.drawable.face, R.string.pattern3_title, R.string.pattern3_id),\n            new ImageInfo(R.drawable.whitebalance, R.string.pattern4_title, R.string.pattern4_id),\n    };\n\n    private ActivityMainBinding binding;\n\n    /**\n     * The {@link androidx.viewpager.widget.PagerAdapter} that will provide fragments for each image.\n     * This uses a {@link FragmentStateAdapter}, which keeps every loaded fragment in memory.\n     */\n    private ImagePagerAdapter mImagePagerAdapter;\n\n    /**\n     * The {@link ViewPager} that will host the patterns.\n     */\n    private ViewPager2 mViewPager;\n\n    /**\n     * The {@code FirebaseAnalytics} used to record screen views.\n     */\n    // [START declare_analytics]\n    private FirebaseAnalytics mFirebaseAnalytics;\n    // [END declare_analytics]\n\n    /**\n     * The user's favorite food, chosen from a dialog.\n     */\n    private String mFavoriteFood;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        // [START shared_app_measurement]\n        // Obtain the FirebaseAnalytics instance.\n        mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);\n        // [END shared_app_measurement]\n\n        // On first app open, ask the user his/her favorite food. Then set this as a user property\n        // on all subsequent opens.\n        String userFavoriteFood = getUserFavoriteFood();\n        if (userFavoriteFood == null) {\n            askFavoriteFood();\n        } else {\n            setUserFavoriteFood(userFavoriteFood);\n        }\n\n        // Create the adapter that will return a fragment for each image.\n        mImagePagerAdapter = new ImagePagerAdapter(getSupportFragmentManager(), IMAGE_INFOS, getLifecycle());\n\n        // Set up the ViewPager with the pattern adapter.\n        mViewPager = binding.viewPager;\n        mViewPager.setAdapter(mImagePagerAdapter);\n\n        mViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {\n            @Override\n            public void onPageSelected(int position) {\n                super.onPageSelected(position);\n                recordImageView();\n                recordScreenView();\n            }\n        });\n\n        TabLayout tabLayout = binding.tabLayout;\n\n        // When the visible image changes, send a screen view hit.\n        new TabLayoutMediator(tabLayout, mViewPager, new TabLayoutMediator.TabConfigurationStrategy() {\n            public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {\n               tab.setText(IMAGE_INFOS[position].title);\n            }\n        }).attach();\n\n        // Send initial screen screen view hit.\n        recordImageView();\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        recordScreenView();\n    }\n\n    /**\n     * Display a dialog prompting the user to pick a favorite food from a list, then record\n     * the answer.\n     */\n    private void askFavoriteFood() {\n        final String[] choices = getResources().getStringArray(R.array.food_items);\n        AlertDialog ad = new AlertDialog.Builder(this)\n                .setCancelable(false)\n                .setTitle(R.string.food_dialog_title)\n                .setItems(choices, new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog, int which) {\n                        String food = choices[which];\n                        setUserFavoriteFood(food);\n                    }\n                }).create();\n\n        ad.show();\n    }\n\n    /**\n     * Get the user's favorite food from shared preferences.\n     * @return favorite food, as a string.\n     */\n    private String getUserFavoriteFood() {\n        return PreferenceManager.getDefaultSharedPreferences(this)\n                .getString(KEY_FAVORITE_FOOD, null);\n    }\n\n    /**\n     * Set the user's favorite food as an app measurement user property and in shared preferences.\n     * @param food the user's favorite food.\n     */\n    private void setUserFavoriteFood(String food) {\n        Log.d(TAG, \"setFavoriteFood: \" + food);\n        mFavoriteFood = food;\n\n        PreferenceManager.getDefaultSharedPreferences(this).edit()\n                .putString(KEY_FAVORITE_FOOD, food)\n                .apply();\n\n        // [START user_property]\n        mFirebaseAnalytics.setUserProperty(\"favorite_food\", mFavoriteFood);\n        // [END user_property]\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.main, menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        int i = item.getItemId();\n        if (i == R.id.menu_share) {\n            String name = getCurrentImageTitle();\n            String text = \"I'd love you to hear about \" + name;\n\n            Intent sendIntent = new Intent();\n            sendIntent.setAction(Intent.ACTION_SEND);\n            sendIntent.putExtra(Intent.EXTRA_TEXT, text);\n            sendIntent.setType(\"text/plain\");\n            startActivity(sendIntent);\n\n            // [START custom_event]\n            Bundle params = new Bundle();\n            params.putString(\"image_name\", name);\n            params.putString(\"full_text\", text);\n            mFirebaseAnalytics.logEvent(\"share_image\", params);\n            // [END custom_event]\n        }\n        return false;\n    }\n\n    /**\n     * Return the title of the currently displayed image.\n     *\n     * @return title of image\n     */\n    private String getCurrentImageTitle() {\n        int position = mViewPager.getCurrentItem();\n        ImageInfo info = IMAGE_INFOS[position];\n        return getString(info.title);\n    }\n\n    /**\n     * Return the id of the currently displayed image.\n     *\n     * @return id of image\n     */\n    private String getCurrentImageId() {\n        int position = mViewPager.getCurrentItem();\n        ImageInfo info = IMAGE_INFOS[position];\n        return getString(info.id);\n    }\n\n    /**\n     * Record a screen view for the visible {@link ImageFragment} displayed\n     * inside {@link FragmentStateAdapter}.\n     */\n    private void recordImageView() {\n        String id =  getCurrentImageId();\n        String name = getCurrentImageTitle();\n\n        // [START image_view_event]\n        Bundle bundle = new Bundle();\n        bundle.putString(FirebaseAnalytics.Param.ITEM_ID, id);\n        bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, name);\n        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, \"image\");\n        mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);\n        // [END image_view_event]\n    }\n\n    /**\n     * This sample has a single Activity, so we need to manually record \"screen views\" as\n     * we change fragments.\n     */\n    private void recordScreenView() {\n        // This string must be <= 36 characters long.\n        String screenName = getCurrentImageId() + \"-\" + getCurrentImageTitle();\n\n        // [START set_current_screen]\n        Bundle bundle = new Bundle();\n        bundle.putString(FirebaseAnalytics.Param.SCREEN_NAME, screenName);\n        bundle.putString(FirebaseAnalytics.Param.SCREEN_CLASS, \"MainActivity\");\n        mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, bundle);\n        // [END set_current_screen]\n    }\n\n    /**\n     * A {@link FragmentStateAdapter} that returns a fragment corresponding to\n     * one of the sections/tabs/pages.\n     */\n    public class ImagePagerAdapter extends FragmentStateAdapter {\n\n        private final ImageInfo[] infos;\n\n        public ImagePagerAdapter(FragmentManager fm, ImageInfo[] infos, Lifecycle lifecyle) {\n            super(fm, lifecyle);\n            this.infos = infos;\n        }\n\n        public CharSequence getPageTitle(int position) {\n            if (position < 0 || position >= infos.length) {\n                return null;\n            }\n            Locale l = Locale.getDefault();\n            ImageInfo info = infos[position];\n            return getString(info.title).toUpperCase(l);\n        }\n\n        @NonNull\n        @Override\n        public Fragment createFragment(int position) {\n            ImageInfo info = infos[position];\n            return ImageFragment.newInstance(info.image);\n        }\n\n        @Override\n        public int getItemCount() {\n            return infos.length;\n        }\n    }\n}\n"
  },
  {
    "path": "analytics/app/src/main/java/com/google/firebase/quickstart/analytics/kotlin/ImageFragment.kt",
    "content": "package com.google.firebase.quickstart.analytics.kotlin\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport androidx.fragment.app.Fragment\nimport com.google.firebase.quickstart.analytics.R\n\n/**\n * This fragment displays a featured, specified image.\n */\nclass ImageFragment : Fragment() {\n\n    private var resId: Int = 0\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        arguments?.let {\n            resId = it.getInt(ARG_PATTERN)\n        }\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View? {\n        val view = inflater.inflate(R.layout.fragment_main, null)\n        val imageView = view.findViewById<ImageView>(R.id.imageView)\n        imageView.setImageResource(resId)\n\n        return view\n    }\n\n    companion object {\n        private const val ARG_PATTERN = \"pattern\"\n\n        /**\n         * Create a [ImageFragment] displaying the given image.\n         *\n         * @param resId to display as the featured image\n         * @return a new instance of [ImageFragment]\n         */\n        fun newInstance(resId: Int): ImageFragment {\n            val fragment = ImageFragment()\n            val args = Bundle()\n            args.putInt(ARG_PATTERN, resId)\n            fragment.arguments = args\n            return fragment\n        }\n    }\n}\n"
  },
  {
    "path": "analytics/app/src/main/java/com/google/firebase/quickstart/analytics/kotlin/ImageInfo.kt",
    "content": "package com.google.firebase.quickstart.analytics.kotlin\n\n/**\n * Pair of resource IDs representing an image and its title.\n */\ndata class ImageInfo(val image: Int, val title: Int, val id: Int)\n"
  },
  {
    "path": "analytics/app/src/main/java/com/google/firebase/quickstart/analytics/kotlin/MainActivity.kt",
    "content": "package com.google.firebase.quickstart.analytics.kotlin\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.Lifecycle\nimport androidx.preference.PreferenceManager\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.tabs.TabLayout\nimport com.google.android.material.tabs.TabLayoutMediator\nimport com.google.firebase.Firebase\nimport com.google.firebase.analytics.FirebaseAnalytics\nimport com.google.firebase.analytics.analytics\nimport com.google.firebase.analytics.logEvent\nimport com.google.firebase.quickstart.analytics.R\nimport com.google.firebase.quickstart.analytics.databinding.ActivityMainBinding\nimport com.google.firebase.quickstart.analytics.kotlin.MainActivity.Companion.IMAGE_INFOS\nimport java.util.Locale\n\n/**\n * Activity which displays numerous background images that may be viewed. These background images\n * are shown via {@link ImageFragment}.\n */\nclass MainActivity : AppCompatActivity() {\n    companion object {\n        private const val TAG = \"MainActivity\"\n        private const val KEY_FAVORITE_FOOD = \"favorite_food\"\n\n        private val IMAGE_INFOS = arrayOf(\n            ImageInfo(R.drawable.favorite, R.string.pattern1_title, R.string.pattern1_id),\n            ImageInfo(R.drawable.flash, R.string.pattern2_title, R.string.pattern2_id),\n            ImageInfo(R.drawable.face, R.string.pattern3_title, R.string.pattern3_id),\n            ImageInfo(R.drawable.whitebalance, R.string.pattern4_title, R.string.pattern4_id),\n        )\n    }\n\n    private lateinit var binding: ActivityMainBinding\n\n    /**\n     * The [androidx.viewpager2.widget.PagerAdapter] that will provide fragments for each image.\n     * This uses a [FragmentStateAdapter], which keeps every loaded fragment in memory.\n     */\n    private lateinit var imagePagerAdapter: ImagePagerAdapter\n\n    /**\n     * The `FirebaseAnalytics` used to record screen views.\n     */\n    // [START declare_analytics]\n    private lateinit var firebaseAnalytics: FirebaseAnalytics\n    // [END declare_analytics]\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        // [START shared_app_measurement]\n        // Obtain the FirebaseAnalytics instance.\n        firebaseAnalytics = Firebase.analytics\n        // [END shared_app_measurement]\n\n        // On first app open, ask the user his/her favorite food. Then set this as a user property\n        // on all subsequent opens.\n        val userFavoriteFood = getUserFavoriteFood()\n        if (userFavoriteFood == null) {\n            askFavoriteFood()\n        } else {\n            setUserFavoriteFood(userFavoriteFood)\n        }\n\n        // Create the adapter that will return a fragment for each image.\n        imagePagerAdapter = ImagePagerAdapter(supportFragmentManager, IMAGE_INFOS, lifecycle)\n\n        // Set up the ViewPager with the pattern adapter.\n        binding.viewPager.adapter = imagePagerAdapter\n\n        val pageChangedCallback = object : ViewPager2.OnPageChangeCallback() {\n            override fun onPageSelected(position: Int) {\n                recordImageView()\n                recordScreenView()\n            }\n        }\n\n        binding.viewPager.registerOnPageChangeCallback(pageChangedCallback)\n\n        val tabLayout: TabLayout = binding.tabLayout\n        TabLayoutMediator(tabLayout, binding.viewPager) { tab, position ->\n            tab.setText(IMAGE_INFOS[position].title)\n        }.attach()\n\n        // Send initial screen screen view hit.\n        recordImageView()\n    }\n\n    public override fun onResume() {\n        super.onResume()\n        recordScreenView()\n    }\n\n    /**\n     * Display a dialog prompting the user to pick a favorite food from a list, then record\n     * the answer.\n     */\n    private fun askFavoriteFood() {\n        val choices = resources.getStringArray(R.array.food_items)\n        val ad = AlertDialog.Builder(this)\n            .setCancelable(false)\n            .setTitle(R.string.food_dialog_title)\n            .setItems(choices) { _, which ->\n                val food = choices[which]\n                setUserFavoriteFood(food)\n            }.create()\n\n        ad.show()\n    }\n\n    /**\n     * Get the user's favorite food from shared preferences.\n     * @return favorite food, as a string.\n     */\n    private fun getUserFavoriteFood(): String? {\n        return PreferenceManager.getDefaultSharedPreferences(this)\n            .getString(KEY_FAVORITE_FOOD, null)\n    }\n\n    /**\n     * Set the user's favorite food as an app measurement user property and in shared preferences.\n     * @param food the user's favorite food.\n     */\n    private fun setUserFavoriteFood(food: String) {\n        Log.d(TAG, \"setFavoriteFood: $food\")\n\n        PreferenceManager.getDefaultSharedPreferences(this).edit()\n            .putString(KEY_FAVORITE_FOOD, food)\n            .apply()\n\n        // [START user_property]\n        firebaseAnalytics.setUserProperty(\"favorite_food\", food)\n        // [END user_property]\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.main, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        val i = item.itemId\n        if (i == R.id.menu_share) {\n            val name = getCurrentImageTitle()\n            val text = \"I'd love you to hear about $name\"\n\n            val sendIntent = Intent()\n            sendIntent.action = Intent.ACTION_SEND\n            sendIntent.putExtra(Intent.EXTRA_TEXT, text)\n            sendIntent.type = \"text/plain\"\n            startActivity(sendIntent)\n\n            // [START custom_event]\n            firebaseAnalytics.logEvent(\"share_image\") {\n                param(\"image_name\", name)\n                param(\"full_text\", text)\n            }\n            // [END custom_event]\n        }\n        return false\n    }\n\n    /**\n     * Return the title of the currently displayed image.\n     *\n     * @return title of image\n     */\n    private fun getCurrentImageTitle(): String {\n        val position = binding.viewPager.currentItem\n        val info = IMAGE_INFOS[position]\n        return getString(info.title)\n    }\n\n    /**\n     * Return the id of the currently displayed image.\n     *\n     * @return id of image\n     */\n    private fun getCurrentImageId(): String {\n        val position = binding.viewPager.currentItem\n        val info = IMAGE_INFOS[position]\n        return getString(info.id)\n    }\n\n    /**\n     * Record a screen view for the visible [ImageFragment] displayed\n     * inside [FragmentStateAdapter].\n     */\n    private fun recordImageView() {\n        val id = getCurrentImageId()\n        val name = getCurrentImageTitle()\n\n        // [START image_view_event]\n        firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_ITEM) {\n            param(FirebaseAnalytics.Param.ITEM_ID, id)\n            param(FirebaseAnalytics.Param.ITEM_NAME, name)\n            param(FirebaseAnalytics.Param.CONTENT_TYPE, \"image\")\n        }\n        // [END image_view_event]\n    }\n\n    /**\n     * This sample has a single Activity, so we need to manually record \"screen views\" as\n     * we change fragments.\n     */\n    private fun recordScreenView() {\n        // This string must be <= 36 characters long.\n        val screenName = \"${getCurrentImageId()}-${getCurrentImageTitle()}\"\n\n        // [START set_current_screen]\n        firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) {\n            param(FirebaseAnalytics.Param.SCREEN_NAME, screenName)\n            param(FirebaseAnalytics.Param.SCREEN_CLASS, \"MainActivity\")\n        }\n        // [END set_current_screen]\n    }\n\n    /**\n     * A [FragmentStateAdapter] that returns a fragment corresponding to\n     * one of the sections/tabs/pages.\n     */\n    inner class ImagePagerAdapter(\n        fm: FragmentManager,\n        private val infos: Array<ImageInfo>,\n        lifecyle: Lifecycle,\n    ) : FragmentStateAdapter(fm, lifecyle) {\n\n        fun getPageTitle(position: Int): CharSequence? {\n            if (position < 0 || position >= infos.size) {\n                return null\n            }\n            val l = Locale.getDefault()\n            val info = infos[position]\n            return getString(info.title).uppercase(l)\n        }\n\n        override fun getItemCount(): Int = infos.size\n\n        override fun createFragment(position: Int): Fragment {\n            val info = infos[position]\n            return ImageFragment.newInstance(info.image)\n        }\n    }\n}\n"
  },
  {
    "path": "analytics/app/src/main/res/drawable/circle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"oval\">\n    <solid android:color=\"#fff\" />\n</shape>\n"
  },
  {
    "path": "analytics/app/src/main/res/layout/activity_main.xml",
    "content": "<!--\nCopyright 2015 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tab_layout\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.viewpager2.widget.ViewPager2\n        android:id=\"@+id/viewPager\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tab_layout\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "analytics/app/src/main/res/layout/fragment_main.xml",
    "content": "<!--\nCopyright 2015 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:tools=\"http://schemas.android.com/tools\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n                android:paddingRight=\"@dimen/activity_horizontal_margin\"\n                android:paddingTop=\"@dimen/activity_vertical_margin\"\n                android:paddingBottom=\"@dimen/activity_vertical_margin\"\n                tools:viewBindingIgnore=\"true\"\n                tools:context=\"com.google.firebase.quickstart.analytics.java.ImageFragment\">\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/imageView\"\n        android:minHeight=\"256dp\"\n        android:minWidth=\"256dp\"\n        android:layout_gravity=\"center\"\n        android:elevation=\"2dp\"\n        android:scaleType=\"center\"\n        android:background=\"@drawable/circle\"/>\n\n</FrameLayout>\n"
  },
  {
    "path": "analytics/app/src/main/res/menu/main.xml",
    "content": "<!--\nCopyright 2015 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item android:id=\"@+id/menu_share\" android:title=\"@string/menu_share\" app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "analytics/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n</resources>\n"
  },
  {
    "path": "analytics/app/src/main/res/values/dimens.xml",
    "content": "<!--\nCopyright 2015 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<resources>\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "analytics/app/src/main/res/values/strings.xml",
    "content": "<!--\nCopyright 2015 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<resources>\n    <string name=\"app_name\">Firebase Analytics</string>\n    <string name=\"pattern1_title\">A</string>\n    <string name=\"pattern2_title\">B</string>\n    <string name=\"pattern3_title\">C</string>\n    <string name=\"pattern4_title\">D</string>\n    <string name=\"pattern1_id\">id-A</string>\n    <string name=\"pattern2_id\">id-B</string>\n    <string name=\"pattern3_id\">id-C</string>\n    <string name=\"pattern4_id\">id-D</string>\n    <string name=\"menu_share\">Share</string>\n\n    <string name=\"food_dialog_title\">Which food is your favorite?</string>\n    <array name=\"food_items\">\n        <item>Hot Dogs</item>\n        <item>Hamburgers</item>\n        <item>Pizza</item>\n    </array>\n</resources>\n"
  },
  {
    "path": "analytics/app/src/main/res/values/styles.xml",
    "content": "<!--\nCopyright 2015 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "analytics/app/src/main/res/values-v21/styles.xml",
    "content": "<!--\nCopyright 2015 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"android:colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "analytics/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<!--\nCopyright 2015 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<resources>\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "analytics/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n         //mavenLocal() must be listed at the top to facilitate testing\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n"
  },
  {
    "path": "analytics/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "analytics/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.useAndroidX=true"
  },
  {
    "path": "analytics/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "analytics/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "analytics/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "appdistribution/.gitignore",
    "content": ".gradle\nlocal.properties\n.idea\nbuild/\n.DS_Store\n*.iml\n*.apk\n*.aar\n*.zip\ngoogle-services.json\n"
  },
  {
    "path": "appdistribution/README.md",
    "content": "# Firebase App Distribution Quickstart\n\n## Introduction\n\nThe Firebase App Distribution SDK enables you to display in-app alerts to your testers when new builds of your app are available to install. This quickstart aims to showcase how to use the App Distribution SDK to create and customize new build alerts for your testers. You can read more \nabout Firebase App Distribution [here](https://firebase.google.com/docs/app-distribution)!\n\n## Getting Started\n\n  * Follow the instructions to [add Firebase to your Android app][add-firebase-android].\n\n## Result\n\n<img src=\"docs/result.png\" height=\"600\" />\n\n[add-firebase-android]: https://firebase.google.com/docs/android/setup\n"
  },
  {
    "path": "appdistribution/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "appdistribution/app/build.gradle.kts",
    "content": "\nplugins {\n    id(\"com.android.application\")\n    id(\"com.google.gms.google-services\")\n}\n\nandroid {\n    namespace = \"com.google.firebase.appdistributionquickstart\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.appdistributionquickstart\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n\n        multiDexEnabled = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n    buildFeatures {\n        viewBinding = true\n    }\n    lint {\n        warning.add(\"InvalidPackage\")\n        // TODO(thatfiredev): Remove this once\n        //  https://github.com/bumptech/glide/issues/4940 is fixed\n        //  (and the same change gets applied to App Distribution)\n        disable.add(\"NotificationPermission\")\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.constraintlayout:constraintlayout:2.2.1\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // ADD the SDK to the \"prerelease\" variant only (example)\n    implementation(\"com.google.firebase:firebase-appdistribution:16.0.0-beta17\")\n\n    // For an optimal experience using App Distribution, add the Firebase SDK\n    // for Google Analytics. This is recommended, but not required.\n    implementation(\"com.google.firebase:firebase-analytics\")\n\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test.uiautomator:uiautomator:2.3.0\")\n}\n"
  },
  {
    "path": "appdistribution/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "appdistribution/app/src/androidTest/java/com/google/firebase/appdistributionquickstart/InstrumentedTest.java",
    "content": "package com.google.firebase.appdistributionquickstart;\n\nimport android.os.RemoteException;\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.test.espresso.Root;\nimport androidx.test.espresso.ViewInteraction;\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.runner.AndroidJUnit4;\nimport androidx.test.uiautomator.UiDevice;\n\nimport org.hamcrest.Matcher;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static androidx.test.InstrumentationRegistry.getInstrumentation;\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.assertion.ViewAssertions.matches;\nimport static androidx.test.espresso.matcher.RootMatchers.withDecorView;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static androidx.test.espresso.matcher.ViewMatchers.withId;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.not;\n\nimport com.google.firebase.appdistributionquickstart.java.MainActivity;\n\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n */\n@RunWith(AndroidJUnit4.class)\npublic class InstrumentedTest {\n\n  @Rule\n  public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);\n\n  private Matcher<Root> rootMatcher;\n\n  @Before\n  public void setUp() {\n    rootMatcher = withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView())));\n  }\n\n  @After\n  public void tearDown() {\n      getView(R.id.collapse_button).perform(click());\n  }\n\n\n  @Test\n  public void testFiamDisplaysOnForegroundCampaign() {\n    reopen_app(); // reopen app to correctly trigger fetch\n    getView(R.id.modal_root).check(matches(isDisplayed()));\n  }\n\n  @Test\n  public void testFiamDisplaysContextualTriggerCampaign() {\n    onView(withId(R.id.eventTriggerButton)).perform(click());\n    getView(R.id.modal_root).check(matches(isDisplayed()));\n  }\n\n  private void reopen_app() {\n    press_recent();\n    press_back();\n  }\n\n  private void press_recent() {\n    try {\n      UiDevice.getInstance(getInstrumentation()).pressRecentApps();\n    } catch (RemoteException e) {\n      e.printStackTrace();\n    }\n    sleep();\n  }\n\n  private void press_back() {\n    UiDevice.getInstance(getInstrumentation()).pressBack();\n    sleep();\n\n  }\n\n  private void sleep() {\n    try {\n      Thread.sleep(3000);\n    } catch (Exception e) {\n      e.printStackTrace();\n    }\n  }\n\n  @NonNull\n  private ViewInteraction getView(@IdRes int id) {\n    return onView(withId(id)).inRoot(rootMatcher);\n  }\n}\n"
  },
  {
    "path": "appdistribution/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity\n            android:name=\".java.MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme\">\n        </activity>\n\n        <activity\n            android:name=\".kotlin.KotlinMainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme\">\n        </activity>\n\n        <activity android:name=\".EntryChoiceActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "appdistribution/app/src/main/java/com/google/firebase/appdistributionquickstart/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.appdistributionquickstart\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\nimport com.google.firebase.appdistributionquickstart.java.MainActivity\nimport com.google.firebase.appdistributionquickstart.kotlin.KotlinMainActivity\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase App Distribution quickstart written in Java.\",\n                Intent(this, MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase App Distribution quickstart written in Kotlin.\",\n                Intent(this, KotlinMainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "appdistribution/app/src/main/java/com/google/firebase/appdistributionquickstart/java/MainActivity.java",
    "content": "package com.google.firebase.appdistributionquickstart.java;\n\nimport android.os.Bundle;\n\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport com.google.firebase.appdistribution.FirebaseAppDistribution;\nimport com.google.firebase.appdistribution.FirebaseAppDistributionException;\nimport com.google.firebase.appdistributionquickstart.databinding.ActivityMainBinding;\n\npublic class MainActivity extends AppCompatActivity {\n\n    private static final String TAG = \"AppDistribution-Quickstart\";\n\n    private FirebaseAppDistribution mFirebaseAppDistribution;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        final ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        mFirebaseAppDistribution = FirebaseAppDistribution.getInstance();\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        mFirebaseAppDistribution.updateIfNewReleaseAvailable()\n                .addOnProgressListener(updateProgress -> {\n                    // (Optional) Implement custom progress updates in addition to\n                    // automatic NotificationManager updates.\n                })\n                .addOnFailureListener(e -> {\n                    if (e instanceof FirebaseAppDistributionException) {\n                        // Handle exception.\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "appdistribution/app/src/main/java/com/google/firebase/appdistributionquickstart/kotlin/KotlinMainActivity.kt",
    "content": "package com.google.firebase.appdistributionquickstart.kotlin\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport com.google.firebase.appdistribution.FirebaseAppDistribution\nimport com.google.firebase.appdistribution.FirebaseAppDistributionException\nimport com.google.firebase.appdistribution.appDistribution\nimport com.google.firebase.appdistributionquickstart.databinding.ActivityMainBinding\nimport com.google.firebase.Firebase\n\nclass KotlinMainActivity : AppCompatActivity() {\n\n    private lateinit var firebaseAppDistribution: FirebaseAppDistribution\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        firebaseAppDistribution = Firebase.appDistribution\n    }\n\n    override fun onResume() {\n        super.onResume()\n        firebaseAppDistribution.updateIfNewReleaseAvailable()\n            .addOnProgressListener { updateProgress ->\n                // (Optional) Implement custom progress updates in addition to\n                // automatic NotificationManager updates.\n            }\n            .addOnFailureListener { e ->\n                if (e is FirebaseAppDistributionException) {\n                    // Handle exception.\n                }\n            }\n    }\n\n    companion object {\n\n        private const val TAG = \"AppDistribution-Quickstart\"\n    }\n}\n"
  },
  {
    "path": "appdistribution/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n    android:height=\"108dp\"\n    android:width=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#26A69A\"\n          android:pathData=\"M0,0h108v108h-108z\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M9,0L9,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,0L19,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M29,0L29,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M39,0L39,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M49,0L49,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M59,0L59,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M69,0L69,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M79,0L79,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M89,0L89,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M99,0L99,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,9L108,9\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,19L108,19\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,29L108,29\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,39L108,39\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,49L108,49\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,59L108,59\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,69L108,69\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,79L108,79\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,89L108,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,99L108,99\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,29L89,29\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,39L89,39\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,49L89,49\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,59L89,59\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,69L89,69\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,79L89,79\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M29,19L29,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M39,19L39,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M49,19L49,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M59,19L59,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M69,19L69,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M79,19L79,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n</vector>\n"
  },
  {
    "path": "appdistribution/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"78.5885\"\n                android:endY=\"90.9159\"\n                android:startX=\"48.7653\"\n                android:startY=\"61.0927\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\" />\n</vector>\n"
  },
  {
    "path": "appdistribution/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\"\n    tools:context=\".MainActivity\">\n\n    <ImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/textView\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:text=\"@string/textview_text\"\n        android:textSize=\"18sp\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/imageView\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "appdistribution/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n\n    <color name=\"primary_text\">#212121</color>\n    <color name=\"secondary_text\">#727272</color>\n    <color name=\"icons\">#212121</color>\n    <color name=\"divider\">#B6B6B6</color>\n\n    <color name=\"grey_500\">#9E9E9E</color>\n</resources>\n"
  },
  {
    "path": "appdistribution/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <dimen name=\"fab_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "appdistribution/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">App Distribution Quickstart</string>\n    <string name=\"textview_text\">Welcome to the Firebase App Distribution Quickstart app. Press the button to trigger an analytics event!</string>\n\n    <string name=\"installation_id_fmt\">Firebase Installation ID: %s</string>\n</resources>\n"
  },
  {
    "path": "appdistribution/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n    <!-- Dark Buttons -->\n    <style name=\"ThemeOverlay.MyDarkButton\" parent=\"ThemeOverlay.AppCompat.Dark\">\n        <item name=\"colorButtonNormal\">@color/colorAccent</item>\n        <item name=\"android:layout_marginRight\">4dp</item>\n        <item name=\"android:layout_marginLeft\">4dp</item>\n        <item name=\"android:textColor\">@android:color/white</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "appdistribution/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "appdistribution/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "appdistribution/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "appdistribution/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "appdistribution/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "appdistribution/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "auth/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n\nservice_account.json\n"
  },
  {
    "path": "auth/README.md",
    "content": "Firebase Auth Quickstart\n==============================\n\nIntroduction\n------------\n\n- [Read more about Firebase Auth](https://firebase.google.com)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n\n\n### Google Sign In Setup\n\n- Go to the [Firebase Console][fir-console] and navigate to your project:\n  - Select the **Auth** panel and then click the **Sign In Method** tab.\n  - Click **Google** and turn on the **Enable** switch, then click **Save**.\n- Run the sample app on your device or emulator.\n    - Select **GoogleSignInActivity** from the main screen.\n    - Click the **Sign In** button to begin.\n\n\n### Facebook Login Setup\n\n- Go to the [Meta for Developers Site](https://developers.facebook.com) and follow all\n  instructions to set up a new Android app.\n  - When asked for a package name, use\n  `com.google.firebase.quickstart.auth`.\n  - When asked for a main class name,\n  use `com.google.firebase.quickstart.auth.kotlin.MainActivity`.\n- Go to the [Firebase Console][fir-console] and navigate to your project:\n  - Select the **Auth** panel and then click the **Sign In Method** tab.\n  - Click **Facebook** and turn on the **Enable** switch, then click **Save**.\n  - Enter your Facebook **App Id** and **App Secret** and click **Save**.\n- Open the file `app/src/main/res/values/ids.xml` and replace the values of `facebook_app_id` and `facebook_client_token` with the ID of the Facebook app you just created and the client token respectively.\n- Run the app on your device or emulator.\n    - Select the **FacebookLoginFragment** from the main screen.\n    - Click the **Sign In** button to begin.\n    - If you see text that says Facebook is disabled, make sure you are running\n      either the **facebookDebug** or **facebookRelease** variants of this sample.\n\n### Email/Password Setup\n\n- Go to the [Firebase Console][fir-console] and navigate to your project:\n  - Select the **Auth** panel and then click the **Sign In Method** tab.\n  - Click **Email/Password** and turn on the **Enable** switch, then click **Save**.\n  - Under **Authorized Domains** click **Add Domain** and add `auth.example.com`.\n- Run the app on your device or emulator.\n    - Select **EmailPasswordActivity** from the main screen.\n    - Fill in your desired email and password and click **Create Account** to begin.\n\n### Passwordless Setup\n\n- Go to the [Firebase Console][fir-console] and navigate to your project:\n  - Select the **Auth** panel and then click the **Sign In Method** tab.\n  - Click **Email/Password** and turn on the **Enable** switch. \n  - Turn on the **Email Link (passwordless sign-in)** switch, then click **Save**.\n- Replace your-project-id in the AndroidManifest.xml file with your project ID.\n- Run the app on your device or emulator.\n    - Select **PasswordlessActivity** from the main screen.\n    - Fill in your desired email and click **Send Link** to begin.\n\n\n### Phone Authentication Setup\n\n- Go to the [Firebase Console][fir-console] and navigate to your project:\n  - Select the **Auth** panel and then click the **Sign In Method** tab.\n  - Click **Phone** and turn on the **Enable** switch, then click **Save**.\n- Run the app on your physical device:\n    - Select **PhoneAuthActivity** from the main screen.\n    - Enter your phone number and click **Verify** to begin.\n\n### Custom Authentication Setup\n\n- Go to the [Google Developers Console](https://console.developers.google.com/project) and navigate to your project:\n    - Click on the **Service accounts** tab in the left.\n    - Click on the **Create Service Account** on the top.\n    - Enter desired service account name and click on the **Create** button.\n    - Once the service account is created, click on the **Options**.\n    - Choose **JSON** as the key type then click on the **Create** button.\n    - You should now have a new JSON file for your service account in your Downloads directory.\n- Open the file `web/auth.html` using your web browser.\n    - Click **Choose File** and upload the JSON file you just downloaded.\n    - Enter any User ID and click **Generate**.\n    - Copy the text from the **ADB Command** section. This will be required later on.\n- Run the Android application on your Android device or emulator.\n    - Select **CustomAuthActivity** from the main screen.\n    - Run the text copied from the **ADB Command** section of the web page in the steps above. This will update the Custom Token field of the running app.\n    - Click **Sign In** to sign in to Firebase User Management with the generated JWT. You should\n      see the User ID you entered when generating the token.\n\n### Generic OAuth Sign In (Microsoft, Apple, Yahoo, Twitter, etc)\n\n#### Microsoft\n\n- Follow the [instructions](https://firebase.google.com/docs/auth/android/microsoft-oauth#before_you_begin)\n  to enable Microsoft authentication in the Firebase console.\n- Run the Android application on your Android device or emulator.\n    - Select **GenericIdpActivity** from the main screen.\n    - Select **Microsoft** in the dropdown.\n    - Hit the sign in button and proceed through the login flow.\n\n#### Apple\n\n- Follow the [instructions](https://firebase.google.com/docs/auth/android/apple-oauth#before_you_begin)\n  to enable Apple authentication in the Firebase console.\n- Run the Android application on your Android device or emulator.\n    - Select **GenericIdpActivity** from the main screen.\n    - Select **Apple** in the dropdown.\n    - Hit the sign in button and proceed through the login flow.\n\n#### Yahoo\n\n- Follow the [instructions](https://firebase.google.com/docs/auth/android/yahoo-oauth#before_you_begin)\n  to enable Yahoo authentication in the Firebase console.\n- Run the Android application on your Android device or emulator.\n    - Select **GenericIdpActivity** from the main screen.\n    - Select **Yahoo** in the dropdown.\n    - Hit the sign in button and proceed through the login flow.\n\n#### Twitter\n\n- Follow the [instructions](https://firebase.google.com/docs/auth/android/twitter-login#before_you_begin)\n  to enable Twitter authentication in the Firebase console.\n- Run the Android application on your Android device or emulator.\n    - Select **GenericIdpActivity** from the main screen.\n    - Select **Twitter** in the dropdown.\n    - Hit the sign in button and proceed through the login flow.\n\n### Multi Factor Authentication\n\n**Note**: Multi Factor authentication only works for apps using\n[Google Cloud Identity Platform](https://cloud.google.com/identity-platform/docs/android/mfa),\na paid service. If you are only using Firebase Authentication this sample will not work for you.\n\n- Run the app on your physical device (emulators will not work)\n    - Select **MultiFactorAuthActivity** from the main screen.\n    - Sign in (if necessary).\n    - Verify your email (if necessary).\n    - Hit **Enroll MFA** to begin enrolling an SMS second factor.\n\n\nResult\n-----------\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-authentication)\n- [Firebase Support](https://firebase.google.com/support/)\n\nLicense\n-------\n\nCopyright 2020 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n\n[fir-console]: https://console.firebase.google.com\n"
  },
  {
    "path": "auth/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "auth/app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.services)\n}\n\ntasks {\n    check.dependsOn(\"assembleDebugAndroidTest\")\n}\n\nandroid {\n    namespace= \"com.google.firebase.quickstart.auth\"\n    compileSdk = 36\n    flavorDimensions += \"minSdkVersion\"\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.quickstart.auth\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n        buildConfig = true\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:chooserx\"))\n    implementation(project(\":internal:lintchecks\"))\n\n    implementation(\"androidx.activity:activity-ktx:1.12.1\")\n    implementation(\"androidx.constraintlayout:constraintlayout:2.2.1\")\n    implementation(\"androidx.vectordrawable:vectordrawable-animated:1.2.0\")\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.navigation:navigation-fragment-ktx:2.9.6\")\n    implementation(\"androidx.navigation:navigation-ui-ktx:2.9.6\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Firebase Authentication\n    implementation(\"com.google.firebase:firebase-auth\")\n\n    // Google Identity Services SDK (only required for Auth with Google)\n    implementation(\"androidx.credentials:credentials:1.5.0\")\n    implementation(\"androidx.credentials:credentials-play-services-auth:1.5.0\")\n    implementation(\"com.google.android.libraries.identity.googleid:googleid:1.1.1\")\n\n    // Firebase UI\n    // Used in FirebaseUIActivity.\n    implementation(\"com.firebaseui:firebase-ui-auth:9.1.1\")\n\n    // Facebook Android SDK (only required for Facebook Login)\n    // Used in FacebookLoginActivity.\n    implementation(\"com.facebook.android:facebook-login:13.2.0\")\n    implementation(\"androidx.browser:browser:1.5.0\")\n\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n}\n"
  },
  {
    "path": "auth/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes Signature\n-keepattributes *Annotation*\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n\n# Required for Twitter Authentication\n# https://docs.fabric.io/android/twitter/twitter.html#set-up-kit\n-dontwarn com.squareup.okhttp.**\n-dontwarn com.google.appengine.api.urlfetch.**\n-dontwarn rx.**\n-dontwarn retrofit.**\n-dontwarn retrofit2.**\n-dontwarn okio.**\n-keep class com.squareup.okhttp.** { *; }\n-keep interface com.squareup.okhttp.** { *; }\n-keep class retrofit.** { *; }\n-keepclasseswithmembers class * {\n    @retrofit.http.* <methods>;\n}\n"
  },
  {
    "path": "auth/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".EntryChoiceActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity android:name=\".java.MainActivity\" />\n        <activity android:name=\".kotlin.MainActivity\" />\n\n        <activity\n            android:name=\".java.PasswordlessActivity\"\n            android:label=\"@string/label_passwordless\"\n            android:launchMode=\"singleTop\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <data\n                    android:host=\"your-project-id.firebaseapp.com\"\n                    android:scheme=\"https\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".kotlin.PasswordlessActivity\"\n            android:label=\"@string/label_passwordless\"\n            android:launchMode=\"singleTop\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <data\n                    android:host=\"your-project-id.firebaseapp.com\"\n                    android:scheme=\"https\" />\n\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n\n        <!-- Facebook Configuration -->\n        <meta-data\n            android:name=\"com.facebook.sdk.ApplicationId\"\n            android:value=\"@string/facebook_app_id\"\n            tools:replace=\"android:value\" />\n\n        <meta-data android:name=\"com.facebook.sdk.ClientToken\" android:value=\"@string/facebook_client_token\"/>\n\n        <activity\n            android:name=\"com.facebook.FacebookActivity\"\n            android:configChanges=\"keyboard|keyboardHidden|screenLayout|screenSize|orientation\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar\"\n            tools:replace=\"android:theme\" />\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.quickstart.auth\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Auth quickstart written in Java.\",\n                Intent(this, com.google.firebase.quickstart.auth.java.MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Auth quickstart written in Kotlin.\",\n                Intent(this, com.google.firebase.quickstart.auth.kotlin.MainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/AnonymousAuthFragment.java",
    "content": "/**\n * Copyright 2021 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.AuthCredential;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.EmailAuthProvider;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentAnonymousAuthBinding;\n\npublic class AnonymousAuthFragment extends BaseFragment {\n    private static final String TAG = \"AnonymousAuth\";\n\n    private FirebaseAuth mAuth;\n\n    private FragmentAnonymousAuthBinding mBinding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,\n                             @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentAnonymousAuthBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        // Fields\n        setProgressBar(mBinding.progressBar);\n\n        // Click listeners\n        mBinding.buttonAnonymousSignIn.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                signInAnonymously();\n            }\n        });\n        mBinding.buttonAnonymousSignOut.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                signOut();\n            }\n        });\n        mBinding.buttonLinkAccount.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                linkAccount();\n            }\n        });\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        // Check if user is signed in (non-null) and update UI accordingly.\n        FirebaseUser currentUser = mAuth.getCurrentUser();\n        updateUI(currentUser);\n    }\n\n    private void signInAnonymously() {\n        showProgressBar();\n        mAuth.signInAnonymously()\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        if (task.isSuccessful()) {\n                            // Sign in success, update UI with the signed-in user's information\n                            Log.d(TAG, \"signInAnonymously:success\");\n                            FirebaseUser user = mAuth.getCurrentUser();\n                            updateUI(user);\n                        } else {\n                            // If sign in fails, display a message to the user.\n                            Log.w(TAG, \"signInAnonymously:failure\", task.getException());\n                            Toast.makeText(getContext(), \"Authentication failed.\",\n                                    Toast.LENGTH_SHORT).show();\n                            updateUI(null);\n                        }\n\n                        hideProgressBar();\n                    }\n                });\n    }\n\n    private void signOut() {\n        mAuth.signOut();\n        updateUI(null);\n    }\n\n    private void linkAccount() {\n        // Make sure form is valid\n        if (!validateLinkForm()) {\n            return;\n        }\n\n        // Get email and password from form\n        String email = mBinding.fieldEmail.getText().toString();\n        String password = mBinding.fieldPassword.getText().toString();\n\n        // Create EmailAuthCredential with email and password\n        AuthCredential credential = EmailAuthProvider.getCredential(email, password);\n\n        // Link the anonymous user to the email credential\n        showProgressBar();\n\n        mAuth.getCurrentUser().linkWithCredential(credential)\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        if (task.isSuccessful()) {\n                            Log.d(TAG, \"linkWithCredential:success\");\n                            FirebaseUser user = task.getResult().getUser();\n                            updateUI(user);\n                        } else {\n                            Log.w(TAG, \"linkWithCredential:failure\", task.getException());\n                            Toast.makeText(getContext(), \"Authentication failed.\",\n                                    Toast.LENGTH_SHORT).show();\n                            updateUI(null);\n                        }\n\n                        hideProgressBar();\n                    }\n                });\n    }\n\n    private boolean validateLinkForm() {\n        boolean valid = true;\n\n        String email = mBinding.fieldEmail.getText().toString();\n        if (TextUtils.isEmpty(email)) {\n            mBinding.fieldEmail.setError(\"Required.\");\n            valid = false;\n        } else {\n            mBinding.fieldEmail.setError(null);\n        }\n\n        String password = mBinding.fieldPassword.getText().toString();\n        if (TextUtils.isEmpty(password)) {\n            mBinding.fieldPassword.setError(\"Required.\");\n            valid = false;\n        } else {\n            mBinding.fieldPassword.setError(null);\n        }\n\n        return valid;\n    }\n\n    private void updateUI(FirebaseUser user) {\n        hideProgressBar();\n\n        TextView idView = mBinding.anonymousStatusId;\n        TextView emailView = mBinding.anonymousStatusEmail;\n        boolean isSignedIn = (user != null);\n\n        // Status text\n        if (isSignedIn) {\n            idView.setText(getString(R.string.id_fmt, user.getUid()));\n            emailView.setText(getString(R.string.email_fmt, user.getEmail()));\n        } else {\n            idView.setText(R.string.signed_out);\n            emailView.setText(null);\n        }\n\n        // Button visibility\n        mBinding.buttonAnonymousSignIn.setEnabled(!isSignedIn);\n        mBinding.buttonAnonymousSignOut.setEnabled(isSignedIn);\n        mBinding.buttonLinkAccount.setEnabled(isSignedIn);\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/BaseActivity.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.ProgressBar;\n\nimport androidx.annotation.VisibleForTesting;\nimport androidx.appcompat.app.AppCompatActivity;\n\npublic class BaseActivity extends AppCompatActivity {\n\n    @VisibleForTesting\n    public ProgressBar mProgressBar;\n\n    public void setProgressBar(ProgressBar progressBar) {\n        mProgressBar = progressBar;\n    }\n\n    public void showProgressBar() {\n        if (mProgressBar != null) {\n            mProgressBar.setVisibility(View.VISIBLE);\n        }\n    }\n\n    public void hideProgressBar() {\n        if (mProgressBar != null) {\n            mProgressBar.setVisibility(View.INVISIBLE);\n        }\n    }\n\n    public void hideKeyboard(View view) {\n        final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);\n        if (imm != null) {\n            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);\n        }\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        hideProgressBar();\n    }\n\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/BaseFragment.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.ProgressBar;\n\nimport androidx.annotation.VisibleForTesting;\nimport androidx.fragment.app.Fragment;\n\npublic class BaseFragment extends Fragment {\n\n    @VisibleForTesting\n    public ProgressBar mProgressBar;\n\n    public void setProgressBar(ProgressBar progressBar) {\n        mProgressBar = progressBar;\n    }\n\n    public void showProgressBar() {\n        if (mProgressBar != null) {\n            mProgressBar.setVisibility(View.VISIBLE);\n        }\n    }\n\n    public void hideProgressBar() {\n        if (mProgressBar != null) {\n            mProgressBar.setVisibility(View.INVISIBLE);\n        }\n    }\n\n    public void hideKeyboard(View view) {\n        final InputMethodManager imm = (InputMethodManager) requireActivity()\n                .getSystemService(Context.INPUT_METHOD_SERVICE);\n        if (imm != null) {\n            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);\n        }\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        hideProgressBar();\n    }\n\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/ChooserFragment.java",
    "content": "/**\n * Copyright 2021 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.java;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AdapterView;\nimport android.widget.ArrayAdapter;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentChooserBinding;\n\n/**\n * Simple list-based Fragment to redirect to one of the other Activities. This Fragment does not\n * contain any useful code related to Firebase Authentication. You may want to start with\n * one of the following Files:\n *     {@link GoogleSignInFragment}\n *     {@link FacebookLoginFragment}}\n *     {@link EmailPasswordFragment}\n *     {@link PasswordlessActivity}\n *     {@link PhoneAuthFragment}\n *     {@link AnonymousAuthFragment}\n *     {@link CustomAuthFragment}\n *     {@link GenericIdpFragment}\n *     {@link MultiFactorFragment}\n */\npublic class ChooserFragment extends Fragment {\n\n    private static final int[] NAV_ACTIONS = new int[]{\n            R.id.action_google,\n            R.id.action_facebook,\n            R.id.action_emailpassword,\n            R.id.action_passwordless,\n            R.id.action_phoneauth,\n            R.id.action_anonymousauth,\n            R.id.action_firebaseui,\n            R.id.action_customauth,\n            R.id.action_genericidp,\n            R.id.action_mfa,\n    };\n\n    private static final String [] CLASS_NAMES = new String[] {\n            \"GoogleSignInFragment\",\n            \"FacebookLoginFragment\",\n            \"EmailPasswordFragment\",\n            \"PasswordlessActivity\",\n            \"PhoneAuthFragment\",\n            \"AnonymousAuthFragment\",\n            \"FirebaseUIFragment\",\n            \"CustomAuthFragment\",\n            \"GenericIdpFragment\",\n            \"MultiFactorFragment\",\n    };\n\n    private static final int[] DESCRIPTION_IDS = new int[] {\n            R.string.desc_google_sign_in,\n            R.string.desc_facebook_login,\n            R.string.desc_emailpassword,\n            R.string.desc_passwordless,\n            R.string.desc_phone_auth,\n            R.string.desc_anonymous_auth,\n            R.string.desc_firebase_ui,\n            R.string.desc_custom_auth,\n            R.string.desc_generic_idp,\n            R.string.desc_multi_factor,\n    };\n\n    private FragmentChooserBinding mBinding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentChooserBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        MyArrayAdapter adapter = new MyArrayAdapter(requireContext(), android.R.layout.simple_list_item_2);\n        adapter.setDescriptionIds(DESCRIPTION_IDS);\n\n        mBinding.listView.setAdapter(adapter);\n        mBinding.listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                int destination = NAV_ACTIONS[position];\n                NavHostFragment.findNavController(ChooserFragment.this)\n                        .navigate(destination);\n            }\n        });\n    }\n\n    public static class MyArrayAdapter extends ArrayAdapter<String> {\n\n        private Context mContext;\n        private int[] mDescriptionIds;\n\n        public MyArrayAdapter(Context context, int resource) {\n            super(context, resource, CLASS_NAMES);\n\n            mContext = context;\n        }\n\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            View view = convertView;\n\n            if (convertView == null) {\n                LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n                view = inflater.inflate(android.R.layout.simple_list_item_2, null);\n            }\n\n            ((TextView) view.findViewById(android.R.id.text1)).setText(CLASS_NAMES[position]);\n            ((TextView) view.findViewById(android.R.id.text2)).setText(mDescriptionIds[position]);\n\n            return view;\n        }\n\n        public void setDescriptionIds(int[] descriptionIds) {\n            mDescriptionIds = descriptionIds;\n        }\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/CustomAuthFragment.java",
    "content": "/**\n * Copyright Google Inc. All Rights Reserved.\n * <p/>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p/>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p/>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.quickstart.auth.databinding.FragmentCustomBinding;\n\n/**\n * Demonstrate Firebase Authentication using a custom minted token. For more information, see:\n * https://firebase.google.com/docs/auth/android/custom-auth\n */\npublic class CustomAuthFragment extends Fragment {\n\n    private static final String TAG = \"CustomAuthFragment\";\n\n    private FirebaseAuth mAuth;\n\n    private FragmentCustomBinding mBinding;\n    private String mCustomToken;\n    private TokenBroadcastReceiver mTokenReceiver;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentCustomBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        // Button click listeners\n        mBinding.buttonSignIn.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                startSignIn();\n            }\n        });\n\n        // Create token receiver (for demo purposes only)\n        mTokenReceiver = new TokenBroadcastReceiver() {\n            @Override\n            public void onNewToken(String token) {\n                Log.d(TAG, \"onNewToken:\" + token);\n                setCustomToken(token);\n            }\n        };\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        // Check if user is signed in (non-null) and update UI accordingly.\n        FirebaseUser currentUser = mAuth.getCurrentUser();\n        updateUI(currentUser);\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        requireActivity().registerReceiver(mTokenReceiver, TokenBroadcastReceiver.getFilter());\n    }\n\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        requireActivity().unregisterReceiver(mTokenReceiver);\n    }\n\n    private void startSignIn() {\n        // Initiate sign in with custom token\n        mAuth.signInWithCustomToken(mCustomToken)\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        if (task.isSuccessful()) {\n                            // Sign in success, update UI with the signed-in user's information\n                            Log.d(TAG, \"signInWithCustomToken:success\");\n                            FirebaseUser user = mAuth.getCurrentUser();\n                            updateUI(user);\n                        } else {\n                            // If sign in fails, display a message to the user.\n                            Log.w(TAG, \"signInWithCustomToken:failure\", task.getException());\n                            Toast.makeText(getContext(), \"Authentication failed.\",\n                                    Toast.LENGTH_SHORT).show();\n                            updateUI(null);\n                        }\n                    }\n                });\n    }\n\n    private void updateUI(FirebaseUser user) {\n        if (user != null) {\n            mBinding.textSignInStatus.setText(\"User ID: \" + user.getUid());\n        } else {\n            mBinding.textSignInStatus.setText(\"Error: sign in failed.\");\n        }\n    }\n\n    private void setCustomToken(String token) {\n        mCustomToken = token;\n\n        String status;\n        if (mCustomToken != null) {\n            status = \"Token:\" + mCustomToken;\n        } else {\n            status = \"Token: null\";\n        }\n\n        // Enable/disable sign-in button and show the token\n        mBinding.buttonSignIn.setEnabled((mCustomToken != null));\n        mBinding.textTokenStatus.setText(status);\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/EmailPasswordFragment.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseAuthMultiFactorException;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.auth.MultiFactorResolver;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentEmailpasswordBinding;\n\npublic class EmailPasswordFragment extends BaseFragment {\n\n    private static final String TAG = \"EmailPassword\";\n\n    private FragmentEmailpasswordBinding mBinding;\n\n    private FirebaseAuth mAuth;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentEmailpasswordBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        setProgressBar(mBinding.progressBar);\n\n        // Buttons\n        mBinding.emailSignInButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                String email = mBinding.fieldEmail.getText().toString();\n                String password = mBinding.fieldPassword.getText().toString();\n                signIn(email, password);\n            }\n        });\n        mBinding.emailCreateAccountButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                String email = mBinding.fieldEmail.getText().toString();\n                String password = mBinding.fieldPassword.getText().toString();\n                createAccount(email, password);\n            }\n        });\n        mBinding.signOutButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                signOut();\n            }\n        });\n        mBinding.verifyEmailButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                sendEmailVerification();\n            }\n        });\n        mBinding.reloadButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                reload();\n            }\n        });\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        // Check if user is signed in (non-null) and update UI accordingly.\n        FirebaseUser currentUser = mAuth.getCurrentUser();\n        if(currentUser != null){\n            reload();\n        }\n    }\n\n    private void createAccount(String email, String password) {\n        Log.d(TAG, \"createAccount:\" + email);\n        if (!validateForm()) {\n            return;\n        }\n\n        showProgressBar();\n\n        mAuth.createUserWithEmailAndPassword(email, password)\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        if (task.isSuccessful()) {\n                            // Sign in success, update UI with the signed-in user's information\n                            Log.d(TAG, \"createUserWithEmail:success\");\n                            FirebaseUser user = mAuth.getCurrentUser();\n                            updateUI(user);\n                        } else {\n                            // If sign in fails, display a message to the user.\n                            Log.w(TAG, \"createUserWithEmail:failure\", task.getException());\n                            Toast.makeText(getContext(), \"Authentication failed.\",\n                                    Toast.LENGTH_SHORT).show();\n                            updateUI(null);\n                        }\n\n                        hideProgressBar();\n                    }\n                });\n    }\n\n    private void signIn(String email, String password) {\n        Log.d(TAG, \"signIn:\" + email);\n        if (!validateForm()) {\n            return;\n        }\n\n        showProgressBar();\n\n        mAuth.signInWithEmailAndPassword(email, password)\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        if (task.isSuccessful()) {\n                            // Sign in success, update UI with the signed-in user's information\n                            Log.d(TAG, \"signInWithEmail:success\");\n                            FirebaseUser user = mAuth.getCurrentUser();\n                            updateUI(user);\n                        } else {\n                            // If sign in fails, display a message to the user.\n                            Log.w(TAG, \"signInWithEmail:failure\", task.getException());\n                            Toast.makeText(getContext(), \"Authentication failed.\",\n                                    Toast.LENGTH_SHORT).show();\n                            updateUI(null);\n                            checkForMultiFactorFailure(task.getException());\n                        }\n\n                        if (!task.isSuccessful()) {\n                            mBinding.status.setText(R.string.auth_failed);\n                        }\n                        hideProgressBar();\n                    }\n                });\n    }\n\n    private void signOut() {\n        mAuth.signOut();\n        updateUI(null);\n    }\n\n    private void sendEmailVerification() {\n        // Disable button\n        mBinding.verifyEmailButton.setEnabled(false);\n\n        // Send verification email\n        final FirebaseUser user = mAuth.getCurrentUser();\n        user.sendEmailVerification()\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<Void>() {\n                    @Override\n                    public void onComplete(@NonNull Task<Void> task) {\n                        // Re-enable button\n                        mBinding.verifyEmailButton.setEnabled(true);\n\n                        if (task.isSuccessful()) {\n                            Toast.makeText(getContext(),\n                                    \"Verification email sent to \" + user.getEmail(),\n                                    Toast.LENGTH_SHORT).show();\n                        } else {\n                            Log.e(TAG, \"sendEmailVerification\", task.getException());\n                            Toast.makeText(getContext(),\n                                    \"Failed to send verification email.\",\n                                    Toast.LENGTH_SHORT).show();\n                        }\n                    }\n                });\n    }\n\n    private void reload() {\n        mAuth.getCurrentUser().reload().addOnCompleteListener(new OnCompleteListener<Void>() {\n            @Override\n            public void onComplete(@NonNull Task<Void> task) {\n                if (task.isSuccessful()) {\n                    updateUI(mAuth.getCurrentUser());\n                    Toast.makeText(getContext(),\n                            \"Reload successful!\",\n                            Toast.LENGTH_SHORT).show();\n                } else {\n                    Log.e(TAG, \"reload\", task.getException());\n                    Toast.makeText(getContext(),\n                            \"Failed to reload user.\",\n                            Toast.LENGTH_SHORT).show();\n                }\n            }\n        });\n    }\n\n    private boolean validateForm() {\n        boolean valid = true;\n\n        String email = mBinding.fieldEmail.getText().toString();\n        if (TextUtils.isEmpty(email)) {\n            mBinding.fieldEmail.setError(\"Required.\");\n            valid = false;\n        } else {\n            mBinding.fieldEmail.setError(null);\n        }\n\n        String password = mBinding.fieldPassword.getText().toString();\n        if (TextUtils.isEmpty(password)) {\n            mBinding.fieldPassword.setError(\"Required.\");\n            valid = false;\n        } else {\n            mBinding.fieldPassword.setError(null);\n        }\n\n        return valid;\n    }\n\n    private void updateUI(FirebaseUser user) {\n        hideProgressBar();\n        if (user != null) {\n            mBinding.status.setText(getString(R.string.emailpassword_status_fmt,\n                    user.getEmail(), user.isEmailVerified()));\n            mBinding.detail.setText(getString(R.string.firebase_status_fmt, user.getUid()));\n\n            mBinding.emailPasswordButtons.setVisibility(View.GONE);\n            mBinding.emailPasswordFields.setVisibility(View.GONE);\n            mBinding.signedInButtons.setVisibility(View.VISIBLE);\n\n            if (user.isEmailVerified()) {\n                mBinding.verifyEmailButton.setVisibility(View.GONE);\n            } else {\n                mBinding.verifyEmailButton.setVisibility(View.VISIBLE);\n            }\n        } else {\n            mBinding.status.setText(R.string.signed_out);\n            mBinding.detail.setText(null);\n\n            mBinding.emailPasswordButtons.setVisibility(View.VISIBLE);\n            mBinding.emailPasswordFields.setVisibility(View.VISIBLE);\n            mBinding.signedInButtons.setVisibility(View.GONE);\n        }\n    }\n\n    private void checkForMultiFactorFailure(Exception e) {\n        // Multi-factor authentication with SMS is currently only available for\n        // Google Cloud Identity Platform projects. For more information:\n        // https://cloud.google.com/identity-platform/docs/android/mfa\n        if (e instanceof FirebaseAuthMultiFactorException) {\n            Log.w(TAG, \"multiFactorFailure\", e);\n            MultiFactorResolver resolver = ((FirebaseAuthMultiFactorException) e).getResolver();\n            Bundle args = new Bundle();\n            args.putParcelable(MultiFactorSignInFragment.EXTRA_MFA_RESOLVER, resolver);\n            NavHostFragment.findNavController(this)\n                    .navigate(R.id.action_emailpassword_to_mfasignin, args);\n        }\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/FacebookLoginFragment.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.facebook.AccessToken;\nimport com.facebook.CallbackManager;\nimport com.facebook.FacebookCallback;\nimport com.facebook.FacebookException;\nimport com.facebook.login.LoginManager;\nimport com.facebook.login.LoginResult;\nimport com.facebook.login.widget.LoginButton;\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.AuthCredential;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.FacebookAuthProvider;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentFacebookBinding;\n\n/**\n * Demonstrate Firebase Authentication using a Facebook access token.\n */\npublic class FacebookLoginFragment extends BaseFragment {\n\n    private static final String TAG = \"FacebookLogin\";\n\n    private FragmentFacebookBinding mBinding;\n\n    private FirebaseAuth mAuth;\n\n    private CallbackManager mCallbackManager;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentFacebookBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        setProgressBar(mBinding.progressBar);\n\n        // Views\n        mBinding.buttonFacebookSignout.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                signOut();\n            }\n        });\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        // Initialize Facebook Login button\n        mCallbackManager = CallbackManager.Factory.create();\n        LoginButton loginButton = mBinding.buttonFacebookLogin;\n        loginButton.setPermissions(\"email\", \"public_profile\");\n        loginButton.registerCallback(mCallbackManager, new FacebookCallback<LoginResult>() {\n            @Override\n            public void onSuccess(LoginResult loginResult) {\n                Log.d(TAG, \"facebook:onSuccess:\" + loginResult);\n                handleFacebookAccessToken(loginResult.getAccessToken());\n            }\n\n            @Override\n            public void onCancel() {\n                Log.d(TAG, \"facebook:onCancel\");\n                updateUI(null);\n            }\n\n            @Override\n            public void onError(FacebookException error) {\n                Log.d(TAG, \"facebook:onError\", error);\n                updateUI(null);\n            }\n        });\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        // Check if user is signed in (non-null) and update UI accordingly.\n        FirebaseUser currentUser = mAuth.getCurrentUser();\n        updateUI(currentUser);\n    }\n\n    private void handleFacebookAccessToken(AccessToken token) {\n        Log.d(TAG, \"handleFacebookAccessToken:\" + token);\n        showProgressBar();\n\n        AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());\n        mAuth.signInWithCredential(credential)\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        if (task.isSuccessful()) {\n                            // Sign in success, update UI with the signed-in user's information\n                            Log.d(TAG, \"signInWithCredential:success\");\n                            FirebaseUser user = mAuth.getCurrentUser();\n                            updateUI(user);\n                        } else {\n                            // If sign in fails, display a message to the user.\n                            Log.w(TAG, \"signInWithCredential:failure\", task.getException());\n                            Toast.makeText(getContext(), \"Authentication failed.\",\n                                    Toast.LENGTH_SHORT).show();\n                            updateUI(null);\n                        }\n\n                        hideProgressBar();\n                    }\n                });\n    }\n\n    public void signOut() {\n        mAuth.signOut();\n        LoginManager.getInstance().logOut();\n\n        updateUI(null);\n    }\n\n    private void updateUI(FirebaseUser user) {\n        hideProgressBar();\n        if (user != null) {\n            mBinding.status.setText(getString(R.string.facebook_status_fmt, user.getDisplayName()));\n            mBinding.detail.setText(getString(R.string.firebase_status_fmt, user.getUid()));\n\n            mBinding.buttonFacebookLogin.setVisibility(View.GONE);\n            mBinding.buttonFacebookSignout.setVisibility(View.VISIBLE);\n        } else {\n            mBinding.status.setText(R.string.signed_out);\n            mBinding.detail.setText(null);\n\n            mBinding.buttonFacebookLogin.setVisibility(View.VISIBLE);\n            mBinding.buttonFacebookSignout.setVisibility(View.GONE);\n        }\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/FirebaseUIFragment.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\n\nimport com.firebase.ui.auth.AuthUI;\nimport com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;\nimport com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.quickstart.auth.BuildConfig;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentFirebaseUiBinding;\n\nimport java.util.Collections;\n\n/**\n * Demonstrate authentication using the FirebaseUI-Android library. This fragment demonstrates\n * using FirebaseUI for basic email/password sign in.\n * <p>\n * For more information, visit https://github.com/firebase/firebaseui-android\n */\npublic class FirebaseUIFragment extends Fragment {\n\n    private FirebaseAuth mAuth;\n\n    private FragmentFirebaseUiBinding mBinding;\n\n    // Build FirebaseUI sign in intent. For documentation on this operation and all\n    // possible customization see: https://github.com/firebase/firebaseui-android\n    private final ActivityResultLauncher<Intent> signInLauncher = registerForActivityResult(\n            new FirebaseAuthUIActivityResultContract(),\n            this::onSignInResult\n    );\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentFirebaseUiBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        mBinding.signInButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                startSignIn();\n            }\n        });\n\n        mBinding.signOutButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                signOut();\n            }\n        });\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        updateUI(mAuth.getCurrentUser());\n    }\n\n    private void onSignInResult(FirebaseAuthUIAuthenticationResult result) {\n        if (result.getResultCode() == Activity.RESULT_OK) {\n            // Sign in succeeded\n            updateUI(mAuth.getCurrentUser());\n        } else {\n            // Sign in failed\n            Toast.makeText(getContext(), \"Sign In Failed\", Toast.LENGTH_SHORT).show();\n            updateUI(null);\n        }\n    }\n\n    private void startSignIn() {\n        Intent intent = AuthUI.getInstance().createSignInIntentBuilder()\n                .setCredentialManagerEnabled(!BuildConfig.DEBUG)\n                .setAvailableProviders(Collections.singletonList(\n                        new AuthUI.IdpConfig.EmailBuilder().build()))\n                .setLogo(R.mipmap.ic_launcher)\n                .build();\n\n        signInLauncher.launch(intent);\n    }\n\n    private void updateUI(FirebaseUser user) {\n        if (user != null) {\n            // Signed in\n            mBinding.status.setText(getString(R.string.firebaseui_status_fmt, user.getEmail()));\n            mBinding.detail.setText(getString(R.string.id_fmt, user.getUid()));\n\n            mBinding.signInButton.setVisibility(View.GONE);\n            mBinding.signOutButton.setVisibility(View.VISIBLE);\n        } else {\n            // Signed out\n            mBinding.status.setText(R.string.signed_out);\n            mBinding.detail.setText(null);\n\n            mBinding.signInButton.setVisibility(View.VISIBLE);\n            mBinding.signOutButton.setVisibility(View.GONE);\n        }\n    }\n\n    private void signOut() {\n        AuthUI.getInstance().signOut(getContext());\n        updateUI(null);\n    }\n}"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/GenericIdpFragment.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AdapterView;\nimport android.widget.ArrayAdapter;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.google.android.gms.tasks.OnFailureListener;\nimport com.google.android.gms.tasks.OnSuccessListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.auth.OAuthProvider;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentGenericIdpBinding;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Demonstrate Firebase Authentication using a Generic Identity Provider (IDP).\n */\n@SuppressWarnings(\"Convert2Lambda\")\npublic class GenericIdpFragment extends BaseFragment {\n\n    private static final String TAG = \"GenericIdp\";\n\n    private static final Map<String,String> PROVIDER_MAP = new HashMap<String, String>() {\n        {\n            put(\"Apple\", \"apple.com\");\n            put(\"Microsoft\", \"microsoft.com\");\n            put(\"Yahoo\", \"yahoo.com\");\n            put(\"Twitter\", \"twitter.com\");\n        }\n    };\n\n    private FragmentGenericIdpBinding mBinding;\n    private ArrayAdapter<String> mSpinnerAdapter;\n\n    private FirebaseAuth mAuth;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentGenericIdpBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        // Set up button click listeners\n        mBinding.genericSignInButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                signIn();\n            }\n        });\n        mBinding.signOutButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                mAuth.signOut();\n                updateUI(null);\n            }\n        });\n\n        // Spinner\n        List<String> providers = new ArrayList<>(PROVIDER_MAP.keySet());\n        mSpinnerAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner_list, providers);\n        mBinding.providerSpinner.setAdapter(mSpinnerAdapter);\n        mBinding.providerSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                mBinding.genericSignInButton.setText(getString(R.string.generic_signin_fmt, mSpinnerAdapter.getItem(position)));\n            }\n\n            @Override\n            public void onNothingSelected(AdapterView<?> parent) {}\n        });\n        mBinding.providerSpinner.setSelection(0);\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        // Check if user is signed in (non-null) and update UI accordingly.\n        FirebaseUser currentUser = mAuth.getCurrentUser();\n        updateUI(currentUser);\n\n        // Look for a pending auth result\n        Task<AuthResult> pending = mAuth.getPendingAuthResult();\n        if (pending != null) {\n            pending.addOnSuccessListener(new OnSuccessListener<AuthResult>() {\n                @Override\n                public void onSuccess(AuthResult authResult) {\n                    Log.d(TAG, \"checkPending:onSuccess:\" + authResult);\n                    updateUI(authResult.getUser());\n                }\n            }).addOnFailureListener(new OnFailureListener() {\n                @Override\n                public void onFailure(@NonNull Exception e) {\n                    Log.w(TAG, \"checkPending:onFailure\", e);\n                }\n            });\n        } else {\n            Log.d(TAG, \"checkPending: null\");\n        }\n    }\n\n    private void signIn() {\n        // Could add custom scopes here\n        ArrayList<String> scopes = new ArrayList<>();\n\n        // Examples of provider ID: apple.com (Apple), microsoft.com (Microsoft), yahoo.com (Yahoo)\n        String providerId = getProviderId();\n\n        mAuth.startActivityForSignInWithProvider(requireActivity(),\n                OAuthProvider.newBuilder(providerId, mAuth)\n                        .setScopes(scopes)\n                        .build())\n                .addOnSuccessListener(\n                        new OnSuccessListener<AuthResult>() {\n                            @Override\n                            public void onSuccess(AuthResult authResult) {\n                                Log.d(TAG, \"activitySignIn:onSuccess:\" + authResult.getUser());\n                                updateUI(authResult.getUser());\n                            }\n                        })\n                .addOnFailureListener(\n                        new OnFailureListener() {\n                            @Override\n                            public void onFailure(@NonNull Exception e) {\n                                Log.w(TAG, \"activitySignIn:onFailure\", e);\n                                showToast(getString(R.string.error_sign_in_failed));\n                            }\n                        });\n    }\n\n    private String getProviderId() {\n        String providerName = mSpinnerAdapter.getItem(mBinding.providerSpinner.getSelectedItemPosition());\n        return PROVIDER_MAP.get(providerName);\n    }\n\n    private void updateUI(FirebaseUser user) {\n        hideProgressBar();\n        if (user != null) {\n            mBinding.status.setText(getString(R.string.generic_status_fmt, user.getDisplayName(), user.getEmail()));\n            mBinding.detail.setText(getString(R.string.firebase_status_fmt, user.getUid()));\n\n            mBinding.spinnerLayout.setVisibility(View.GONE);\n            mBinding.genericSignInButton.setVisibility(View.GONE);\n            mBinding.signOutButton.setVisibility(View.VISIBLE);\n        } else {\n            mBinding.status.setText(R.string.signed_out);\n            mBinding.detail.setText(null);\n\n            mBinding.spinnerLayout.setVisibility(View.VISIBLE);\n            mBinding.genericSignInButton.setVisibility(View.VISIBLE);\n            mBinding.signOutButton.setVisibility(View.GONE);\n        }\n    }\n\n    private void showToast(String message) {\n        Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/GoogleSignInFragment.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.java;\n\nimport static com.google.android.libraries.identity.googleid.GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL;\n\nimport android.os.Bundle;\nimport android.os.CancellationSignal;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.credentials.ClearCredentialStateRequest;\nimport androidx.credentials.Credential;\nimport androidx.credentials.CredentialManager;\nimport androidx.credentials.CredentialManagerCallback;\nimport androidx.credentials.CustomCredential;\nimport androidx.credentials.GetCredentialRequest;\nimport androidx.credentials.GetCredentialResponse;\nimport androidx.credentials.exceptions.ClearCredentialException;\nimport androidx.credentials.exceptions.GetCredentialException;\nimport com.google.android.libraries.identity.googleid.GetGoogleIdOption;\nimport com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption;\nimport com.google.android.libraries.identity.googleid.GoogleIdTokenCredential;\nimport com.google.android.material.snackbar.Snackbar;\nimport com.google.firebase.auth.AuthCredential;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.auth.GoogleAuthProvider;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentGoogleBinding;\nimport java.util.concurrent.Executors;\n\n/**\n * Demonstrate Firebase Authentication using a Google ID Token.\n */\npublic class GoogleSignInFragment extends BaseFragment {\n\n    private static final String TAG = \"GoogleFragment\";\n\n    private FirebaseAuth mAuth;\n\n    private CredentialManager credentialManager;\n    private FragmentGoogleBinding mBinding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentGoogleBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        setProgressBar(mBinding.progressBar);\n\n        // Initialize Credential Manager\n        credentialManager = CredentialManager.create(requireContext());\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        // Button listeners\n        mBinding.signInButton.setOnClickListener(v -> signIn());\n        mBinding.signOutButton.setOnClickListener(v -> signOut());\n\n        // Display Credential Manager Bottom Sheet if user isn't logged in\n        if (mAuth.getCurrentUser() == null) {\n            showBottomSheet();\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        // Check if user is signed in (non-null) and update UI accordingly.\n        FirebaseUser currentUser = mAuth.getCurrentUser();\n        updateUI(currentUser);\n    }\n\n    private void signIn() {\n        // Create the dialog configuration for the Credential Manager request\n        GetSignInWithGoogleOption signInWithGoogleOption = new GetSignInWithGoogleOption\n                .Builder(requireContext().getString(R.string.default_web_client_id))\n                .build();\n\n        // Create the Credential Manager request using the configuration created above\n        GetCredentialRequest request = new GetCredentialRequest.Builder()\n                .addCredentialOption(signInWithGoogleOption)\n                .build();\n\n        launchCredentialManager(request);\n    }\n\n    private void showBottomSheet() {\n        // Create the bottom sheet configuration for the Credential Manager request\n        GetGoogleIdOption googleIdOption = new GetGoogleIdOption.Builder()\n                .setFilterByAuthorizedAccounts(true)\n                .setServerClientId(requireContext().getString(R.string.default_web_client_id))\n                .build();\n\n        // Create the Credential Manager request using the configuration created above\n        GetCredentialRequest request = new GetCredentialRequest.Builder()\n                .addCredentialOption(googleIdOption)\n                .build();\n\n        launchCredentialManager(request);\n    }\n\n    private void launchCredentialManager(GetCredentialRequest request) {\n        credentialManager.getCredentialAsync(\n                requireContext(),\n                request,\n                new CancellationSignal(),\n                Executors.newSingleThreadExecutor(),\n                new CredentialManagerCallback<>() {\n                    @Override\n                    public void onResult(GetCredentialResponse result) {\n                        // Extract credential from the result returned by Credential Manager\n                        createGoogleIdToken(result.getCredential());\n                    }\n\n                    @Override\n                    public void onError(GetCredentialException e) {\n                        Log.e(TAG, \"Couldn't retrieve user's credentials: \" + e.getLocalizedMessage());\n                    }\n                }\n        );\n    }\n\n    private void createGoogleIdToken(Credential credential) {\n        // Update UI to show progress bar while response is being processed\n        requireActivity().runOnUiThread(this::showProgressBar);\n\n        // Check if credential is of type Google ID\n        if (credential instanceof CustomCredential customCredential\n                && credential.getType().equals(TYPE_GOOGLE_ID_TOKEN_CREDENTIAL)) {\n            // Create Google ID Token\n            Bundle credentialData = customCredential.getData();\n            GoogleIdTokenCredential googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credentialData);\n\n            // Sign in to Firebase with using the token\n            firebaseAuthWithGoogle(googleIdTokenCredential.getIdToken());\n        } else {\n            Log.w(TAG, \"Credential is not of type Google ID!\");\n        }\n    }\n\n    private void firebaseAuthWithGoogle(String idToken) {\n        AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);\n        mAuth.signInWithCredential(credential)\n                .addOnCompleteListener(requireActivity(), task -> {\n                    if (task.isSuccessful()) {\n                        // Sign in success, update UI with the signed-in user's information\n                        Log.d(TAG, \"signInWithCredential:success\");\n                        FirebaseUser user = mAuth.getCurrentUser();\n                        updateUI(user);\n                    } else {\n                        // If sign in fails, display a message to the user.\n                        Log.w(TAG, \"signInWithCredential:failure\", task.getException());\n                        Snackbar.make(mBinding.mainLayout, \"Authentication Failed.\", Snackbar.LENGTH_SHORT).show();\n                        updateUI(null);\n                    }\n\n                    hideProgressBar();\n                });\n    }\n\n    private void signOut() {\n        // Firebase sign out\n        mAuth.signOut();\n\n        // When a user signs out, clear the current user credential state from all credential providers.\n        // This will notify all providers that any stored credential session for the given app should be cleared.\n        ClearCredentialStateRequest clearRequest = new ClearCredentialStateRequest();\n        credentialManager.clearCredentialStateAsync(\n                clearRequest,\n                new CancellationSignal(),\n                Executors.newSingleThreadExecutor(),\n                new CredentialManagerCallback<>() {\n                    @Override\n                    public void onResult(@NonNull Void result) {\n                        updateUI(null);\n                    }\n\n                    @Override\n                    public void onError(@NonNull ClearCredentialException e) {\n                        Log.e(TAG, \"Couldn't clear user credentials: \" + e.getLocalizedMessage());\n                    }\n                });\n    }\n\n    private void updateUI(FirebaseUser user) {\n        requireActivity().runOnUiThread(() -> {\n            hideProgressBar();\n            if (user != null) {\n                mBinding.status.setText(getString(R.string.google_status_fmt, user.getEmail()));\n                mBinding.detail.setText(getString(R.string.firebase_status_fmt, user.getUid()));\n\n                mBinding.signInButton.setVisibility(View.GONE);\n                mBinding.signOutButton.setVisibility(View.VISIBLE);\n            } else {\n                mBinding.status.setText(R.string.signed_out);\n                mBinding.detail.setText(null);\n\n                mBinding.signInButton.setVisibility(View.VISIBLE);\n                mBinding.signOutButton.setVisibility(View.GONE);\n            }\n        });\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/MainActivity.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.navigation.Navigation;\n\nimport com.google.firebase.quickstart.auth.R;\n\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        Navigation.findNavController(this, R.id.nav_host_fragment)\n                .setGraph(R.navigation.nav_graph_java);\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/MultiFactorEnrollFragment.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.OnFailureListener;\nimport com.google.android.gms.tasks.OnSuccessListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.FirebaseException;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.MultiFactorSession;\nimport com.google.firebase.auth.PhoneAuthCredential;\nimport com.google.firebase.auth.PhoneAuthOptions;\nimport com.google.firebase.auth.PhoneAuthProvider;\nimport com.google.firebase.auth.PhoneAuthProvider.OnVerificationStateChangedCallbacks;\nimport com.google.firebase.auth.PhoneMultiFactorGenerator;\nimport com.google.firebase.quickstart.auth.databinding.FragmentPhoneAuthBinding;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Fragment that allows the user to enroll second factors.\n */\npublic class MultiFactorEnrollFragment extends BaseFragment {\n\n    private static final String TAG = \"MfaEnrollFragment\";\n\n    private FragmentPhoneAuthBinding mBinding;\n\n    private String mCodeVerificationId;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentPhoneAuthBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        mBinding.titleText.setText(\"SMS as a Second Factor\");\n        mBinding.status.setVisibility(View.GONE);\n        mBinding.detail.setVisibility(View.GONE);\n        mBinding.buttonStartVerification.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onClickVerifyPhoneNumber();\n            }\n        });\n        mBinding.buttonVerifyPhone.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onClickSignInWithPhoneNumber();\n            }\n        });\n    }\n\n    private void onClickVerifyPhoneNumber() {\n        String phoneNumber = mBinding.fieldPhoneNumber.getText().toString();\n\n        OnVerificationStateChangedCallbacks callbacks =\n                new OnVerificationStateChangedCallbacks() {\n                    @Override\n                    public void onVerificationCompleted(PhoneAuthCredential credential) {\n                        // Instant-validation has been disabled (see requireSmsValidation below).\n                        // Auto-retrieval has also been disabled (timeout is set to 0).\n                        // This should never be triggered.\n                        throw new RuntimeException(\n                                \"onVerificationCompleted() triggered with instant-validation and auto-retrieval disabled.\");\n                    }\n\n                    @Override\n                    public void onCodeSent(\n                            final String verificationId, PhoneAuthProvider.ForceResendingToken token) {\n                        Log.d(TAG, \"onCodeSent:\" + verificationId);\n                        Toast.makeText( getContext(), \"SMS code has been sent\", Toast.LENGTH_SHORT)\n                                .show();\n\n                        mCodeVerificationId = verificationId;\n                    }\n\n                    @Override\n                    public void onVerificationFailed(FirebaseException e) {\n                        Log.w(TAG, \"onVerificationFailed \", e);\n                        Toast.makeText(getContext(), \"Verification failed: \" + e.getMessage(), Toast.LENGTH_SHORT)\n                                .show();\n                    }\n                };\n\n        FirebaseAuth.getInstance()\n                .getCurrentUser()\n                .getMultiFactor()\n                .getSession()\n                .addOnCompleteListener(\n                        new OnCompleteListener<MultiFactorSession>() {\n                            @Override\n                            public void onComplete(@NonNull Task<MultiFactorSession> task) {\n                                if (task.isSuccessful()) {\n                                    PhoneAuthOptions phoneAuthOptions =\n                                            PhoneAuthOptions.newBuilder()\n                                                    .setActivity(requireActivity())\n                                                    .setPhoneNumber(phoneNumber)\n                                                    // A timeout of 0 disables SMS-auto-retrieval.\n                                                    .setTimeout(0L, TimeUnit.SECONDS)\n                                                    .setMultiFactorSession(task.getResult())\n                                                    .setCallbacks(callbacks)\n                                                    // Disable instant-validation.\n                                                    .requireSmsValidation(true)\n                                                    .build();\n\n                                    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);\n                                } else {\n                                    Toast.makeText(getContext(),\n                                            \"Failed to get session: \" + task.getException(), Toast.LENGTH_SHORT)\n                                            .show();\n                                }\n                            }\n                        });\n    }\n\n    private void onClickSignInWithPhoneNumber() {\n        String smsCode = mBinding.fieldVerificationCode.getText().toString();\n        if (TextUtils.isEmpty(smsCode)) {\n            return;\n        }\n        PhoneAuthCredential credential = PhoneAuthProvider.getCredential(mCodeVerificationId, smsCode);\n        enrollWithPhoneAuthCredential(credential);\n    }\n\n    private void enrollWithPhoneAuthCredential(PhoneAuthCredential credential) {\n        FirebaseAuth.getInstance()\n                .getCurrentUser()\n                .getMultiFactor()\n                .enroll(PhoneMultiFactorGenerator.getAssertion(credential), /* displayName= */ null)\n                .addOnSuccessListener(new OnSuccessListener<Void>() {\n                    @Override\n                    public void onSuccess(Void aVoid) {\n                        Toast.makeText(getContext(), \"MFA enrollment was successful\",\n                                Toast.LENGTH_LONG)\n                                .show();\n\n                        NavHostFragment.findNavController(MultiFactorEnrollFragment.this)\n                                .popBackStack();\n                    }\n                })\n                .addOnFailureListener(new OnFailureListener() {\n                    @Override\n                    public void onFailure(@NonNull Exception e) {\n                        Log.d(TAG, \"MFA failure\", e);\n                        Toast.makeText(getContext(),\n                                \"MFA enrollment was unsuccessful. \" + e,\n                                Toast.LENGTH_LONG)\n                                .show();\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/MultiFactorFragment.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.java;\n\nimport android.app.AlertDialog;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.auth.MultiFactorInfo;\nimport com.google.firebase.auth.PhoneMultiFactorInfo;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentMultiFactorBinding;\n\nimport java.util.List;\n\npublic class MultiFactorFragment extends BaseFragment {\n\n    private static final String TAG = \"MultiFactor\";\n\n    private FragmentMultiFactorBinding mBinding;\n\n    private FirebaseAuth mAuth;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentMultiFactorBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        setProgressBar(mBinding.progressBar);\n\n        // Buttons\n        mBinding.emailSignInButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                NavHostFragment.findNavController(MultiFactorFragment.this)\n                        .navigate(R.id.action_mfa_to_emailpassword);\n            }\n        });\n        mBinding.signOutButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                signOut();\n            }\n        });\n        mBinding.verifyEmailButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                sendEmailVerification();\n            }\n        });\n        mBinding.enrollMfa.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                NavHostFragment.findNavController(MultiFactorFragment.this)\n                        .navigate(R.id.action_mfa_to_enroll);\n            }\n        });\n        mBinding.unenrollMfa.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                NavHostFragment.findNavController(MultiFactorFragment.this)\n                        .navigate(R.id.action_mfa_to_unenroll);\n            }\n        });\n        mBinding.reloadButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                reload();\n            }\n        });\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        showDisclaimer();\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        // Check if user is signed in (non-null) and update UI accordingly.\n        FirebaseUser currentUser = mAuth.getCurrentUser();\n        updateUI(currentUser);\n    }\n\n    private void signOut() {\n        mAuth.signOut();\n        updateUI(null);\n    }\n\n    private void sendEmailVerification() {\n        // Disable button\n        mBinding.verifyEmailButton.setEnabled(false);\n\n        // Send verification email\n        final FirebaseUser user = mAuth.getCurrentUser();\n        user.sendEmailVerification()\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<Void>() {\n                    @Override\n                    public void onComplete(@NonNull Task<Void> task) {\n                        // Re-enable button\n                        mBinding.verifyEmailButton.setEnabled(true);\n\n                        if (task.isSuccessful()) {\n                            Toast.makeText(getContext(),\n                                    \"Verification email sent to \" + user.getEmail(),\n                                    Toast.LENGTH_SHORT).show();\n                        } else {\n                            Log.e(TAG, \"sendEmailVerification\", task.getException());\n                            Toast.makeText(getContext(),\n                                    \"Failed to send verification email.\",\n                                    Toast.LENGTH_SHORT).show();\n                        }\n                    }\n                });\n    }\n\n    private void reload() {\n        mAuth.getCurrentUser().reload().addOnCompleteListener(new OnCompleteListener<Void>() {\n            @Override\n            public void onComplete(@NonNull Task<Void> task) {\n                if (task.isSuccessful()) {\n                    updateUI(mAuth.getCurrentUser());\n                    Toast.makeText(getContext(),\n                            \"Reload successful!\",\n                            Toast.LENGTH_SHORT).show();\n                } else {\n                    Log.e(TAG, \"reload\", task.getException());\n                    Toast.makeText(getContext(),\n                            \"Failed to reload user.\",\n                            Toast.LENGTH_SHORT).show();\n                }\n            }\n        });\n    }\n\n    private void updateUI(FirebaseUser user) {\n        hideProgressBar();\n        if (user != null) {\n            mBinding.status.setText(getString(R.string.emailpassword_status_fmt,\n                    user.getEmail(), user.isEmailVerified()));\n            mBinding.detail.setText(getString(R.string.firebase_status_fmt, user.getUid()));\n\n            List<MultiFactorInfo> secondFactors = user.getMultiFactor().getEnrolledFactors();\n\n            if (secondFactors.isEmpty()) {\n                mBinding.unenrollMfa.setVisibility(View.GONE);\n            } else {\n                mBinding.unenrollMfa.setVisibility(View.VISIBLE);\n\n                StringBuilder sb = new StringBuilder(\"Second Factors: \");\n                String delimiter = \", \";\n                for (MultiFactorInfo x : secondFactors) {\n                    sb.append(((PhoneMultiFactorInfo) x).getPhoneNumber() + delimiter);\n                }\n                sb.setLength(sb.length() - delimiter.length());\n                mBinding.mfaInfo.setText(sb.toString());\n            }\n\n            mBinding.emailSignInButton.setVisibility(View.GONE);\n            mBinding.signedInButtons.setVisibility(View.VISIBLE);\n\n            int reloadVisibility = secondFactors.isEmpty() ? View.VISIBLE : View.GONE;\n            mBinding.reloadButton.setVisibility(reloadVisibility);\n\n            if (user.isEmailVerified()) {\n                mBinding.verifyEmailButton.setVisibility(View.GONE);\n                mBinding.enrollMfa.setVisibility(View.VISIBLE);\n            } else {\n                mBinding.verifyEmailButton.setVisibility(View.VISIBLE);\n                mBinding.enrollMfa.setVisibility(View.GONE);\n            }\n        } else {\n            mBinding.status.setText(R.string.multi_factor_signed_out);\n            mBinding.detail.setText(null);\n            mBinding.mfaInfo.setText(null);\n\n            mBinding.emailSignInButton.setVisibility(View.VISIBLE);\n            mBinding.signedInButtons.setVisibility(View.GONE);\n        }\n    }\n\n    private void showDisclaimer() {\n        new AlertDialog.Builder(getContext())\n                .setTitle(\"Warning\")\n                .setMessage(\"Multi-factor authentication with SMS is currently only available for \" +\n                        \"Google Cloud Identity Platform projects. For more information see: \" +\n                        \"https://cloud.google.com/identity-platform/docs/android/mfa\")\n                .setPositiveButton(\"OK\", null)\n                .show();\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/MultiFactorSignInFragment.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.android.gms.tasks.OnFailureListener;\nimport com.google.android.gms.tasks.OnSuccessListener;\nimport com.google.firebase.FirebaseException;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.MultiFactorInfo;\nimport com.google.firebase.auth.MultiFactorResolver;\nimport com.google.firebase.auth.PhoneAuthCredential;\nimport com.google.firebase.auth.PhoneAuthOptions;\nimport com.google.firebase.auth.PhoneAuthProvider;\nimport com.google.firebase.auth.PhoneMultiFactorGenerator;\nimport com.google.firebase.auth.PhoneMultiFactorInfo;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentMultiFactorSignInBinding;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static android.text.TextUtils.isEmpty;\n\n/**\n * Fragment that handles MFA sign-in\n */\npublic class MultiFactorSignInFragment extends BaseFragment {\n\n    private static final String KEY_VERIFICATION_ID = \"key_verification_id\";\n    public static final String EXTRA_MFA_RESOLVER = \"EXTRA_MFA_RESOLVER\";\n\n    private FragmentMultiFactorSignInBinding mBinding;\n\n    private MultiFactorResolver mMultiFactorResolver;\n    private PhoneAuthCredential mPhoneAuthCredential;\n    private String mVerificationId;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentMultiFactorSignInBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (savedInstanceState != null) {\n            onViewStateRestored(savedInstanceState);\n        }\n\n        List<Button> phoneFactorButtonList = new ArrayList<>();\n        phoneFactorButtonList.add(mBinding.phoneFactor1);\n        phoneFactorButtonList.add(mBinding.phoneFactor2);\n        phoneFactorButtonList.add(mBinding.phoneFactor3);\n        phoneFactorButtonList.add(mBinding.phoneFactor4);\n        phoneFactorButtonList.add(mBinding.phoneFactor5);\n\n        for (Button button : phoneFactorButtonList) {\n            button.setVisibility(View.GONE);\n        }\n\n        mBinding.finishMfaSignIn.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onClickFinishSignIn();\n            }\n        });\n\n        mMultiFactorResolver = getResolverFromArguments(requireArguments());\n        List<MultiFactorInfo> multiFactorInfoList = mMultiFactorResolver.getHints();\n\n        for (int i = 0; i < multiFactorInfoList.size(); ++i) {\n            PhoneMultiFactorInfo phoneMultiFactorInfo = (PhoneMultiFactorInfo) multiFactorInfoList.get(i);\n            Button button = phoneFactorButtonList.get(i);\n            button.setVisibility(View.VISIBLE);\n            button.setText(phoneMultiFactorInfo.getPhoneNumber());\n            button.setClickable(true);\n            button.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    PhoneAuthProvider.verifyPhoneNumber(\n                            PhoneAuthOptions.newBuilder()\n                                    .setActivity(requireActivity())\n                                    .setMultiFactorSession(mMultiFactorResolver.getSession())\n                                    .setMultiFactorHint(phoneMultiFactorInfo)\n                                    .setCallbacks(generateCallbacks())\n                                    // A timeout of 0 disables SMS-auto-retrieval.\n                                    .setTimeout(0L, TimeUnit.SECONDS)\n                                    .build());\n                }\n            });\n        }\n    }\n\n    @Override\n    public void onSaveInstanceState(Bundle bundle) {\n        super.onSaveInstanceState(bundle);\n        bundle.putString(KEY_VERIFICATION_ID, mVerificationId);\n    }\n\n    @Override\n    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {\n        super.onViewStateRestored(savedInstanceState);\n        if (savedInstanceState != null) {\n            mVerificationId = savedInstanceState.getString(KEY_VERIFICATION_ID);\n        }\n    }\n\n    private PhoneAuthProvider.OnVerificationStateChangedCallbacks generateCallbacks() {\n        return new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {\n            @Override\n            public void onVerificationCompleted(@NonNull PhoneAuthCredential phoneAuthCredential) {\n                MultiFactorSignInFragment.this.mPhoneAuthCredential = phoneAuthCredential;\n                mBinding.finishMfaSignIn.performClick();\n                Toast.makeText(getContext(), \"Verification complete!\", Toast.LENGTH_SHORT)\n                        .show();\n            }\n\n            @Override\n            public void onCodeSent(@NonNull String verificationId, @NonNull PhoneAuthProvider.ForceResendingToken token) {\n                MultiFactorSignInFragment.this.mVerificationId = verificationId;\n                mBinding.finishMfaSignIn.setClickable(true);\n            }\n\n            @Override\n            public void onVerificationFailed(@NonNull FirebaseException e) {\n                Toast.makeText(getContext(), \"Error: \" + e.getMessage(), Toast.LENGTH_SHORT)\n                        .show();\n            }\n        };\n    }\n\n    private MultiFactorResolver getResolverFromArguments(Bundle arguments) {\n        return arguments.getParcelable(EXTRA_MFA_RESOLVER);\n    }\n\n    private void onClickFinishSignIn() {\n        if (mPhoneAuthCredential == null) {\n            if (isEmpty(mBinding.smsCode.getText().toString())) {\n                Toast.makeText(getContext(), \"You need to enter an SMS code.\", Toast.LENGTH_SHORT)\n                        .show();\n                return;\n            }\n            mPhoneAuthCredential =\n                    PhoneAuthProvider.getCredential(\n                            mVerificationId, mBinding.smsCode.getText().toString());\n        }\n        mMultiFactorResolver\n                .resolveSignIn(PhoneMultiFactorGenerator.getAssertion(mPhoneAuthCredential))\n                .addOnSuccessListener(\n                        new OnSuccessListener<AuthResult>() {\n                            @Override\n                            public void onSuccess(AuthResult authResult) {\n                                NavHostFragment.findNavController(MultiFactorSignInFragment.this)\n                                        .navigate(R.id.action_mfasignin_to_mfa);\n                            }\n                        })\n                .addOnFailureListener(\n                        new OnFailureListener() {\n                            @Override\n                            public void onFailure(@NonNull Exception e) {\n                                Toast.makeText(getContext(), \"Error: \" + e.getMessage(), Toast.LENGTH_SHORT)\n                                        .show();\n                            }\n                        });\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/MultiFactorUnenrollFragment.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.MultiFactorInfo;\nimport com.google.firebase.auth.PhoneMultiFactorInfo;\nimport com.google.firebase.quickstart.auth.databinding.FragmentMultiFactorSignInBinding;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MultiFactorUnenrollFragment extends BaseFragment {\n\n    private FragmentMultiFactorSignInBinding mBinding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentMultiFactorSignInBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mBinding.smsCode.setVisibility(View.GONE);\n        mBinding.finishMfaSignIn.setVisibility(View.GONE);\n\n        List<Button> phoneFactorButtonList = new ArrayList<>();\n        phoneFactorButtonList.add(mBinding.phoneFactor1);\n        phoneFactorButtonList.add(mBinding.phoneFactor2);\n        phoneFactorButtonList.add(mBinding.phoneFactor3);\n        phoneFactorButtonList.add(mBinding.phoneFactor4);\n        phoneFactorButtonList.add(mBinding.phoneFactor5);\n\n        for (Button button : phoneFactorButtonList) {\n            button.setVisibility(View.GONE);\n        }\n\n        List<MultiFactorInfo> multiFactorInfoList =\n                FirebaseAuth.getInstance().getCurrentUser().getMultiFactor().getEnrolledFactors();\n\n        for (int i = 0; i < multiFactorInfoList.size(); ++i) {\n            PhoneMultiFactorInfo phoneMultiFactorInfo = (PhoneMultiFactorInfo) multiFactorInfoList.get(i);\n            Button button = phoneFactorButtonList.get(i);\n            button.setVisibility(View.VISIBLE);\n            button.setText(phoneMultiFactorInfo.getPhoneNumber());\n            button.setClickable(true);\n            button.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    FirebaseAuth.getInstance()\n                            .getCurrentUser()\n                            .getMultiFactor()\n                            .unenroll(phoneMultiFactorInfo)\n                            .addOnCompleteListener(\n                                    new OnCompleteListener<Void>() {\n                                        @Override\n                                        public void onComplete(@NonNull Task<Void> task) {\n                                            if (task.isSuccessful()) {\n                                                Toast.makeText(getContext(),\n                                                        \"Successfully unenrolled!\", Toast.LENGTH_SHORT).show();\n                                                NavHostFragment.findNavController(MultiFactorUnenrollFragment.this)\n                                                        .popBackStack();\n                                            } else {\n                                                Toast.makeText(getContext(),\n                                                        \"Unable to unenroll second factor. \" + task.getException(), Toast.LENGTH_SHORT).show();\n                                            }\n                                        }\n                                    });\n                }\n            });\n        }\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/PasswordlessActivity.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.material.snackbar.Snackbar;\nimport com.google.firebase.auth.ActionCodeSettings;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseAuthActionCodeException;\nimport com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.ActivityPasswordlessBinding;\n\n/**\n * Demonstrate Firebase Authentication without a password, using a link sent to an\n * email address.\n */\npublic class PasswordlessActivity extends BaseActivity implements View.OnClickListener {\n\n    private static final String TAG = \"PasswordlessSignIn\";\n    private static final String KEY_PENDING_EMAIL = \"key_pending_email\";\n\n    private FirebaseAuth mAuth;\n\n    private ActivityPasswordlessBinding mBinding;\n\n    private String mPendingEmail;\n    private String mEmailLink;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mBinding = ActivityPasswordlessBinding.inflate(getLayoutInflater());\n        setContentView(mBinding.getRoot());\n        setProgressBar(mBinding.progressBar);\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        mBinding.passwordlessSendEmailButton.setOnClickListener(this);\n        mBinding.passwordlessSignInButton.setOnClickListener(this);\n        mBinding.signOutButton.setOnClickListener(this);\n\n        // Restore the \"pending\" email address\n        if (savedInstanceState != null) {\n            mPendingEmail = savedInstanceState.getString(KEY_PENDING_EMAIL, null);\n            mBinding.fieldEmail.setText(mPendingEmail);\n        }\n\n        // Check if the Intent that started the Activity contains an email sign-in link.\n        checkIntent(getIntent());\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        updateUI(mAuth.getCurrentUser());\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        super.onNewIntent(intent);\n        checkIntent(intent);\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        outState.putString(KEY_PENDING_EMAIL, mPendingEmail);\n    }\n\n    /**\n     * Check to see if the Intent has an email link, and if so set up the UI accordingly.\n     * This can be called from either onCreate or onNewIntent, depending on how the Activity\n     * was launched.\n     */\n    private void checkIntent(@Nullable Intent intent) {\n        if (intentHasEmailLink(intent)) {\n            mEmailLink = intent.getData().toString();\n\n            mBinding.status.setText(R.string.status_link_found);\n            mBinding.passwordlessSendEmailButton.setEnabled(false);\n            mBinding.passwordlessSignInButton.setEnabled(true);\n        } else {\n            mBinding.status.setText(R.string.status_email_not_sent);\n            mBinding.passwordlessSendEmailButton.setEnabled(true);\n            mBinding.passwordlessSignInButton.setEnabled(false);\n        }\n    }\n\n    /**\n     * Determine if the given Intent contains an email sign-in link.\n     */\n    private boolean intentHasEmailLink(@Nullable Intent intent) {\n        if (intent != null && intent.getData() != null) {\n            String intentData = intent.getData().toString();\n            if (mAuth.isSignInWithEmailLink(intentData)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Send an email sign-in link to the specified email.\n     */\n    private void sendSignInLink(String email) {\n        ActionCodeSettings settings = ActionCodeSettings.newBuilder()\n                .setAndroidPackageName(\n                        getPackageName(),\n                        false, /* install if not available? */\n                        null   /* minimum app version */)\n                .setHandleCodeInApp(true)\n                .setUrl(\"https://auth.example.com/emailSignInLink\")\n                .build();\n\n        hideKeyboard(mBinding.fieldEmail);\n        showProgressBar();\n\n        mAuth.sendSignInLinkToEmail(email, settings)\n                .addOnCompleteListener(new OnCompleteListener<Void>() {\n                    @Override\n                    public void onComplete(@NonNull Task<Void> task) {\n                        hideProgressBar();\n\n                        if (task.isSuccessful()) {\n                            Log.d(TAG, \"Link sent\");\n                            showSnackbar(\"Sign-in link sent!\");\n\n                            mPendingEmail = email;\n                            mBinding.status.setText(R.string.status_email_sent);\n                        } else {\n                            Exception e = task.getException();\n                            Log.w(TAG, \"Could not send link\", e);\n                            showSnackbar(\"Failed to send link.\");\n\n                            if (e instanceof FirebaseAuthInvalidCredentialsException) {\n                                mBinding.fieldEmail.setError(\"Invalid email address.\");\n                            }\n                        }\n                    }\n                });\n    }\n\n    /**\n     * Sign in using an email address and a link, the link is passed to the Activity\n     * from the dynamic link contained in the email.\n     */\n    private void signInWithEmailLink(String email, String link) {\n        Log.d(TAG, \"signInWithLink:\" + link);\n\n        hideKeyboard(mBinding.fieldEmail);\n        showProgressBar();\n\n        mAuth.signInWithEmailLink(email, link)\n                .addOnCompleteListener(new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        hideProgressBar();\n                        mPendingEmail = null;\n\n                        if (task.isSuccessful()) {\n                            Log.d(TAG, \"signInWithEmailLink:success\");\n\n                            mBinding.fieldEmail.setText(null);\n                            updateUI(task.getResult().getUser());\n                        } else {\n                            Log.w(TAG, \"signInWithEmailLink:failure\", task.getException());\n                            updateUI(null);\n\n                            if (task.getException() instanceof FirebaseAuthActionCodeException) {\n                                showSnackbar(\"Invalid or expired sign-in link.\");\n                            }\n                        }\n                    }\n                });\n    }\n\n    private void onSendLinkClicked() {\n        String email = mBinding.fieldEmail.getText().toString();\n        if (TextUtils.isEmpty(email)) {\n            mBinding.fieldEmail.setError(\"Email must not be empty.\");\n            return;\n        }\n\n        sendSignInLink(email);\n    }\n\n    private void onSignInClicked() {\n        String email = mBinding.fieldEmail.getText().toString();\n        if (TextUtils.isEmpty(email)) {\n            mBinding.fieldEmail.setError(\"Email must not be empty.\");\n            return;\n        }\n\n        signInWithEmailLink(email, mEmailLink);\n    }\n\n    private void onSignOutClicked() {\n        mAuth.signOut();\n\n        updateUI(null);\n        mBinding.status.setText(R.string.status_email_not_sent);\n    }\n\n    private void updateUI(@Nullable FirebaseUser user) {\n        if (user != null) {\n            mBinding.status.setText(getString(R.string.passwordless_status_fmt,\n                    user.getEmail(), user.isEmailVerified()));\n\n            mBinding.fieldEmail.setVisibility(View.GONE);\n            mBinding.passwordlessButtons.setVisibility(View.GONE);\n            mBinding.signOutButton.setVisibility(View.VISIBLE);\n        } else {\n            mBinding.fieldEmail.setVisibility(View.VISIBLE);\n            mBinding.passwordlessButtons.setVisibility(View.VISIBLE);\n            mBinding.signOutButton.setVisibility(View.GONE);\n        }\n    }\n\n    private void showSnackbar(String message) {\n        Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show();\n    }\n\n    @Override\n    public void onClick(View view) {\n        //Due to bump in Java version, we can not use view ids in switch\n        //(see: http://tools.android.com/tips/non-constant-fields), so we\n        //need to use if/else:\n\n        int viewId = view.getId();\n        if (viewId == R.id.passwordlessSendEmailButton) {\n            onSendLinkClicked();\n        } else if (viewId == R.id.passwordlessSignInButton) {\n            onSignInClicked();\n        } else if (viewId == R.id.signOutButton) {\n            onSignOutClicked();\n        }\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/PhoneAuthFragment.java",
    "content": "package com.google.firebase.quickstart.auth.java;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.material.snackbar.Snackbar;\nimport com.google.firebase.FirebaseException;\nimport com.google.firebase.FirebaseTooManyRequestsException;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.auth.PhoneAuthCredential;\nimport com.google.firebase.auth.PhoneAuthOptions;\nimport com.google.firebase.auth.PhoneAuthProvider;\nimport com.google.firebase.quickstart.auth.R;\nimport com.google.firebase.quickstart.auth.databinding.FragmentPhoneAuthBinding;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class PhoneAuthFragment extends Fragment {\n\n    private static final String TAG = \"PhoneAuthFragment\";\n\n    private static final String KEY_VERIFY_IN_PROGRESS = \"key_verify_in_progress\";\n\n    private static final int STATE_INITIALIZED = 1;\n    private static final int STATE_CODE_SENT = 2;\n    private static final int STATE_VERIFY_FAILED = 3;\n    private static final int STATE_VERIFY_SUCCESS = 4;\n    private static final int STATE_SIGNIN_FAILED = 5;\n    private static final int STATE_SIGNIN_SUCCESS = 6;\n\n    private FirebaseAuth mAuth;\n\n    private boolean mVerificationInProgress = false;\n    private String mVerificationId;\n    private PhoneAuthProvider.ForceResendingToken mResendToken;\n    private PhoneAuthProvider.OnVerificationStateChangedCallbacks mCallbacks;\n\n    private FragmentPhoneAuthBinding mBinding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentPhoneAuthBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        // Restore instance state\n        if (savedInstanceState != null) {\n            onViewStateRestored(savedInstanceState);\n        }\n\n        // Assign click listeners\n        mBinding.buttonStartVerification.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (!validatePhoneNumber()) {\n                    return;\n                }\n                startPhoneNumberVerification(mBinding.fieldPhoneNumber.getText().toString());\n            }\n        });\n        mBinding.buttonVerifyPhone.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                String code = mBinding.fieldVerificationCode.getText().toString();\n                if (TextUtils.isEmpty(code)) {\n                    mBinding.fieldVerificationCode.setError(\"Cannot be empty.\");\n                    return;\n                }\n                verifyPhoneNumberWithCode(mVerificationId, code);\n            }\n        });\n        mBinding.buttonResend.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (!validatePhoneNumber()) {\n                    return;\n                }\n                resendVerificationCode(mBinding.fieldPhoneNumber.getText().toString(), mResendToken);\n            }\n        });\n        mBinding.signOutButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                signOut();\n            }\n        });\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        // Initialize phone auth callbacks\n        mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {\n\n            @Override\n            public void onVerificationCompleted(PhoneAuthCredential credential) {\n                // This callback will be invoked in two situations:\n                // 1 - Instant verification. In some cases the phone number can be instantly\n                //     verified without needing to send or enter a verification code.\n                // 2 - Auto-retrieval. On some devices Google Play services can automatically\n                //     detect the incoming verification SMS and perform verification without\n                //     user action.\n                Log.d(TAG, \"onVerificationCompleted:\" + credential);\n                mVerificationInProgress = false;\n\n                // Update the UI and attempt sign in with the phone credential\n                updateUI(STATE_VERIFY_SUCCESS, credential);\n                signInWithPhoneAuthCredential(credential);\n            }\n\n            @Override\n            public void onVerificationFailed(FirebaseException e) {\n                // This callback is invoked in an invalid request for verification is made,\n                // for instance if the the phone number format is not valid.\n                Log.w(TAG, \"onVerificationFailed\", e);\n                mVerificationInProgress = false;\n\n                if (e instanceof FirebaseAuthInvalidCredentialsException) {\n                    // Invalid request\n                    mBinding.fieldPhoneNumber.setError(\"Invalid phone number.\");\n                } else if (e instanceof FirebaseTooManyRequestsException) {\n                    // The SMS quota for the project has been exceeded\n                    Snackbar.make(view, \"Quota exceeded.\",\n                            Snackbar.LENGTH_SHORT).show();\n                }\n\n                // Show a message and update the UI\n                updateUI(STATE_VERIFY_FAILED);\n            }\n\n            @Override\n            public void onCodeSent(@NonNull String verificationId,\n                                   @NonNull PhoneAuthProvider.ForceResendingToken token) {\n                // The SMS verification code has been sent to the provided phone number, we\n                // now need to ask the user to enter the code and then construct a credential\n                // by combining the code with a verification ID.\n                Log.d(TAG, \"onCodeSent:\" + verificationId);\n\n                // Save verification ID and resending token so we can use them later\n                mVerificationId = verificationId;\n                mResendToken = token;\n\n                // Update UI\n                updateUI(STATE_CODE_SENT);\n            }\n        };\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        // Check if user is signed in (non-null) and update UI accordingly.\n        FirebaseUser currentUser = mAuth.getCurrentUser();\n        updateUI(currentUser);\n\n        if (mVerificationInProgress && validatePhoneNumber()) {\n            startPhoneNumberVerification(mBinding.fieldPhoneNumber.getText().toString());\n        }\n    }\n\n    @Override\n    public void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        outState.putBoolean(KEY_VERIFY_IN_PROGRESS, mVerificationInProgress);\n    }\n\n    @Override\n    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {\n        super.onViewStateRestored(savedInstanceState);\n        if (savedInstanceState != null) {\n            mVerificationInProgress = savedInstanceState.getBoolean(KEY_VERIFY_IN_PROGRESS);\n        }\n    }\n\n    private void startPhoneNumberVerification(String phoneNumber) {\n        PhoneAuthOptions options =\n          PhoneAuthOptions.newBuilder(mAuth) \n              .setPhoneNumber(phoneNumber) // Phone number to verify\n              .setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit\n              .setActivity(requireActivity()) // Activity (for callback binding)\n              .setCallbacks(mCallbacks) // OnVerificationStateChangedCallbacks\n              .build();\n          PhoneAuthProvider.verifyPhoneNumber(options);     \n\n        mVerificationInProgress = true;\n    }\n\n    private void verifyPhoneNumberWithCode(String verificationId, String code) {\n        PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, code);\n        signInWithPhoneAuthCredential(credential);\n    }\n\n    private void resendVerificationCode(String phoneNumber,\n                                        PhoneAuthProvider.ForceResendingToken token) {\n        PhoneAuthOptions options =\n                PhoneAuthOptions.newBuilder(mAuth)\n                        .setPhoneNumber(phoneNumber)       // Phone number to verify\n                        .setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit\n                        .setActivity(requireActivity())                 // Activity (for callback binding)\n                        .setCallbacks(mCallbacks)          // OnVerificationStateChangedCallbacks\n                        .setForceResendingToken(token)     // ForceResendingToken from callbacks\n                        .build();\n        PhoneAuthProvider.verifyPhoneNumber(options);\n    }\n\n    private void signInWithPhoneAuthCredential(PhoneAuthCredential credential) {\n        mAuth.signInWithCredential(credential)\n                .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        if (task.isSuccessful()) {\n                            // Sign in success, update UI with the signed-in user's information\n                            Log.d(TAG, \"signInWithCredential:success\");\n\n                            FirebaseUser user = task.getResult().getUser();\n                            updateUI(STATE_SIGNIN_SUCCESS, user);\n                        } else {\n                            // Sign in failed, display a message and update the UI\n                            Log.w(TAG, \"signInWithCredential:failure\", task.getException());\n                            if (task.getException() instanceof FirebaseAuthInvalidCredentialsException) {\n                                // The verification code entered was invalid\n                                mBinding.fieldVerificationCode.setError(\"Invalid code.\");\n                            }\n                            // Update UI\n                            updateUI(STATE_SIGNIN_FAILED);\n                        }\n                    }\n                });\n    }\n\n    private void signOut() {\n        mAuth.signOut();\n        updateUI(STATE_INITIALIZED);\n    }\n\n    private void updateUI(int uiState) {\n        updateUI(uiState, mAuth.getCurrentUser(), null);\n    }\n\n    private void updateUI(FirebaseUser user) {\n        if (user != null) {\n            updateUI(STATE_SIGNIN_SUCCESS, user);\n        } else {\n            updateUI(STATE_INITIALIZED);\n        }\n    }\n\n    private void updateUI(int uiState, FirebaseUser user) {\n        updateUI(uiState, user, null);\n    }\n\n    private void updateUI(int uiState, PhoneAuthCredential cred) {\n        updateUI(uiState, null, cred);\n    }\n\n    private void updateUI(int uiState, FirebaseUser user, PhoneAuthCredential cred) {\n        switch (uiState) {\n            case STATE_INITIALIZED:\n                // Initialized state, show only the phone number field and start button\n                enableViews(mBinding.buttonStartVerification, mBinding.fieldPhoneNumber);\n                disableViews(mBinding.buttonVerifyPhone, mBinding.buttonResend, mBinding.fieldVerificationCode);\n                mBinding.detail.setText(null);\n                break;\n            case STATE_CODE_SENT:\n                // Code sent state, show the verification field, the\n                enableViews(mBinding.buttonVerifyPhone, mBinding.buttonResend, mBinding.fieldPhoneNumber, mBinding.fieldVerificationCode);\n                disableViews(mBinding.buttonStartVerification);\n                mBinding.detail.setText(R.string.status_code_sent);\n                break;\n            case STATE_VERIFY_FAILED:\n                // Verification has failed, show all options\n                enableViews(mBinding.buttonStartVerification, mBinding.buttonVerifyPhone, mBinding.buttonResend, mBinding.fieldPhoneNumber,\n                        mBinding.fieldVerificationCode);\n                mBinding.detail.setText(R.string.status_verification_failed);\n                break;\n            case STATE_VERIFY_SUCCESS:\n                // Verification has succeeded, proceed to firebase sign in\n                disableViews(mBinding.buttonStartVerification, mBinding.buttonVerifyPhone, mBinding.buttonResend, mBinding.fieldPhoneNumber,\n                        mBinding.fieldVerificationCode);\n                mBinding.detail.setText(R.string.status_verification_succeeded);\n\n                // Set the verification text based on the credential\n                if (cred != null) {\n                    if (cred.getSmsCode() != null) {\n                        mBinding.fieldVerificationCode.setText(cred.getSmsCode());\n                    } else {\n                        mBinding.fieldVerificationCode.setText(R.string.instant_validation);\n                    }\n                }\n\n                break;\n            case STATE_SIGNIN_FAILED:\n                // No-op, handled by sign-in check\n                mBinding.detail.setText(R.string.status_sign_in_failed);\n                break;\n            case STATE_SIGNIN_SUCCESS:\n                // Np-op, handled by sign-in check\n                break;\n        }\n\n        if (user == null) {\n            // Signed out\n            mBinding.phoneAuthFields.setVisibility(View.VISIBLE);\n            mBinding.signOutButton.setVisibility(View.GONE);\n\n            mBinding.status.setText(R.string.signed_out);\n        } else {\n            // Signed in\n            mBinding.phoneAuthFields.setVisibility(View.GONE);\n            mBinding.signOutButton.setVisibility(View.VISIBLE);\n\n            enableViews(mBinding.fieldPhoneNumber, mBinding.fieldVerificationCode);\n            mBinding.fieldPhoneNumber.setText(null);\n            mBinding.fieldVerificationCode.setText(null);\n\n            mBinding.status.setText(R.string.signed_in);\n            mBinding.detail.setText(getString(R.string.firebase_status_fmt, user.getUid()));\n        }\n    }\n\n    private boolean validatePhoneNumber() {\n        String phoneNumber = mBinding.fieldPhoneNumber.getText().toString();\n        if (TextUtils.isEmpty(phoneNumber)) {\n            mBinding.fieldPhoneNumber.setError(\"Invalid phone number.\");\n            return false;\n        }\n\n        return true;\n    }\n\n    private void enableViews(View... views) {\n        for (View v : views) {\n            v.setEnabled(true);\n        }\n    }\n\n    private void disableViews(View... views) {\n        for (View v : views) {\n            v.setEnabled(false);\n        }\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/java/TokenBroadcastReceiver.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.java;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.util.Log;\n\n/**\n * Receiver to capture tokens broadcast via ADB and insert them into the\n * running application to facilitate easy testing of custom authentication.\n */\npublic abstract class TokenBroadcastReceiver extends BroadcastReceiver {\n\n    private static final String TAG = \"TokenBroadcastReceiver\";\n\n    public static final String ACTION_TOKEN = \"com.google.example.ACTION_TOKEN\";\n    public static final String EXTRA_KEY_TOKEN = \"key_token\";\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        Log.d(TAG, \"onReceive:\" + intent);\n\n        if (ACTION_TOKEN.equals(intent.getAction())) {\n            String token = intent.getExtras().getString(EXTRA_KEY_TOKEN);\n            onNewToken(token);\n        }\n    }\n\n    public static IntentFilter getFilter() {\n        IntentFilter filter = new IntentFilter(ACTION_TOKEN);\n        return filter;\n    }\n\n    public abstract void onNewToken(String token);\n\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/AnonymousAuthFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport com.google.firebase.auth.EmailAuthProvider\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentAnonymousAuthBinding\n\nclass AnonymousAuthFragment : BaseFragment() {\n    private var _binding: FragmentAnonymousAuthBinding? = null\n    private val binding: FragmentAnonymousAuthBinding\n        get() = _binding!!\n\n    private lateinit var auth: FirebaseAuth\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View {\n        _binding = FragmentAnonymousAuthBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        setProgressBar(binding.progressBar)\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        // Click listeners\n        binding.buttonAnonymousSignIn.setOnClickListener {\n            signInAnonymously()\n        }\n        binding.buttonAnonymousSignOut.setOnClickListener {\n            signOut()\n        }\n        binding.buttonLinkAccount.setOnClickListener {\n            linkAccount()\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        // Check if user is signed in (non-null) and update UI accordingly.\n        val currentUser = auth.currentUser\n        updateUI(currentUser)\n    }\n\n    private fun signInAnonymously() {\n        showProgressBar()\n        auth.signInAnonymously()\n            .addOnCompleteListener(requireActivity()) { task ->\n                if (task.isSuccessful) {\n                    // Sign in success, update UI with the signed-in user's information\n                    Log.d(TAG, \"signInAnonymously:success\")\n                    val user = auth.currentUser\n                    updateUI(user)\n                } else {\n                    // If sign in fails, display a message to the user.\n                    Log.w(TAG, \"signInAnonymously:failure\", task.exception)\n                    Toast.makeText(\n                        context,\n                        \"Authentication failed.\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                    updateUI(null)\n                }\n\n                hideProgressBar()\n            }\n    }\n\n    private fun signOut() {\n        auth.signOut()\n        updateUI(null)\n    }\n\n    private fun linkAccount() {\n        // Make sure form is valid\n        if (!validateLinkForm()) {\n            return\n        }\n\n        // Get email and password from the form\n        val email = binding.fieldEmail.text.toString()\n        val password = binding.fieldPassword.text.toString()\n\n        // Create EmailAuthCredential with email and password\n        val credential = EmailAuthProvider.getCredential(email, password)\n\n        // Link the anonymous user to the email credential\n        showProgressBar()\n\n        auth.currentUser!!.linkWithCredential(credential)\n            .addOnCompleteListener(requireActivity()) { task ->\n                if (task.isSuccessful) {\n                    Log.d(TAG, \"linkWithCredential:success\")\n                    val user = task.result?.user\n                    updateUI(user)\n                } else {\n                    Log.w(TAG, \"linkWithCredential:failure\", task.exception)\n                    Toast.makeText(\n                        context,\n                        \"Authentication failed.\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                    updateUI(null)\n                }\n\n                hideProgressBar()\n            }\n    }\n\n    private fun validateLinkForm(): Boolean {\n        var valid = true\n\n        val email = binding.fieldEmail.text.toString()\n        if (TextUtils.isEmpty(email)) {\n            binding.fieldEmail.error = \"Required.\"\n            valid = false\n        } else {\n            binding.fieldEmail.error = null\n        }\n\n        val password = binding.fieldPassword.text.toString()\n        if (TextUtils.isEmpty(password)) {\n            binding.fieldPassword.error = \"Required.\"\n            valid = false\n        } else {\n            binding.fieldPassword.error = null\n        }\n\n        return valid\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        hideProgressBar()\n        val isSignedIn = user != null\n\n        // Status text\n        if (isSignedIn) {\n            binding.anonymousStatusId.text = getString(R.string.id_fmt, user!!.uid)\n            binding.anonymousStatusEmail.text = getString(R.string.email_fmt, user.email)\n        } else {\n            binding.anonymousStatusId.setText(R.string.signed_out)\n            binding.anonymousStatusEmail.text = null\n        }\n\n        // Button visibility\n        binding.buttonAnonymousSignIn.isEnabled = !isSignedIn\n        binding.buttonAnonymousSignOut.isEnabled = isSignedIn\n        binding.buttonLinkAccount.isEnabled = isSignedIn\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"AnonymousAuth\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/BaseActivity.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.content.Context\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.ProgressBar\nimport androidx.appcompat.app.AppCompatActivity\n\nopen class BaseActivity : AppCompatActivity() {\n\n    private var progressBar: ProgressBar? = null\n\n    fun setProgressBar(bar: ProgressBar) {\n        progressBar = bar\n    }\n\n    fun showProgressBar() {\n        progressBar?.visibility = View.VISIBLE\n    }\n\n    fun hideProgressBar() {\n        progressBar?.visibility = View.INVISIBLE\n    }\n\n    fun hideKeyboard(view: View) {\n        val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager\n        imm.hideSoftInputFromWindow(view.windowToken, 0)\n    }\n\n    public override fun onStop() {\n        super.onStop()\n        hideProgressBar()\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/BaseFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.content.Context\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.ProgressBar\nimport androidx.fragment.app.Fragment\n\nopen class BaseFragment : Fragment() {\n\n    private var progressBar: ProgressBar? = null\n\n    fun setProgressBar(bar: ProgressBar) {\n        progressBar = bar\n    }\n\n    fun showProgressBar() {\n        progressBar?.visibility = View.VISIBLE\n    }\n\n    fun hideProgressBar() {\n        progressBar?.visibility = View.INVISIBLE\n    }\n\n    fun hideKeyboard(view: View) {\n        val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager\n        imm.hideSoftInputFromWindow(view.windowToken, 0)\n    }\n\n    override fun onStop() {\n        super.onStop()\n        hideProgressBar()\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/ChooserFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ArrayAdapter\nimport android.widget.TextView\nimport androidx.fragment.app.Fragment\nimport androidx.navigation.fragment.findNavController\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentChooserBinding\n\n/**\n * Simple list-based Fragment to redirect to one of the other Fragments. This Fragment does not\n * contain any useful code related to Firebase Authentication. You may want to start with\n * one of the following Files:\n *     {@link GoogleSignInFragment}\n *     {@link FacebookLoginFragment}\n *     {@link EmailPasswordFragment}\n *     {@link PasswordlessActivity}\n *     {@link PhoneAuthFragment}\n *     {@link AnonymousAuthFragment}\n *     {@link CustomAuthFragment}\n *     {@link GenericIdpFragment}\n *     {@link MultiFactorFragment}\n */\nclass ChooserFragment : Fragment() {\n\n    private var _binding: FragmentChooserBinding? = null\n    private val binding: FragmentChooserBinding\n        get() = _binding!!\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View {\n        _binding = FragmentChooserBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // Set up Adapter\n        val adapter = MyArrayAdapter(requireContext(), android.R.layout.simple_list_item_2)\n        adapter.setDescriptionIds(DESCRIPTION_IDS)\n\n        binding.listView.adapter = adapter\n        binding.listView.setOnItemClickListener { _, _, position, _ ->\n            val actionId = NAV_ACTIONS[position]\n            findNavController().navigate(actionId)\n        }\n    }\n\n    class MyArrayAdapter(\n        private val ctx: Context,\n        resource: Int,\n    ) : ArrayAdapter<String>(ctx, resource, CLASS_NAMES) {\n        private var descriptionIds: IntArray? = null\n\n        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {\n            var view = convertView\n\n            if (convertView == null) {\n                val inflater = ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater\n                view = inflater.inflate(android.R.layout.simple_list_item_2, null)\n            }\n\n            // Android internal resource hence can't use synthetic binding\n            view?.findViewById<TextView>(android.R.id.text1)?.text = CLASS_NAMES[position]\n            view?.findViewById<TextView>(android.R.id.text2)?.setText(descriptionIds!![position])\n\n            return view!!\n        }\n\n        fun setDescriptionIds(descriptionIds: IntArray) {\n            this.descriptionIds = descriptionIds\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private val NAV_ACTIONS = arrayOf(\n            R.id.action_google,\n            R.id.action_facebook,\n            R.id.action_emailpassword,\n            R.id.action_passwordless,\n            R.id.action_phoneauth,\n            R.id.action_anonymousauth,\n            R.id.action_firebaseui,\n            R.id.action_customauth,\n            R.id.action_genericidp,\n            R.id.action_mfa,\n        )\n        private val CLASS_NAMES = arrayOf(\n            \"GoogleSignInFragment\",\n            \"FacebookLoginFragment\",\n            \"EmailPasswordFragment\",\n            \"PasswordlessActivity\",\n            \"PhoneAuthFragment\",\n            \"AnonymousAuthFragment\",\n            \"FirebaseUIFragment\",\n            \"CustomAuthFragment\",\n            \"GenericIdpFragment\",\n            \"MultiFactorFragment\",\n        )\n        private val DESCRIPTION_IDS = intArrayOf(\n            R.string.desc_google_sign_in,\n            R.string.desc_facebook_login,\n            R.string.desc_emailpassword,\n            R.string.desc_passwordless,\n            R.string.desc_phone_auth,\n            R.string.desc_anonymous_auth,\n            R.string.desc_firebase_ui,\n            R.string.desc_custom_auth,\n            R.string.desc_generic_idp,\n            R.string.desc_multi_factor,\n        )\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/CustomAuthFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.fragment.app.Fragment\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentCustomBinding\n\n/**\n * Demonstrate Firebase Authentication using a custom minted token. For more information, see:\n * https://firebase.google.com/docs/auth/android/custom-auth\n */\nclass CustomAuthFragment : Fragment() {\n\n    private lateinit var auth: FirebaseAuth\n\n    private var _binding: FragmentCustomBinding? = null\n    private val binding: FragmentCustomBinding\n        get() = _binding!!\n\n    private var customToken: String? = null\n    private lateinit var tokenReceiver: TokenBroadcastReceiver\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        _binding = FragmentCustomBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // Button click listeners\n        binding.buttonSignIn.setOnClickListener { startSignIn() }\n\n        // Create token receiver (for demo purposes only)\n        tokenReceiver = object : TokenBroadcastReceiver() {\n            override fun onNewToken(token: String?) {\n                Log.d(TAG, \"onNewToken:$token\")\n                setCustomToken(token.toString())\n            }\n        }\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n    }\n\n    override fun onStart() {\n        super.onStart()\n        // Check if user is signed in (non-null) and update UI accordingly.\n        val currentUser = auth.currentUser\n        updateUI(currentUser)\n    }\n\n    override fun onResume() {\n        super.onResume()\n        requireActivity().registerReceiver(tokenReceiver, TokenBroadcastReceiver.filter)\n    }\n\n    override fun onPause() {\n        super.onPause()\n        requireActivity().unregisterReceiver(tokenReceiver)\n    }\n\n    private fun startSignIn() {\n        // Initiate sign in with custom token\n        customToken?.let {\n            auth.signInWithCustomToken(it)\n                .addOnCompleteListener(requireActivity()) { task ->\n                    if (task.isSuccessful) {\n                        // Sign in success, update UI with the signed-in user's information\n                        Log.d(TAG, \"signInWithCustomToken:success\")\n                        val user = auth.currentUser\n                        updateUI(user)\n                    } else {\n                        // If sign in fails, display a message to the user.\n                        Log.w(TAG, \"signInWithCustomToken:failure\", task.exception)\n                        Toast.makeText(\n                            context,\n                            \"Authentication failed.\",\n                            Toast.LENGTH_SHORT,\n                        ).show()\n                        updateUI(null)\n                    }\n                }\n        }\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        if (user != null) {\n            binding.textSignInStatus.text = getString(R.string.custom_auth_signin_status_user, user.uid)\n        } else {\n            binding.textSignInStatus.text = getString(R.string.custom_auth_signin_status_failed)\n        }\n    }\n\n    private fun setCustomToken(token: String) {\n        customToken = token\n\n        val status = \"Token:$customToken\"\n\n        // Enable/disable sign-in button and show the token\n        binding.buttonSignIn.isEnabled = true\n        binding.textTokenStatus.text = status\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"CustomAuthFragment\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/EmailPasswordFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.core.os.bundleOf\nimport androidx.navigation.fragment.findNavController\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseAuthMultiFactorException\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentEmailpasswordBinding\n\nclass EmailPasswordFragment : BaseFragment() {\n\n    private lateinit var auth: FirebaseAuth\n\n    private var _binding: FragmentEmailpasswordBinding? = null\n    private val binding: FragmentEmailpasswordBinding\n        get() = _binding!!\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        _binding = FragmentEmailpasswordBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        setProgressBar(binding.progressBar)\n\n        // Buttons\n        with(binding) {\n            emailSignInButton.setOnClickListener {\n                val email = binding.fieldEmail.text.toString()\n                val password = binding.fieldPassword.text.toString()\n                signIn(email, password)\n            }\n            emailCreateAccountButton.setOnClickListener {\n                val email = binding.fieldEmail.text.toString()\n                val password = binding.fieldPassword.text.toString()\n                createAccount(email, password)\n            }\n            signOutButton.setOnClickListener { signOut() }\n            verifyEmailButton.setOnClickListener { sendEmailVerification() }\n            reloadButton.setOnClickListener { reload() }\n        }\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n    }\n\n    public override fun onStart() {\n        super.onStart()\n        // Check if user is signed in (non-null) and update UI accordingly.\n        val currentUser = auth.currentUser\n        if (currentUser != null) {\n            reload()\n        }\n    }\n\n    private fun createAccount(email: String, password: String) {\n        Log.d(TAG, \"createAccount:$email\")\n        if (!validateForm()) {\n            return\n        }\n\n        showProgressBar()\n\n        auth.createUserWithEmailAndPassword(email, password)\n            .addOnCompleteListener(requireActivity()) { task ->\n                if (task.isSuccessful) {\n                    // Sign in success, update UI with the signed-in user's information\n                    Log.d(TAG, \"createUserWithEmail:success\")\n                    val user = auth.currentUser\n                    updateUI(user)\n                } else {\n                    // If sign in fails, display a message to the user.\n                    Log.w(TAG, \"createUserWithEmail:failure\", task.exception)\n                    Toast.makeText(\n                        context,\n                        \"Authentication failed.\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                    updateUI(null)\n                }\n\n                hideProgressBar()\n            }\n    }\n\n    private fun signIn(email: String, password: String) {\n        Log.d(TAG, \"signIn:$email\")\n        if (!validateForm()) {\n            return\n        }\n\n        showProgressBar()\n\n        auth.signInWithEmailAndPassword(email, password)\n            .addOnCompleteListener(requireActivity()) { task ->\n                if (task.isSuccessful) {\n                    // Sign in success, update UI with the signed-in user's information\n                    Log.d(TAG, \"signInWithEmail:success\")\n                    val user = auth.currentUser\n                    updateUI(user)\n                } else {\n                    // If sign in fails, display a message to the user.\n                    Log.w(TAG, \"signInWithEmail:failure\", task.exception)\n                    Toast.makeText(\n                        context,\n                        \"Authentication failed.\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                    updateUI(null)\n                    checkForMultiFactorFailure(task.exception!!)\n                }\n\n                if (!task.isSuccessful) {\n                    binding.status.setText(R.string.auth_failed)\n                }\n                hideProgressBar()\n            }\n    }\n\n    private fun signOut() {\n        auth.signOut()\n        updateUI(null)\n    }\n\n    private fun sendEmailVerification() {\n        // Disable button\n        binding.verifyEmailButton.isEnabled = false\n\n        // Send verification email\n        val user = auth.currentUser!!\n        user.sendEmailVerification()\n            .addOnCompleteListener(requireActivity()) { task ->\n                // Re-enable button\n                binding.verifyEmailButton.isEnabled = true\n\n                if (task.isSuccessful) {\n                    Toast.makeText(\n                        context,\n                        \"Verification email sent to ${user.email} \",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                } else {\n                    Log.e(TAG, \"sendEmailVerification\", task.exception)\n                    Toast.makeText(\n                        context,\n                        \"Failed to send verification email.\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                }\n            }\n    }\n\n    private fun reload() {\n        auth.currentUser!!.reload().addOnCompleteListener { task ->\n            if (task.isSuccessful) {\n                updateUI(auth.currentUser)\n                Toast.makeText(context, \"Reload successful!\", Toast.LENGTH_SHORT).show()\n            } else {\n                Log.e(TAG, \"reload\", task.exception)\n                Toast.makeText(context, \"Failed to reload user.\", Toast.LENGTH_SHORT).show()\n            }\n        }\n    }\n\n    private fun validateForm(): Boolean {\n        var valid = true\n\n        val email = binding.fieldEmail.text.toString()\n        if (TextUtils.isEmpty(email)) {\n            binding.fieldEmail.error = \"Required.\"\n            valid = false\n        } else {\n            binding.fieldEmail.error = null\n        }\n\n        val password = binding.fieldPassword.text.toString()\n        if (TextUtils.isEmpty(password)) {\n            binding.fieldPassword.error = \"Required.\"\n            valid = false\n        } else {\n            binding.fieldPassword.error = null\n        }\n\n        return valid\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        hideProgressBar()\n        if (user != null) {\n            binding.status.text = getString(\n                R.string.emailpassword_status_fmt,\n                user.email,\n                user.isEmailVerified,\n            )\n            binding.detail.text = getString(R.string.firebase_status_fmt, user.uid)\n\n            binding.emailPasswordButtons.visibility = View.GONE\n            binding.emailPasswordFields.visibility = View.GONE\n            binding.signedInButtons.visibility = View.VISIBLE\n\n            if (user.isEmailVerified) {\n                binding.verifyEmailButton.visibility = View.GONE\n            } else {\n                binding.verifyEmailButton.visibility = View.VISIBLE\n            }\n        } else {\n            binding.status.setText(R.string.signed_out)\n            binding.detail.text = null\n\n            binding.emailPasswordButtons.visibility = View.VISIBLE\n            binding.emailPasswordFields.visibility = View.VISIBLE\n            binding.signedInButtons.visibility = View.GONE\n        }\n    }\n\n    private fun checkForMultiFactorFailure(e: Exception) {\n        // Multi-factor authentication with SMS is currently only available for\n        // Google Cloud Identity Platform projects. For more information:\n        // https://cloud.google.com/identity-platform/docs/android/mfa\n        if (e is FirebaseAuthMultiFactorException) {\n            Log.w(TAG, \"multiFactorFailure\", e)\n            val resolver = e.resolver\n            val args = bundleOf(\n                MultiFactorSignInFragment.EXTRA_MFA_RESOLVER to resolver\n            )\n            findNavController().navigate(R.id.action_emailpassword_to_mfasignin, args)\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"EmailPassword\"\n        private const val RC_MULTI_FACTOR = 9005\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FacebookLoginFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport com.facebook.AccessToken\nimport com.facebook.CallbackManager\nimport com.facebook.FacebookCallback\nimport com.facebook.FacebookException\nimport com.facebook.login.LoginManager\nimport com.facebook.login.LoginResult\nimport com.google.firebase.auth.FacebookAuthProvider\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentFacebookBinding\n\n/**\n * Demonstrate Firebase Authentication using a Facebook access token.\n */\nclass FacebookLoginFragment : BaseFragment() {\n\n    private lateinit var auth: FirebaseAuth\n\n    private var _binding: FragmentFacebookBinding? = null\n    private val binding: FragmentFacebookBinding\n        get() = _binding!!\n\n    private lateinit var callbackManager: CallbackManager\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        _binding = FragmentFacebookBinding.inflate(layoutInflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        setProgressBar(binding.progressBar)\n\n        binding.buttonFacebookSignout.setOnClickListener { signOut() }\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        // Initialize Facebook Login button\n        callbackManager = CallbackManager.Factory.create()\n\n        binding.buttonFacebookLogin.setPermissions(\"email\", \"public_profile\")\n        binding.buttonFacebookLogin.registerCallback(\n            callbackManager,\n            object : FacebookCallback<LoginResult> {\n                override fun onSuccess(loginResult: LoginResult) {\n                    Log.d(TAG, \"facebook:onSuccess:$loginResult\")\n                    handleFacebookAccessToken(loginResult.accessToken)\n                }\n\n                override fun onCancel() {\n                    Log.d(TAG, \"facebook:onCancel\")\n                    updateUI(null)\n                }\n\n                override fun onError(error: FacebookException) {\n                    Log.d(TAG, \"facebook:onError\", error)\n                    updateUI(null)\n                }\n            },\n        )\n    }\n\n    override fun onStart() {\n        super.onStart()\n        // Check if user is signed in (non-null) and update UI accordingly.\n        val currentUser = auth.currentUser\n        updateUI(currentUser)\n    }\n\n    private fun handleFacebookAccessToken(token: AccessToken) {\n        Log.d(TAG, \"handleFacebookAccessToken:$token\")\n        showProgressBar()\n\n        val credential = FacebookAuthProvider.getCredential(token.token)\n        auth.signInWithCredential(credential)\n            .addOnCompleteListener(requireActivity()) { task ->\n                if (task.isSuccessful) {\n                    // Sign in success, update UI with the signed-in user's information\n                    Log.d(TAG, \"signInWithCredential:success\")\n                    val user = auth.currentUser\n                    updateUI(user)\n                } else {\n                    // If sign in fails, display a message to the user.\n                    Log.w(TAG, \"signInWithCredential:failure\", task.exception)\n                    Toast.makeText(\n                        context,\n                        \"Authentication failed.\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                    updateUI(null)\n                }\n\n                hideProgressBar()\n            }\n    }\n\n    fun signOut() {\n        auth.signOut()\n        LoginManager.getInstance().logOut()\n\n        updateUI(null)\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        hideProgressBar()\n        if (user != null) {\n            binding.status.text = getString(R.string.facebook_status_fmt, user.displayName)\n            binding.detail.text = getString(R.string.firebase_status_fmt, user.uid)\n\n            binding.buttonFacebookLogin.visibility = View.GONE\n            binding.buttonFacebookSignout.visibility = View.VISIBLE\n        } else {\n            binding.status.setText(R.string.signed_out)\n            binding.detail.text = null\n\n            binding.buttonFacebookLogin.visibility = View.VISIBLE\n            binding.buttonFacebookSignout.visibility = View.GONE\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"FacebookLogin\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/FirebaseUIFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.fragment.app.Fragment\nimport com.firebase.ui.auth.AuthUI\nimport com.firebase.ui.auth.FirebaseAuthUIActivityResultContract\nimport com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.BuildConfig\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentFirebaseUiBinding\n\n/**\n * Demonstrate authentication using the FirebaseUI-Android library. This fragment demonstrates\n * using FirebaseUI for basic email/password sign in.\n *\n * For more information, visit https://github.com/firebase/firebaseui-android\n */\nclass FirebaseUIFragment : Fragment() {\n\n    private lateinit var auth: FirebaseAuth\n\n    private var _binding: FragmentFirebaseUiBinding? = null\n    private val binding: FragmentFirebaseUiBinding\n        get() = _binding!!\n\n    // Build FirebaseUI sign in intent. For documentation on this operation and all\n    // possible customization see: https://github.com/firebase/firebaseui-android\n    private val signInLauncher = registerForActivityResult(\n        FirebaseAuthUIActivityResultContract(),\n    ) { result -> this.onSignInResult(result) }\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        _binding = FragmentFirebaseUiBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        binding.signInButton.setOnClickListener { startSignIn() }\n        binding.signOutButton.setOnClickListener { signOut() }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        updateUI(auth.currentUser)\n    }\n\n    private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) {\n        if (result.resultCode == Activity.RESULT_OK) {\n            // Sign in succeeded\n            updateUI(auth.currentUser)\n        } else {\n            // Sign in failed\n            Toast.makeText(context, \"Sign In Failed\", Toast.LENGTH_SHORT).show()\n            updateUI(null)\n        }\n    }\n\n    private fun startSignIn() {\n        val intent = AuthUI.getInstance().createSignInIntentBuilder()\n            .setCredentialManagerEnabled(!BuildConfig.DEBUG)\n            .setAvailableProviders(listOf(AuthUI.IdpConfig.EmailBuilder().build()))\n            .setLogo(R.mipmap.ic_launcher)\n            .build()\n\n        signInLauncher.launch(intent)\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        if (user != null) {\n            // Signed in\n            binding.status.text = getString(R.string.firebaseui_status_fmt, user.email)\n            binding.detail.text = getString(R.string.id_fmt, user.uid)\n\n            binding.signInButton.visibility = View.GONE\n            binding.signOutButton.visibility = View.VISIBLE\n        } else {\n            // Signed out\n            binding.status.setText(R.string.signed_out)\n            binding.detail.text = null\n\n            binding.signInButton.visibility = View.VISIBLE\n            binding.signOutButton.visibility = View.GONE\n        }\n    }\n\n    private fun signOut() {\n        AuthUI.getInstance().signOut(requireContext())\n        updateUI(null)\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/GenericIdpFragment.kt",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.AdapterView\nimport android.widget.ArrayAdapter\nimport android.widget.Toast\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.auth\nimport com.google.firebase.auth.oAuthProvider\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentGenericIdpBinding\nimport java.util.ArrayList\n\n/**\n * Demonstrate Firebase Authentication using a Generic Identity Provider (IDP).\n */\nclass GenericIdpFragment : BaseFragment() {\n\n    private lateinit var auth: FirebaseAuth\n\n    private var _binding: FragmentGenericIdpBinding? = null\n    private val binding: FragmentGenericIdpBinding\n        get() = _binding!!\n\n    private lateinit var spinnerAdapter: ArrayAdapter<String>\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        _binding = FragmentGenericIdpBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        // Set up button click listeners\n        binding.genericSignInButton.setOnClickListener { signIn() }\n        binding.signOutButton.setOnClickListener {\n            auth.signOut()\n            updateUI(null)\n        }\n\n        // Spinner\n        val providers = ArrayList(PROVIDER_MAP.keys)\n        spinnerAdapter = ArrayAdapter(requireContext(), R.layout.item_spinner_list, providers)\n        binding.providerSpinner.adapter = spinnerAdapter\n        binding.providerSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {\n            override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {\n                binding.genericSignInButton.text =\n                    getString(R.string.generic_signin_fmt, spinnerAdapter.getItem(position))\n            }\n\n            override fun onNothingSelected(parent: AdapterView<*>) {}\n        }\n        binding.providerSpinner.setSelection(0)\n    }\n\n    override fun onStart() {\n        super.onStart()\n        // Check if user is signed in (non-null) and update UI accordingly.\n        val currentUser = auth.currentUser\n        updateUI(currentUser)\n\n        // Look for a pending auth result\n        val pending = auth.pendingAuthResult\n        if (pending != null) {\n            pending.addOnSuccessListener { authResult ->\n                Log.d(TAG, \"checkPending:onSuccess:$authResult\")\n                updateUI(authResult.user)\n            }.addOnFailureListener { e ->\n                Log.w(TAG, \"checkPending:onFailure\", e)\n            }\n        } else {\n            Log.d(TAG, \"checkPending: null\")\n        }\n    }\n\n    private fun signIn() {\n        // Could add custom scopes here\n        val customScopes = ArrayList<String>()\n\n        // Examples of provider ID: apple.com (Apple), microsoft.com (Microsoft), yahoo.com (Yahoo)\n        val providerId = getProviderId()\n\n        auth.startActivityForSignInWithProvider(\n            requireActivity(),\n            oAuthProvider(providerId, auth) {\n                scopes = customScopes\n            },\n        )\n            .addOnSuccessListener { authResult ->\n                Log.d(TAG, \"activitySignIn:onSuccess:${authResult.user}\")\n                updateUI(authResult.user)\n            }\n            .addOnFailureListener { e ->\n                Log.w(TAG, \"activitySignIn:onFailure\", e)\n                showToast(getString(R.string.error_sign_in_failed))\n            }\n    }\n\n    private fun getProviderId(): String {\n        val providerName = spinnerAdapter.getItem(binding.providerSpinner.selectedItemPosition)\n        return PROVIDER_MAP[providerName!!] ?: error(\"No provider selected\")\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        hideProgressBar()\n        if (user != null) {\n            binding.status.text = getString(R.string.generic_status_fmt, user.displayName, user.email)\n            binding.detail.text = getString(R.string.firebase_status_fmt, user.uid)\n\n            binding.spinnerLayout.visibility = View.GONE\n            binding.genericSignInButton.visibility = View.GONE\n            binding.signOutButton.visibility = View.VISIBLE\n        } else {\n            binding.status.setText(R.string.signed_out)\n            binding.detail.text = null\n\n            binding.spinnerLayout.visibility = View.VISIBLE\n            binding.genericSignInButton.visibility = View.VISIBLE\n            binding.signOutButton.visibility = View.GONE\n        }\n    }\n\n    private fun showToast(message: String) {\n        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"GenericIdp\"\n        private val PROVIDER_MAP = mapOf(\n            \"Apple\" to \"apple.com\",\n            \"Microsoft\" to \"microsoft.com\",\n            \"Yahoo\" to \"yahoo.com\",\n            \"Twitter\" to \"twitter.com\",\n        )\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/GoogleSignInFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.credentials.ClearCredentialStateRequest\nimport androidx.credentials.Credential\nimport androidx.credentials.CredentialManager\nimport androidx.credentials.CustomCredential\nimport androidx.credentials.GetCredentialRequest\nimport androidx.credentials.exceptions.ClearCredentialException\nimport androidx.credentials.exceptions.GetCredentialException\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.libraries.identity.googleid.GetGoogleIdOption\nimport com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption\nimport com.google.android.libraries.identity.googleid.GoogleIdTokenCredential\nimport com.google.android.libraries.identity.googleid.GoogleIdTokenCredential.Companion.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL\nimport com.google.android.material.snackbar.Snackbar\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.GoogleAuthProvider\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentGoogleBinding\nimport kotlinx.coroutines.launch\n\n/**\n * Demonstrate Firebase Authentication using a Google ID Token.\n */\nclass GoogleSignInFragment : BaseFragment() {\n\n    private lateinit var auth: FirebaseAuth\n\n    private var _binding: FragmentGoogleBinding? = null\n    private val binding: FragmentGoogleBinding\n        get() = _binding!!\n\n    private lateinit var credentialManager: CredentialManager\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        _binding = FragmentGoogleBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        setProgressBar(binding.progressBar)\n\n        // Initialize Credential Manager\n        credentialManager = CredentialManager.create(requireContext())\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        // Button listeners\n        binding.signInButton.setOnClickListener { signIn() }\n\n        binding.signOutButton.setOnClickListener { signOut() }\n\n        // Display Credential Manager Bottom Sheet if user isn't logged in\n        if (auth.currentUser == null) { showBottomSheet() }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        // Check if user is signed in (non-null) and update UI accordingly.\n        val currentUser = auth.currentUser\n        updateUI(currentUser)\n    }\n\n    private fun signIn() {\n        // Create the dialog configuration for the Credential Manager request\n        val signInWithGoogleOption = GetSignInWithGoogleOption\n            .Builder(serverClientId = requireContext().getString(R.string.default_web_client_id))\n            .build()\n\n        // Create the Credential Manager request using the configuration created above\n        val request = GetCredentialRequest.Builder()\n            .addCredentialOption(signInWithGoogleOption)\n            .build()\n\n        launchCredentialManager(request)\n    }\n\n    private fun showBottomSheet() {\n        // Create the bottom sheet configuration for the Credential Manager request\n        val googleIdOption = GetGoogleIdOption.Builder()\n            .setFilterByAuthorizedAccounts(true)\n            .setServerClientId(requireContext().getString(R.string.default_web_client_id))\n            .build()\n\n        // Create the Credential Manager request using the configuration created above\n        val request = GetCredentialRequest.Builder()\n            .addCredentialOption(googleIdOption)\n            .build()\n\n        launchCredentialManager(request)\n    }\n\n    private fun launchCredentialManager(request: GetCredentialRequest) {\n        viewLifecycleOwner.lifecycleScope.launch {\n            try {\n                // Launch Credential Manager UI\n                val result = credentialManager.getCredential(\n                    context = requireContext(),\n                    request = request\n                )\n\n                // Extract credential from the result returned by Credential Manager\n                createGoogleIdToken(result.credential)\n            } catch (e: GetCredentialException) {\n                Log.e(TAG, \"Couldn't retrieve user's credentials: ${e.localizedMessage}\")\n            }\n        }\n    }\n\n    private fun createGoogleIdToken(credential: Credential) {\n        // Check if credential is of type Google ID\n        if (credential is CustomCredential && credential.type == TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {\n            // Create Google ID Token\n            val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)\n\n            // Sign in to Firebase with using the token\n            firebaseAuthWithGoogle(googleIdTokenCredential.idToken)\n        } else {\n            Log.w(TAG, \"Credential is not of type Google ID!\")\n        }\n    }\n\n    private fun firebaseAuthWithGoogle(idToken: String) {\n        showProgressBar()\n        val credential = GoogleAuthProvider.getCredential(idToken, null)\n        auth.signInWithCredential(credential)\n            .addOnCompleteListener(requireActivity()) { task ->\n                if (task.isSuccessful) {\n                    // Sign in success, update UI with the signed-in user's information\n                    Log.d(TAG, \"signInWithCredential:success\")\n                    val user = auth.currentUser\n                    updateUI(user)\n                } else {\n                    // If sign in fails, display a message to the user.\n                    Log.w(TAG, \"signInWithCredential:failure\", task.exception)\n                    val view = binding.mainLayout\n                    Snackbar.make(view, \"Authentication Failed.\", Snackbar.LENGTH_SHORT).show()\n                    updateUI(null)\n                }\n\n                hideProgressBar()\n            }\n    }\n\n    private fun signOut() {\n        // Firebase sign out\n        auth.signOut()\n\n        // When a user signs out, clear the current user credential state from all credential providers.\n        // This will notify all providers that any stored credential session for the given app should be cleared.\n        viewLifecycleOwner.lifecycleScope.launch {\n            try {\n                val clearRequest = ClearCredentialStateRequest()\n                credentialManager.clearCredentialState(clearRequest)\n                updateUI(null)\n            } catch (e: ClearCredentialException) {\n                Log.e(TAG, \"Couldn't clear user credentials: ${e.localizedMessage}\")\n            }\n        }\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        hideProgressBar()\n        if (user != null) {\n            binding.status.text = getString(R.string.google_status_fmt, user.email)\n            binding.detail.text = getString(R.string.firebase_status_fmt, user.uid)\n\n            binding.signInButton.visibility = View.GONE\n            binding.signOutButton.visibility = View.VISIBLE\n        } else {\n            binding.status.setText(R.string.signed_out)\n            binding.detail.text = null\n\n            binding.signInButton.visibility = View.VISIBLE\n            binding.signOutButton.visibility = View.GONE\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"GoogleFragmentKt\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/MainActivity.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.navigation.findNavController\nimport com.google.firebase.quickstart.auth.R\n\nclass MainActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        findNavController(R.id.nav_host_fragment).setGraph(R.navigation.nav_graph_kotlin)\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/MultiFactorEnrollFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.navigation.fragment.findNavController\nimport com.google.firebase.FirebaseException\nimport com.google.firebase.auth.PhoneAuthCredential\nimport com.google.firebase.auth.PhoneAuthOptions\nimport com.google.firebase.auth.PhoneAuthProvider\nimport com.google.firebase.auth.PhoneMultiFactorGenerator\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.databinding.FragmentPhoneAuthBinding\nimport java.util.concurrent.TimeUnit\n\n/**\n * Activity that allows the user to enroll second factors.\n */\nclass MultiFactorEnrollFragment : BaseFragment() {\n\n    private var _binding: FragmentPhoneAuthBinding? = null\n    private val binding: FragmentPhoneAuthBinding\n        get() = _binding!!\n\n    private var lastCodeVerificationId: String? = null\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        _binding = FragmentPhoneAuthBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        binding.titleText.text = \"SMS as a Second Factor\"\n        binding.status.visibility = View.GONE\n        binding.detail.visibility = View.GONE\n        binding.buttonStartVerification.setOnClickListener { onClickVerifyPhoneNumber() }\n        binding.buttonVerifyPhone.setOnClickListener { onClickSignInWithPhoneNumber() }\n    }\n\n    private fun onClickVerifyPhoneNumber() {\n        val phoneNumber = binding.fieldPhoneNumber.text.toString()\n        val callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {\n            override fun onVerificationCompleted(credential: PhoneAuthCredential) {\n                // Instant-validation has been disabled (see requireSmsValidation below).\n                // Auto-retrieval has also been disabled (timeout is set to 0).\n                // This should never be triggered.\n                throw RuntimeException(\n                    \"onVerificationCompleted() triggered with instant-validation and auto-retrieval disabled.\",\n                )\n            }\n\n            override fun onCodeSent(\n                verificationId: String,\n                token: PhoneAuthProvider.ForceResendingToken,\n            ) {\n                Log.d(TAG, \"onCodeSent:$verificationId\")\n                Toast.makeText(context, \"SMS code has been sent\", Toast.LENGTH_SHORT)\n                    .show()\n                lastCodeVerificationId = verificationId\n            }\n\n            override fun onVerificationFailed(e: FirebaseException) {\n                Log.w(TAG, \"onVerificationFailed \", e)\n                Toast.makeText(context, \"Verification failed: ${e.message}\", Toast.LENGTH_SHORT)\n                    .show()\n            }\n        }\n        Firebase.auth\n            .currentUser!!\n            .multiFactor\n            .session\n            .addOnCompleteListener { task ->\n                if (task.isSuccessful) {\n                    val phoneAuthOptions = PhoneAuthOptions.newBuilder()\n                        .setActivity(requireActivity())\n                        .setPhoneNumber(phoneNumber) // A timeout of 0 disables SMS-auto-retrieval.\n                        .setTimeout(0L, TimeUnit.SECONDS)\n                        .setMultiFactorSession(task.result!!)\n                        .setCallbacks(callbacks) // Disable instant-validation.\n                        .requireSmsValidation(true)\n                        .build()\n                    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)\n                } else {\n                    Toast.makeText(\n                        context,\n                        \"Failed to get session: ${task.exception}\",\n                        Toast.LENGTH_SHORT,\n                    )\n                        .show()\n                }\n            }\n    }\n\n    private fun onClickSignInWithPhoneNumber() {\n        val smsCode = binding.fieldVerificationCode.text.toString()\n        if (TextUtils.isEmpty(smsCode)) {\n            return\n        }\n        val credential = PhoneAuthProvider.getCredential(lastCodeVerificationId!!, smsCode)\n        enrollWithPhoneAuthCredential(credential)\n    }\n\n    private fun enrollWithPhoneAuthCredential(credential: PhoneAuthCredential) {\n        Firebase.auth\n            .currentUser!!\n            .multiFactor\n            .enroll(PhoneMultiFactorGenerator.getAssertion(credential), null)\n            .addOnSuccessListener {\n                Toast.makeText(context, \"MFA enrollment was successful\", Toast.LENGTH_LONG)\n                    .show()\n                findNavController().popBackStack()\n            }\n            .addOnFailureListener { e ->\n                Log.d(TAG, \"MFA failure\", e)\n                Toast.makeText(\n                    context,\n                    \"MFA enrollment was unsuccessful. $e\",\n                    Toast.LENGTH_LONG,\n                )\n                    .show()\n            }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"MfaEnrollFragment\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/MultiFactorFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.app.AlertDialog\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.core.view.isGone\nimport androidx.navigation.fragment.findNavController\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.PhoneMultiFactorInfo\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentMultiFactorBinding\n\nclass MultiFactorFragment : BaseFragment() {\n    private lateinit var auth: FirebaseAuth\n\n    private var _binding: FragmentMultiFactorBinding? = null\n    private val binding: FragmentMultiFactorBinding\n        get() = _binding!!\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        _binding = FragmentMultiFactorBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        setProgressBar(binding.progressBar)\n\n        // Buttons\n        binding.emailSignInButton.setOnClickListener {\n            findNavController().navigate(R.id.action_mfa_to_emailpassword)\n        }\n        binding.signOutButton.setOnClickListener { signOut() }\n        binding.verifyEmailButton.setOnClickListener { sendEmailVerification() }\n        binding.enrollMfa.setOnClickListener {\n            findNavController().navigate(R.id.action_mfa_to_enroll)\n        }\n        binding.unenrollMfa.setOnClickListener {\n            findNavController().navigate(R.id.action_mfa_to_unenroll)\n        }\n        binding.reloadButton.setOnClickListener { reload() }\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        showDisclaimer()\n    }\n\n    public override fun onStart() {\n        super.onStart()\n        // Check if user is signed in (non-null) and update UI accordingly.\n        val currentUser = auth.currentUser\n        updateUI(currentUser)\n    }\n\n    private fun signOut() {\n        auth.signOut()\n        updateUI(null)\n    }\n\n    private fun sendEmailVerification() { // Disable button\n        binding.verifyEmailButton.isEnabled = false\n        // Send verification email\n        val user = auth.currentUser!!\n        user.sendEmailVerification()\n            .addOnCompleteListener(requireActivity()) { task ->\n                // Re-enable button\n                binding.verifyEmailButton.isEnabled = true\n                if (task.isSuccessful) {\n                    Toast.makeText(\n                        context,\n                        \"Verification email sent to \" + user.email,\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                } else {\n                    Log.e(TAG, \"sendEmailVerification\", task.exception)\n                    Toast.makeText(\n                        context,\n                        \"Failed to send verification email.\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                }\n            }\n    }\n\n    private fun reload() {\n        auth.currentUser!!.reload().addOnCompleteListener { task ->\n            if (task.isSuccessful) {\n                updateUI(auth.currentUser)\n                Toast.makeText(\n                    context,\n                    \"Reload successful!\",\n                    Toast.LENGTH_SHORT,\n                ).show()\n            } else {\n                Log.e(TAG, \"reload\", task.exception)\n                Toast.makeText(\n                    context,\n                    \"Failed to reload user.\",\n                    Toast.LENGTH_SHORT,\n                ).show()\n            }\n        }\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        hideProgressBar()\n        if (user != null) {\n            binding.status.text = getString(\n                R.string.emailpassword_status_fmt,\n                user.email,\n                user.isEmailVerified,\n            )\n            binding.detail.text = getString(R.string.firebase_status_fmt, user.uid)\n            val secondFactors = user.multiFactor.enrolledFactors\n            if (secondFactors.isEmpty()) {\n                binding.unenrollMfa.visibility = View.GONE\n            } else {\n                binding.unenrollMfa.visibility = View.VISIBLE\n                val sb = StringBuilder(\"Second Factors: \")\n                val delimiter = \", \"\n                for (x in secondFactors) {\n                    sb.append((x as PhoneMultiFactorInfo).phoneNumber + delimiter)\n                }\n                sb.setLength(sb.length - delimiter.length)\n                binding.mfaInfo.text = sb.toString()\n            }\n            binding.emailSignInButton.visibility = View.GONE\n            binding.signedInButtons.visibility = View.VISIBLE\n            val reloadVisibility = if (secondFactors.isEmpty()) View.VISIBLE else View.GONE\n            binding.reloadButton.visibility = reloadVisibility\n            binding.verifyEmailButton.isGone = user.isEmailVerified\n            binding.enrollMfa.isGone = !user.isEmailVerified\n        } else {\n            binding.status.setText(R.string.multi_factor_signed_out)\n            binding.detail.text = null\n            binding.mfaInfo.text = null\n            binding.emailSignInButton.visibility = View.VISIBLE\n            binding.signedInButtons.visibility = View.GONE\n        }\n    }\n\n    private fun showDisclaimer() {\n        AlertDialog.Builder(requireContext())\n            .setTitle(\"Warning\")\n            .setMessage(\n                \"Multi-factor authentication with SMS is currently only available for \" +\n                        \"Google Cloud Identity Platform projects. For more information see: \" +\n                        \"https://cloud.google.com/identity-platform/docs/android/mfa\",\n            )\n            .setPositiveButton(\"OK\", null)\n            .show()\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"MultiFactor\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/MultiFactorSignInFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.navigation.fragment.findNavController\nimport com.google.firebase.FirebaseException\nimport com.google.firebase.auth.MultiFactorResolver\nimport com.google.firebase.auth.PhoneAuthCredential\nimport com.google.firebase.auth.PhoneAuthOptions\nimport com.google.firebase.auth.PhoneAuthProvider\nimport com.google.firebase.auth.PhoneMultiFactorGenerator\nimport com.google.firebase.auth.PhoneMultiFactorInfo\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentMultiFactorSignInBinding\nimport java.util.concurrent.TimeUnit\n\n/**\n * Fragment that handles MFA sign-in\n */\nclass MultiFactorSignInFragment : BaseFragment() {\n\n    private var _binding: FragmentMultiFactorSignInBinding? = null\n    private val binding: FragmentMultiFactorSignInBinding\n        get() = _binding!!\n\n    private lateinit var multiFactorResolver: MultiFactorResolver\n    private var lastPhoneAuthCredential: PhoneAuthCredential? = null\n    private var lastVerificationId: String? = null\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        _binding = FragmentMultiFactorSignInBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        savedInstanceState?.let { onViewStateRestored(it) }\n\n        // Users are currently limited to having 5 second factors\n        val phoneFactorButtonList = listOf(\n            binding.phoneFactor1,\n            binding.phoneFactor2,\n            binding.phoneFactor3,\n            binding.phoneFactor4,\n            binding.phoneFactor5,\n        )\n        for (button in phoneFactorButtonList) {\n            button.visibility = View.GONE\n        }\n\n        binding.finishMfaSignIn.setOnClickListener { onClickFinishSignIn() }\n        multiFactorResolver = getResolverFromArguments(requireArguments())\n\n        val multiFactorInfoList = multiFactorResolver.hints\n        for (i in multiFactorInfoList.indices) {\n            val phoneMultiFactorInfo = multiFactorInfoList[i] as PhoneMultiFactorInfo\n            val button = phoneFactorButtonList[i]\n            button.visibility = View.VISIBLE\n            button.text = phoneMultiFactorInfo.phoneNumber\n            button.isClickable = true\n            button.setOnClickListener(generateFactorOnClickListener(phoneMultiFactorInfo))\n        }\n    }\n\n    override fun onSaveInstanceState(bundle: Bundle) {\n        super.onSaveInstanceState(bundle)\n        bundle.putString(KEY_VERIFICATION_ID, lastVerificationId)\n    }\n\n    override fun onViewStateRestored(savedInstanceState: Bundle?) {\n        super.onViewStateRestored(savedInstanceState)\n        savedInstanceState?.let { savedState ->\n            lastVerificationId = savedState.getString(KEY_VERIFICATION_ID)\n        }\n    }\n\n    private fun generateFactorOnClickListener(phoneMultiFactorInfo: PhoneMultiFactorInfo): View.OnClickListener {\n        return View.OnClickListener {\n            PhoneAuthProvider.verifyPhoneNumber(\n                PhoneAuthOptions.newBuilder()\n                    .setActivity(requireActivity())\n                    .setMultiFactorSession(multiFactorResolver.session)\n                    .setMultiFactorHint(phoneMultiFactorInfo)\n                    .setCallbacks(generateCallbacks()) // A timeout of 0 disables SMS-auto-retrieval.\n                    .setTimeout(0L, TimeUnit.SECONDS)\n                    .build(),\n            )\n        }\n    }\n\n    private fun generateCallbacks(): PhoneAuthProvider.OnVerificationStateChangedCallbacks {\n        return object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {\n            override fun onVerificationCompleted(phoneAuthCredential: PhoneAuthCredential) {\n                lastPhoneAuthCredential = phoneAuthCredential\n                binding.finishMfaSignIn.performClick()\n                Toast.makeText(context, \"Verification complete!\", Toast.LENGTH_SHORT)\n                    .show()\n            }\n\n            override fun onCodeSent(verificationId: String, token: PhoneAuthProvider.ForceResendingToken) {\n                lastVerificationId = verificationId\n                binding.finishMfaSignIn.isClickable = true\n            }\n\n            override fun onVerificationFailed(e: FirebaseException) {\n                Toast.makeText(context, \"Error: ${e.message}\", Toast.LENGTH_SHORT)\n                    .show()\n            }\n        }\n    }\n\n    private fun getResolverFromArguments(arguments: Bundle): MultiFactorResolver {\n        return arguments.getParcelable(EXTRA_MFA_RESOLVER)!!\n    }\n\n    private fun onClickFinishSignIn() {\n        if (lastPhoneAuthCredential == null) {\n            if (TextUtils.isEmpty(binding.smsCode.text.toString())) {\n                Toast.makeText(context, \"You need to enter an SMS code.\", Toast.LENGTH_SHORT)\n                    .show()\n                return\n            }\n            lastPhoneAuthCredential = PhoneAuthProvider.getCredential(\n                lastVerificationId!!,\n                binding.smsCode.text.toString(),\n            )\n        }\n        multiFactorResolver\n            .resolveSignIn(PhoneMultiFactorGenerator.getAssertion(lastPhoneAuthCredential!!))\n            .addOnSuccessListener {\n                findNavController().navigate(R.id.action_mfasignin_to_mfa)\n            }\n            .addOnFailureListener { e ->\n                Toast.makeText(context, \"Error: \" + e.message, Toast.LENGTH_SHORT)\n                    .show()\n            }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val KEY_VERIFICATION_ID = \"key_verification_id\"\n        const val EXTRA_MFA_RESOLVER = \"EXTRA_MFA_RESOLVER\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/MultiFactorUnenrollFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.navigation.fragment.findNavController\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.PhoneMultiFactorInfo\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.databinding.FragmentMultiFactorSignInBinding\n\nclass MultiFactorUnenrollFragment : BaseFragment() {\n\n    private var _binding: FragmentMultiFactorSignInBinding? = null\n    private val binding: FragmentMultiFactorSignInBinding\n        get() = _binding!!\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        _binding = FragmentMultiFactorSignInBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        binding.smsCode.visibility = View.GONE\n        binding.finishMfaSignIn.visibility = View.GONE\n\n        // Users are currently limited to having 5 second factors\n        val phoneFactorButtonList = listOf(\n            binding.phoneFactor1,\n            binding.phoneFactor2,\n            binding.phoneFactor3,\n            binding.phoneFactor4,\n            binding.phoneFactor5,\n        )\n        for (button in phoneFactorButtonList) {\n            button.visibility = View.GONE\n        }\n\n        val multiFactorInfoList = FirebaseAuth.getInstance().currentUser!!.multiFactor.enrolledFactors\n        for (i in multiFactorInfoList.indices) {\n            val phoneMultiFactorInfo = multiFactorInfoList[i] as PhoneMultiFactorInfo\n            val button = phoneFactorButtonList[i]\n            button.visibility = View.VISIBLE\n            button.text = phoneMultiFactorInfo.phoneNumber\n            button.isClickable = true\n            button.setOnClickListener {\n                Firebase.auth\n                    .currentUser!!\n                    .multiFactor\n                    .unenroll(phoneMultiFactorInfo)\n                    .addOnCompleteListener { task ->\n                        if (task.isSuccessful) {\n                            Toast.makeText(\n                                context,\n                                \"Successfully unenrolled!\",\n                                Toast.LENGTH_SHORT,\n                            ).show()\n                            findNavController().popBackStack()\n                        } else {\n                            Toast.makeText(\n                                context,\n                                \"Unable to unenroll second factor. ${task.exception}\",\n                                Toast.LENGTH_SHORT,\n                            )\n                                .show()\n                        }\n                    }\n            }\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/PasswordlessActivity.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.View\nimport com.google.android.material.snackbar.Snackbar\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseAuthActionCodeException\nimport com.google.firebase.auth.FirebaseAuthInvalidCredentialsException\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.actionCodeSettings\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.ActivityPasswordlessBinding\n\n/**\n * Demonstrate Firebase Authentication without a password, using a link sent to an\n * email address.\n */\nclass PasswordlessActivity : BaseActivity(), View.OnClickListener {\n\n    private var pendingEmail: String = \"\"\n    private var emailLink: String = \"\"\n    private lateinit var auth: FirebaseAuth\n    private lateinit var binding: ActivityPasswordlessBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityPasswordlessBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setProgressBar(binding.progressBar)\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        binding.passwordlessSendEmailButton.setOnClickListener(this)\n        binding.passwordlessSignInButton.setOnClickListener(this)\n        binding.signOutButton.setOnClickListener(this)\n\n        // Restore the \"pending\" email address\n        if (savedInstanceState != null) {\n            pendingEmail = savedInstanceState.getString(KEY_PENDING_EMAIL, null)\n            binding.fieldEmail.setText(pendingEmail)\n        }\n\n        // Check if the Intent that started the Activity contains an email sign-in link.\n        checkIntent(intent)\n    }\n\n    override fun onStart() {\n        super.onStart()\n        updateUI(auth.currentUser)\n    }\n\n    override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n        checkIntent(intent)\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        outState.putString(KEY_PENDING_EMAIL, pendingEmail)\n    }\n\n    /**\n     * Check to see if the Intent has an email link, and if so set up the UI accordingly.\n     * This can be called from either onCreate or onNewIntent, depending on how the Activity\n     * was launched.\n     */\n    private fun checkIntent(intent: Intent?) {\n        if (intentHasEmailLink(intent)) {\n            emailLink = intent!!.data!!.toString()\n\n            binding.status.setText(R.string.status_link_found)\n            binding.passwordlessSendEmailButton.isEnabled = false\n            binding.passwordlessSignInButton.isEnabled = true\n        } else {\n            binding.status.setText(R.string.status_email_not_sent)\n            binding.passwordlessSendEmailButton.isEnabled = true\n            binding.passwordlessSignInButton.isEnabled = false\n        }\n    }\n\n    /**\n     * Determine if the given Intent contains an email sign-in link.\n     */\n    private fun intentHasEmailLink(intent: Intent?): Boolean {\n        if (intent != null && intent.data != null) {\n            val intentData = intent.data.toString()\n            if (auth.isSignInWithEmailLink(intentData)) {\n                return true\n            }\n        }\n\n        return false\n    }\n\n    /**\n     * Send an email sign-in link to the specified email.\n     */\n    private fun sendSignInLink(email: String) {\n        val settings = actionCodeSettings {\n            setAndroidPackageName(\n                packageName,\n                false,\n                null, // minimum app version\n            ) // install if not available?\n            handleCodeInApp = true\n            url = \"https://kotlin.auth.example.com/emailSignInLink\"\n        }\n\n        hideKeyboard(binding.fieldEmail)\n        showProgressBar()\n\n        auth.sendSignInLinkToEmail(email, settings)\n            .addOnCompleteListener { task ->\n                hideProgressBar()\n\n                if (task.isSuccessful) {\n                    Log.d(TAG, \"Link sent\")\n                    showSnackbar(\"Sign-in link sent!\")\n\n                    pendingEmail = email\n                    binding.status.setText(R.string.status_email_sent)\n                } else {\n                    val e = task.exception\n                    Log.w(TAG, \"Could not send link\", e)\n                    showSnackbar(\"Failed to send link.\")\n\n                    if (e is FirebaseAuthInvalidCredentialsException) {\n                        binding.fieldEmail.error = \"Invalid email address.\"\n                    }\n                }\n            }\n    }\n\n    /**\n     * Sign in using an email address and a link, the link is passed to the Activity\n     * from the dynamic link contained in the email.\n     */\n    private fun signInWithEmailLink(email: String, link: String?) {\n        Log.d(TAG, \"signInWithLink:\" + link!!)\n\n        hideKeyboard(binding.fieldEmail)\n        showProgressBar()\n\n        auth.signInWithEmailLink(email, link)\n            .addOnCompleteListener { task ->\n                hideProgressBar()\n                if (task.isSuccessful) {\n                    Log.d(TAG, \"signInWithEmailLink:success\")\n\n                    binding.fieldEmail.text = null\n                    updateUI(task.result?.user)\n                } else {\n                    Log.w(TAG, \"signInWithEmailLink:failure\", task.exception)\n                    updateUI(null)\n\n                    if (task.exception is FirebaseAuthActionCodeException) {\n                        showSnackbar(\"Invalid or expired sign-in link.\")\n                    }\n                }\n            }\n    }\n\n    private fun onSendLinkClicked() {\n        val email = binding.fieldEmail.text.toString()\n        if (TextUtils.isEmpty(email)) {\n            binding.fieldEmail.error = \"Email must not be empty.\"\n            return\n        }\n\n        sendSignInLink(email)\n    }\n\n    private fun onSignInClicked() {\n        val email = binding.fieldEmail.text.toString()\n        if (TextUtils.isEmpty(email)) {\n            binding.fieldEmail.error = \"Email must not be empty.\"\n            return\n        }\n\n        signInWithEmailLink(email, emailLink)\n    }\n\n    private fun onSignOutClicked() {\n        auth.signOut()\n\n        updateUI(null)\n        binding.status.setText(R.string.status_email_not_sent)\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        if (user != null) {\n            binding.status.text = getString(\n                R.string.passwordless_status_fmt,\n                user.email,\n                user.isEmailVerified,\n            )\n            binding.passwordlessButtons.visibility = View.GONE\n            binding.signOutButton.visibility = View.VISIBLE\n        } else {\n            binding.passwordlessButtons.visibility = View.VISIBLE\n            binding.signOutButton.visibility = View.GONE\n        }\n    }\n\n    private fun showSnackbar(message: String) {\n        Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show()\n    }\n\n    override fun onClick(view: View) {\n        when (view.id) {\n            R.id.passwordlessSendEmailButton -> onSendLinkClicked()\n            R.id.passwordlessSignInButton -> onSignInClicked()\n            R.id.signOutButton -> onSignOutClicked()\n        }\n    }\n\n    companion object {\n        private const val TAG = \"PasswordlessSignIn\"\n        private const val KEY_PENDING_EMAIL = \"key_pending_email\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/PhoneAuthFragment.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport com.google.android.material.snackbar.Snackbar\nimport com.google.firebase.FirebaseException\nimport com.google.firebase.FirebaseTooManyRequestsException\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseAuthInvalidCredentialsException\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.PhoneAuthCredential\nimport com.google.firebase.auth.PhoneAuthOptions\nimport com.google.firebase.auth.PhoneAuthProvider\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.auth.R\nimport com.google.firebase.quickstart.auth.databinding.FragmentPhoneAuthBinding\nimport java.util.concurrent.TimeUnit\n\nclass PhoneAuthFragment : Fragment() {\n\n    private lateinit var auth: FirebaseAuth\n\n    private var _binding: FragmentPhoneAuthBinding? = null\n    private val binding: FragmentPhoneAuthBinding\n        get() = _binding!!\n\n    private var verificationInProgress = false\n    private var storedVerificationId: String? = \"\"\n    private lateinit var resendToken: PhoneAuthProvider.ForceResendingToken\n    private lateinit var callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        _binding = FragmentPhoneAuthBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // Restore instance state\n        savedInstanceState?.let { onViewStateRestored(it) }\n\n        // Assign click listeners\n        binding.buttonStartVerification.setOnClickListener {\n            if (!validatePhoneNumber()) {\n                return@setOnClickListener\n            }\n            startPhoneNumberVerification(binding.fieldPhoneNumber.text.toString())\n        }\n        binding.buttonVerifyPhone.setOnClickListener {\n            val code = binding.fieldVerificationCode.text.toString()\n            if (TextUtils.isEmpty(code)) {\n                binding.fieldVerificationCode.error = \"Cannot be empty.\"\n                return@setOnClickListener\n            }\n            verifyPhoneNumberWithCode(storedVerificationId, code)\n        }\n        binding.buttonResend.setOnClickListener {\n            if (!validatePhoneNumber()) {\n                return@setOnClickListener\n            }\n            resendVerificationCode(binding.fieldPhoneNumber.text.toString(), resendToken)\n        }\n        binding.signOutButton.setOnClickListener { signOut() }\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        // Initialize phone auth callbacks\n        callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {\n\n            override fun onVerificationCompleted(credential: PhoneAuthCredential) {\n                // This callback will be invoked in two situations:\n                // 1 - Instant verification. In some cases the phone number can be instantly\n                //     verified without needing to send or enter a verification code.\n                // 2 - Auto-retrieval. On some devices Google Play services can automatically\n                //     detect the incoming verification SMS and perform verification without\n                //     user action.\n                Log.d(TAG, \"onVerificationCompleted:$credential\")\n                verificationInProgress = false\n\n                // Update the UI and attempt sign in with the phone credential\n                updateUI(STATE_VERIFY_SUCCESS, credential)\n                signInWithPhoneAuthCredential(credential)\n            }\n\n            override fun onVerificationFailed(e: FirebaseException) {\n                // This callback is invoked in an invalid request for verification is made,\n                // for instance if the the phone number format is not valid.\n                Log.w(TAG, \"onVerificationFailed\", e)\n                verificationInProgress = false\n\n                if (e is FirebaseAuthInvalidCredentialsException) {\n                    // Invalid request\n                    binding.fieldPhoneNumber.error = \"Invalid phone number.\"\n                } else if (e is FirebaseTooManyRequestsException) {\n                    // The SMS quota for the project has been exceeded\n                    Snackbar.make(\n                        view,\n                        \"Quota exceeded.\",\n                        Snackbar.LENGTH_SHORT,\n                    ).show()\n                }\n\n                // Show a message and update the UI\n                updateUI(STATE_VERIFY_FAILED)\n            }\n\n            override fun onCodeSent(\n                verificationId: String,\n                token: PhoneAuthProvider.ForceResendingToken,\n            ) {\n                // The SMS verification code has been sent to the provided phone number, we\n                // now need to ask the user to enter the code and then construct a credential\n                // by combining the code with a verification ID.\n                Log.d(TAG, \"onCodeSent:$verificationId\")\n\n                // Save verification ID and resending token so we can use them later\n                storedVerificationId = verificationId\n                resendToken = token\n\n                // Update UI\n                updateUI(STATE_CODE_SENT)\n            }\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        // Check if user is signed in (non-null) and update UI accordingly.\n        val currentUser = auth.currentUser\n        updateUI(currentUser)\n\n        if (verificationInProgress && validatePhoneNumber()) {\n            startPhoneNumberVerification(binding.fieldPhoneNumber.text.toString())\n        }\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        outState.putBoolean(KEY_VERIFY_IN_PROGRESS, verificationInProgress)\n    }\n\n    override fun onViewStateRestored(savedInstanceState: Bundle?) {\n        super.onViewStateRestored(savedInstanceState)\n        savedInstanceState?.let { savedState ->\n            verificationInProgress = savedState.getBoolean(KEY_VERIFY_IN_PROGRESS)\n        }\n    }\n\n    private fun startPhoneNumberVerification(phoneNumber: String) {\n        val options = PhoneAuthOptions.newBuilder(auth)\n            .setPhoneNumber(phoneNumber) // Phone number to verify\n            .setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit\n            .setActivity(requireActivity()) // Activity (for callback binding)\n            .setCallbacks(callbacks) // OnVerificationStateChangedCallbacks\n            .build()\n        PhoneAuthProvider.verifyPhoneNumber(options)\n\n        verificationInProgress = true\n    }\n\n    private fun verifyPhoneNumberWithCode(verificationId: String?, code: String) {\n        val credential = PhoneAuthProvider.getCredential(verificationId!!, code)\n        signInWithPhoneAuthCredential(credential)\n    }\n\n    private fun resendVerificationCode(\n        phoneNumber: String,\n        token: PhoneAuthProvider.ForceResendingToken?,\n    ) {\n        val optionsBuilder = PhoneAuthOptions.newBuilder(auth)\n            .setPhoneNumber(phoneNumber) // Phone number to verify\n            .setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit\n            .setActivity(requireActivity()) // Activity (for callback binding)\n            .setCallbacks(callbacks) // OnVerificationStateChangedCallbacks\n        if (token != null) {\n            optionsBuilder.setForceResendingToken(token) // callback's ForceResendingToken\n        }\n        PhoneAuthProvider.verifyPhoneNumber(optionsBuilder.build())\n    }\n\n    private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {\n        auth.signInWithCredential(credential)\n            .addOnCompleteListener(requireActivity()) { task ->\n                if (task.isSuccessful) {\n                    // Sign in success, update UI with the signed-in user's information\n                    Log.d(TAG, \"signInWithCredential:success\")\n\n                    val user = task.result?.user\n                    updateUI(STATE_SIGNIN_SUCCESS, user)\n                } else {\n                    // Sign in failed, display a message and update the UI\n                    Log.w(TAG, \"signInWithCredential:failure\", task.exception)\n                    if (task.exception is FirebaseAuthInvalidCredentialsException) {\n                        // The verification code entered was invalid\n                        binding.fieldVerificationCode.error = \"Invalid code.\"\n                    }\n                    // Update UI\n                    updateUI(STATE_SIGNIN_FAILED)\n                }\n            }\n    }\n\n    private fun signOut() {\n        auth.signOut()\n        updateUI(STATE_INITIALIZED)\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        if (user != null) {\n            updateUI(STATE_SIGNIN_SUCCESS, user)\n        } else {\n            updateUI(STATE_INITIALIZED)\n        }\n    }\n\n    private fun updateUI(uiState: Int, cred: PhoneAuthCredential) {\n        updateUI(uiState, null, cred)\n    }\n\n    private fun updateUI(\n        uiState: Int,\n        user: FirebaseUser? = auth.currentUser,\n        cred: PhoneAuthCredential? = null,\n    ) {\n        when (uiState) {\n            STATE_INITIALIZED -> {\n                // Initialized state, show only the phone number field and start button\n                enableViews(binding.buttonStartVerification, binding.fieldPhoneNumber)\n                disableViews(binding.buttonVerifyPhone, binding.buttonResend, binding.fieldVerificationCode)\n                binding.detail.text = null\n            }\n            STATE_CODE_SENT -> {\n                // Code sent state, show the verification field, the\n                enableViews(\n                    binding.buttonVerifyPhone,\n                    binding.buttonResend,\n                    binding.fieldPhoneNumber,\n                    binding.fieldVerificationCode,\n                )\n                disableViews(binding.buttonStartVerification)\n                binding.detail.setText(R.string.status_code_sent)\n            }\n            STATE_VERIFY_FAILED -> {\n                // Verification has failed, show all options\n                enableViews(\n                    binding.buttonStartVerification,\n                    binding.buttonVerifyPhone,\n                    binding.buttonResend,\n                    binding.fieldPhoneNumber,\n                    binding.fieldVerificationCode,\n                )\n                binding.detail.setText(R.string.status_verification_failed)\n            }\n            STATE_VERIFY_SUCCESS -> {\n                // Verification has succeeded, proceed to firebase sign in\n                disableViews(\n                    binding.buttonStartVerification,\n                    binding.buttonVerifyPhone,\n                    binding.buttonResend,\n                    binding.fieldPhoneNumber,\n                    binding.fieldVerificationCode,\n                )\n                binding.detail.setText(R.string.status_verification_succeeded)\n\n                // Set the verification text based on the credential\n                if (cred != null) {\n                    if (cred.smsCode != null) {\n                        binding.fieldVerificationCode.setText(cred.smsCode)\n                    } else {\n                        binding.fieldVerificationCode.setText(R.string.instant_validation)\n                    }\n                }\n            }\n            STATE_SIGNIN_FAILED ->\n                // No-op, handled by sign-in check\n                binding.detail.setText(R.string.status_sign_in_failed)\n            STATE_SIGNIN_SUCCESS -> {\n            }\n        } // Np-op, handled by sign-in check\n\n        if (user == null) {\n            // Signed out\n            binding.phoneAuthFields.visibility = View.VISIBLE\n            binding.signOutButton.visibility = View.GONE\n\n            binding.status.setText(R.string.signed_out)\n        } else {\n            // Signed in\n            binding.phoneAuthFields.visibility = View.GONE\n            binding.signOutButton.visibility = View.VISIBLE\n\n            enableViews(binding.fieldPhoneNumber, binding.fieldVerificationCode)\n            binding.fieldPhoneNumber.text = null\n            binding.fieldVerificationCode.text = null\n\n            binding.status.setText(R.string.signed_in)\n            binding.detail.text = getString(R.string.firebase_status_fmt, user.uid)\n        }\n    }\n\n    private fun validatePhoneNumber(): Boolean {\n        val phoneNumber = binding.fieldPhoneNumber.text.toString()\n        if (TextUtils.isEmpty(phoneNumber)) {\n            binding.fieldPhoneNumber.error = \"Invalid phone number.\"\n            return false\n        }\n\n        return true\n    }\n\n    private fun enableViews(vararg views: View) {\n        for (v in views) {\n            v.isEnabled = true\n        }\n    }\n\n    private fun disableViews(vararg views: View) {\n        for (v in views) {\n            v.isEnabled = false\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"PhoneAuthFragment\"\n        private const val KEY_VERIFY_IN_PROGRESS = \"key_verify_in_progress\"\n        private const val STATE_INITIALIZED = 1\n        private const val STATE_VERIFY_FAILED = 3\n        private const val STATE_VERIFY_SUCCESS = 4\n        private const val STATE_CODE_SENT = 2\n        private const val STATE_SIGNIN_FAILED = 5\n        private const val STATE_SIGNIN_SUCCESS = 6\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/TokenBroadcastReceiver.kt",
    "content": "package com.google.firebase.quickstart.auth.kotlin\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.util.Log\n\n/**\n * Receiver to capture tokens broadcast via ADB and insert them into the\n * running application to facilitate easy testing of custom authentication.\n */\nabstract class TokenBroadcastReceiver : BroadcastReceiver() {\n\n    override fun onReceive(context: Context, intent: Intent) {\n        Log.d(TAG, \"onReceive:$intent\")\n\n        if (ACTION_TOKEN == intent.action) {\n            val token = intent.extras?.getString(EXTRA_KEY_TOKEN)\n            onNewToken(token)\n        }\n    }\n\n    abstract fun onNewToken(token: String?)\n\n    companion object {\n        const val ACTION_TOKEN = \"com.google.example.ACTION_TOKEN\"\n        val filter: IntentFilter\n            get() = IntentFilter(ACTION_TOKEN)\n        private const val TAG = \"TokenBroadcastReceiver\"\n        const val EXTRA_KEY_TOKEN = \"key_token\"\n    }\n}\n"
  },
  {
    "path": "auth/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n    tools:viewBindingIgnore=\"true\">\n\n    <fragment\n        android:id=\"@+id/nav_host_fragment\"\n        android:name=\"androidx.navigation.fragment.NavHostFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:defaultNavHost=\"true\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"/>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "auth/app/src/main/res/layout/activity_passwordless.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        android:indeterminate=\"true\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"invisible\"\n        style=\"?android:attr/progressBarStyleHorizontal\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/passwordless_title_text\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n\n    </LinearLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:paddingTop=\"16dp\"\n        android:background=\"@color/grey_300\"\n        android:gravity=\"center_vertical\">\n\n        <Button\n            android:id=\"@+id/passwordlessSendEmailButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:text=\"@string/send_link\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"@+id/fieldEmail\"\n            app:layout_constraintTop_toBottomOf=\"@+id/fieldEmail\" />\n\n        <Button\n            android:id=\"@+id/passwordlessSignInButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:text=\"@string/sign_in\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/passwordlessSendEmailButton\"\n            app:layout_constraintEnd_toEndOf=\"@+id/fieldEmail\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toTopOf=\"@+id/passwordlessSendEmailButton\"\n            tools:enabled=\"false\" />\n\n        <Button\n            android:id=\"@+id/signOutButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginEnd=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:text=\"@string/sign_out\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            android:visibility=\"gone\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/passwordlessSendEmailButton\" />\n\n        <EditText\n            android:id=\"@+id/fieldEmail\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginEnd=\"16dp\"\n            android:autofillHints=\"emailAddress\"\n            android:hint=\"@string/hint_email\"\n            android:inputType=\"textEmailAddress\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"/>\n\n        <androidx.constraintlayout.widget.Group\n            android:id=\"@+id/passwordlessButtons\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:constraint_referenced_ids=\"passwordlessSignInButton, passwordlessSendEmailButton, fieldEmail\"/>\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            app:layout_constraintGuide_percent=\"0.5\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_anonymous_auth.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:indeterminate=\"true\"\n        android:visibility=\"invisible\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"4\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/label_anonymous_sign_in\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/anonymousStatusId\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/anonymousStatusEmail\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            tools:text=\"Email: none\" />\n\n    </LinearLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/grey_300\"\n        android:gravity=\"center_vertical\"\n        android:paddingTop=\"16dp\">\n\n        <Button\n            android:id=\"@+id/buttonAnonymousSignIn\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:text=\"@string/sign_in\"\n            app:layout_constraintEnd_toStartOf=\"@+id/buttonAnonymousSignOut\"\n            app:layout_constraintHorizontal_chainStyle=\"packed\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/textView\" />\n\n        <Button\n            android:id=\"@+id/buttonAnonymousSignOut\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:enabled=\"false\"\n            android:text=\"@string/sign_out\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/buttonAnonymousSignIn\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintHorizontal_bias=\"0.5\"\n            app:layout_constraintStart_toEndOf=\"@+id/buttonAnonymousSignIn\"\n            app:layout_constraintTop_toTopOf=\"@+id/buttonAnonymousSignIn\" />\n\n        <TextView\n            android:id=\"@+id/textView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:text=\"@string/label_anonymous_sign_in\"\n            android:textSize=\"24sp\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintHorizontal_bias=\"0.0\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <EditText\n            android:id=\"@+id/fieldEmail\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:hint=\"@string/hint_email\"\n            android:inputType=\"textEmailAddress\"\n            app:layout_constraintEnd_toStartOf=\"@+id/fieldPassword\"\n            app:layout_constraintHorizontal_bias=\"0.5\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/textView3\" />\n\n        <EditText\n            android:id=\"@+id/fieldPassword\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:ems=\"10\"\n            android:hint=\"@string/hint_password\"\n            android:inputType=\"textPassword\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/fieldEmail\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintHorizontal_bias=\"0.5\"\n            app:layout_constraintStart_toEndOf=\"@+id/fieldEmail\"\n            app:layout_constraintTop_toTopOf=\"@+id/fieldEmail\"\n            app:layout_constraintVertical_bias=\"0.0\" />\n\n        <Button\n            android:id=\"@+id/buttonLinkAccount\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:enabled=\"false\"\n            android:text=\"@string/btn_link_account\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintHorizontal_bias=\"0.0\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/fieldEmail\"\n            app:layout_constraintVertical_bias=\"0.0\" />\n\n        <TextView\n            android:id=\"@+id/textView3\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginTop=\"16dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:text=\"@string/label_account_linking\"\n            android:textSize=\"24sp\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintHorizontal_bias=\"0.0\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/buttonAnonymousSignIn\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</LinearLayout>\n\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_chooser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ListView\n        android:id=\"@+id/listView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n\n</FrameLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_custom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\">\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:contentDescription=\"@string/desc_firebase_lockup\"\n        android:layout_gravity=\"center_horizontal\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n    <TextView\n        style=\"@style/TextAppearance.AppCompat.Medium\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:text=\"@string/custom_token\" />\n\n    <TextView\n        android:id=\"@+id/textTokenStatus\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"end\"\n        android:maxLines=\"1\"\n        android:singleLine=\"true\"\n        android:text=\"@string/token_null\" />\n\n    <TextView\n        style=\"@style/TextAppearance.AppCompat.Medium\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"32dp\"\n        android:text=\"@string/firebase_user_management\" />\n\n    <TextView\n        android:id=\"@+id/textSignInStatus\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/signed_out\" />\n\n    <Button\n        android:id=\"@+id/buttonSignIn\"\n        android:layout_width=\"@dimen/field_width_standard\"\n        android:layout_height=\"wrap_content\"\n        android:enabled=\"false\"\n        android:text=\"@string/sign_in\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_emailpassword.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        android:indeterminate=\"true\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"invisible\"\n        style=\"?android:attr/progressBarStyleHorizontal\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/emailpassword_title_text\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n    </LinearLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:paddingTop=\"16dp\"\n        android:background=\"@color/grey_300\"\n        android:gravity=\"center_vertical\">\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            app:layout_constraintGuide_percent=\"0.5\" />\n\n        <EditText\n            android:id=\"@+id/fieldEmail\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:hint=\"@string/hint_email\"\n            android:inputType=\"textEmailAddress\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <EditText\n            android:id=\"@+id/fieldPassword\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:hint=\"@string/hint_password\"\n            android:inputType=\"textPassword\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <androidx.constraintlayout.widget.Group\n            android:id=\"@+id/emailPasswordFields\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"visible\"\n            app:constraint_referenced_ids=\"fieldEmail, fieldPassword\"/>\n\n        <Button\n            android:id=\"@+id/emailSignInButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginEnd=\"@dimen/button_horizontal_margin\"\n            android:layout_marginRight=\"@dimen/button_horizontal_margin\"\n            android:text=\"@string/sign_in\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/fieldEmail\" />\n\n        <Button\n            android:id=\"@+id/emailCreateAccountButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"@dimen/button_horizontal_margin\"\n            android:layout_marginLeft=\"@dimen/button_horizontal_margin\"\n            android:layout_marginEnd=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:text=\"@string/create_account\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toBottomOf=\"@+id/fieldPassword\" />\n\n        <androidx.constraintlayout.widget.Group\n            android:id=\"@+id/emailPasswordButtons\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"visible\"\n            app:constraint_referenced_ids=\"emailCreateAccountButton, emailSignInButton\" />\n\n        <Button\n            android:id=\"@+id/signOutButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/sign_out\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/verifyEmailButton\"\n            app:layout_constraintEnd_toStartOf=\"@+id/verifyEmailButton\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@+id/verifyEmailButton\" />\n\n        <Button\n            android:id=\"@+id/verifyEmailButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/verify_email\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toBottomOf=\"@+id/emailSignInButton\" />\n\n        <Button\n            android:id=\"@+id/reloadButton\"\n            style=\"@style/Widget.AppCompat.Button.Colored\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/reload\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/verifyEmailButton\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toEndOf=\"@+id/verifyEmailButton\"\n            app:layout_constraintTop_toTopOf=\"@+id/verifyEmailButton\" />\n\n        <androidx.constraintlayout.widget.Group\n            android:id=\"@+id/signedInButtons\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"gone\"\n            app:constraint_referenced_ids=\"signOutButton, verifyEmailButton, reloadButton\" />\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_facebook.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        android:indeterminate=\"true\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"invisible\"\n        style=\"?android:attr/progressBarStyleHorizontal\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/facebook_title_text\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n\n    </LinearLayout>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@color/grey_300\">\n\n        <com.facebook.login.widget.LoginButton\n            android:id=\"@+id/buttonFacebookLogin\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"/>\n\n        <Button\n            android:id=\"@+id/buttonFacebookSignout\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"@string/sign_out\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            android:visibility=\"gone\"/>\n\n    </FrameLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_firebase_ui.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/firebaseui_title_text\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n\n    </LinearLayout>\n\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@color/grey_300\"\n        android:gravity=\"center_vertical\">\n\n        <Button\n            android:id=\"@+id/signInButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"@string/sign_in\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            android:visibility=\"visible\" />\n\n        <Button\n            android:id=\"@+id/signOutButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"@string/sign_out\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            android:visibility=\"gone\" />\n\n    </FrameLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_generic_idp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:gravity=\"center\"\n            android:text=\"@string/generic_title_text\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\"\n            app:layout_constraintEnd_toEndOf=\"@+id/icon\"\n            app:layout_constraintStart_toStartOf=\"@+id/icon\"\n            app:layout_constraintTop_toBottomOf=\"@+id/icon\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:layout_marginTop=\"16dp\"\n            android:text=\"@string/signed_out\"\n            app:layout_constraintEnd_toEndOf=\"@+id/titleText\"\n            app:layout_constraintStart_toStartOf=\"@+id/titleText\"\n            app:layout_constraintTop_toBottomOf=\"@+id/titleText\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            app:layout_constraintEnd_toEndOf=\"@+id/status\"\n            app:layout_constraintStart_toStartOf=\"@+id/status\"\n            app:layout_constraintTop_toBottomOf=\"@+id/status\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n\n        <TextView\n            android:id=\"@+id/providerSpinnerLabel\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:text=\"@string/generic_label_provider\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"bold\"\n            app:layout_constraintStart_toStartOf=\"@+id/titleText\"\n            app:layout_constraintTop_toBottomOf=\"@+id/detail\" />\n\n        <Spinner\n            android:id=\"@+id/providerSpinner\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignBaseline=\"@+id/providerSpinnerLabel\"\n            android:layout_toEndOf=\"@+id/providerSpinnerLabel\"\n            android:layout_toRightOf=\"@+id/providerSpinnerLabel\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/providerSpinnerLabel\"\n            app:layout_constraintEnd_toEndOf=\"@+id/titleText\"\n            app:layout_constraintStart_toEndOf=\"@+id/providerSpinnerLabel\"\n            app:layout_constraintTop_toTopOf=\"@+id/providerSpinnerLabel\" />\n\n        <androidx.constraintlayout.widget.Group\n            android:id=\"@+id/spinnerLayout\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:constraint_referenced_ids=\"providerSpinner, providerSpinnerLabel\"/>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@color/grey_300\"\n        android:gravity=\"center_vertical\">\n\n        <Button\n            android:id=\"@+id/genericSignInButton\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/sign_in\"\n            android:layout_gravity=\"center\"\n            android:layout_margin=\"16dp\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\" />\n\n        <Button\n            android:id=\"@+id/signOutButton\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/sign_out\"\n            android:layout_gravity=\"center\"\n            android:layout_margin=\"16dp\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            android:visibility=\"gone\" />\n\n    </FrameLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_google.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        android:indeterminate=\"true\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"invisible\"\n        style=\"?android:attr/progressBarStyleHorizontal\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:id=\"@+id/googleIcon\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/google_title_text\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n\n    </LinearLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:paddingTop=\"16dp\"\n        android:background=\"@color/grey_300\">\n\n        <com.google.android.gms.common.SignInButton\n            android:id=\"@+id/signInButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:visibility=\"visible\"\n            tools:visibility=\"gone\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <Button\n            android:id=\"@+id/signOutButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/sign_out\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/signInButton\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_multi_factor.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        android:indeterminate=\"true\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"invisible\"\n        style=\"?android:attr/progressBarStyleHorizontal\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/label_multi_factor\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n\n        <TextView\n            android:id=\"@+id/mfaInfo\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Second factors: \" />\n    </LinearLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:paddingTop=\"16dp\"\n        android:background=\"@color/grey_300\">\n\n        <Button\n            android:id=\"@+id/emailSignInButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginEnd=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:text=\"@string/sign_in\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            app:layout_constraintGuide_percent=\"0.5\" />\n\n        <Button\n            android:id=\"@+id/verifyEmailButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginEnd=\"4dp\"\n            android:layout_marginRight=\"4dp\"\n            android:text=\"@string/verify_email\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/emailSignInButton\" />\n\n        <Button\n            android:id=\"@+id/reloadButton\"\n            style=\"@style/Widget.AppCompat.Button.Colored\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"4dp\"\n            android:layout_marginLeft=\"4dp\"\n            android:layout_marginEnd=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:text=\"@string/reload\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/verifyEmailButton\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toTopOf=\"@+id/verifyEmailButton\" />\n\n        <Button\n            android:id=\"@+id/enrollMfa\"\n            style=\"@style/Widget.AppCompat.Button.Colored\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"4dp\"\n            android:layout_marginRight=\"4dp\"\n            android:text=\"@string/enroll_mfa\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"@+id/verifyEmailButton\"\n            app:layout_constraintTop_toBottomOf=\"@+id/verifyEmailButton\" />\n\n        <Button\n            android:id=\"@+id/unenrollMfa\"\n            style=\"@style/Widget.AppCompat.Button.Colored\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"4dp\"\n            android:layout_marginLeft=\"4dp\"\n            android:layout_weight=\"1\"\n            android:text=\"@string/unenroll_mfa\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/enrollMfa\"\n            app:layout_constraintEnd_toEndOf=\"@+id/reloadButton\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toBottomOf=\"@+id/reloadButton\" />\n\n        <Button\n            android:id=\"@+id/signOutButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"@string/sign_out\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toEndOf=\"@+id/unenrollMfa\"\n            app:layout_constraintStart_toStartOf=\"@+id/enrollMfa\"\n            app:layout_constraintTop_toBottomOf=\"@+id/unenrollMfa\" />\n\n        <androidx.constraintlayout.widget.Group\n            android:id=\"@+id/signedInButtons\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"gone\"\n            app:constraint_referenced_ids=\"verifyEmailButton, reloadButton, enrollMfa, unenrollMfa, signOutButton\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_multi_factor_sign_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <Button\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/phoneFactor1\"/>\n\n    <Button\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/phoneFactor2\"/>\n\n    <Button\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/phoneFactor3\"/>\n\n    <Button\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/phoneFactor4\"/>\n\n    <Button\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/phoneFactor5\"/>\n\n\n    <EditText\n        android:id=\"@+id/smsCode\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/hint_verification_code\"/>\n\n    <Button\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/finishMfaSignIn\"\n        android:text=\"@string/verify_phone_auth\"\n        android:clickable=\"false\"/>\n</LinearLayout>"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_passwordless.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        android:indeterminate=\"true\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"invisible\"\n        style=\"?android:attr/progressBarStyleHorizontal\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/passwordless_title_text\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n\n    </LinearLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:paddingTop=\"16dp\"\n        android:background=\"@color/grey_300\"\n        android:gravity=\"center_vertical\">\n\n        <Button\n            android:id=\"@+id/passwordlessSendEmailButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:text=\"@string/send_link\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"@+id/fieldEmail\"\n            app:layout_constraintTop_toBottomOf=\"@+id/fieldEmail\" />\n\n        <Button\n            android:id=\"@+id/passwordlessSignInButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:text=\"@string/sign_in\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/passwordlessSendEmailButton\"\n            app:layout_constraintEnd_toEndOf=\"@+id/fieldEmail\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toTopOf=\"@+id/passwordlessSendEmailButton\"\n            tools:enabled=\"false\" />\n\n        <Button\n            android:id=\"@+id/signOutButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginEnd=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:text=\"@string/sign_out\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            android:visibility=\"gone\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/passwordlessSendEmailButton\" />\n\n        <EditText\n            android:id=\"@+id/fieldEmail\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginEnd=\"16dp\"\n            android:autofillHints=\"emailAddress\"\n            android:hint=\"@string/hint_email\"\n            android:inputType=\"textEmailAddress\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"/>\n\n        <androidx.constraintlayout.widget.Group\n            android:id=\"@+id/passwordlessButtons\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:constraint_referenced_ids=\"passwordlessSignInButton, passwordlessSendEmailButton, fieldEmail\"/>\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            app:layout_constraintGuide_percent=\"0.5\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "auth/app/src/main/res/layout/fragment_phone_auth.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/grey_100\"\n    android:orientation=\"vertical\"\n    android:weightSum=\"4\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"3\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            style=\"@style/ThemeOverlay.FirebaseIcon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/desc_firebase_lockup\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <TextView\n            android:id=\"@+id/titleText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"@dimen/title_bottom_margin\"\n            android:text=\"@string/phone_title_text\"\n            android:theme=\"@style/ThemeOverlay.MyTitleText\" />\n\n        <TextView\n            android:id=\"@+id/status\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            android:text=\"@string/signed_out\" />\n\n        <TextView\n            android:id=\"@+id/detail\"\n            style=\"@style/ThemeOverlay.MyTextDetail\"\n            tools:text=\"Firebase User ID: 123456789abc\" />\n\n    </LinearLayout>\n\n\n    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:paddingTop=\"16dp\"\n        android:background=\"@color/grey_300\">\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            app:layout_constraintGuide_percent=\"0.5\" />\n\n        <EditText\n            android:id=\"@+id/fieldPhoneNumber\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginEnd=\"4dp\"\n            android:layout_marginRight=\"4dp\"\n            android:hint=\"@string/hint_phone_number\"\n            android:inputType=\"phone\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <EditText\n            android:id=\"@+id/fieldVerificationCode\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"4dp\"\n            android:layout_marginLeft=\"4dp\"\n            android:layout_marginEnd=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:layout_weight=\"1.0\"\n            android:hint=\"@string/hint_verification_code\"\n            android:inputType=\"number\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/fieldPhoneNumber\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toTopOf=\"@+id/fieldPhoneNumber\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/buttonStartVerification\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:text=\"@string/start_phone_auth\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/buttonVerifyPhone\"\n            app:layout_constraintEnd_toStartOf=\"@+id/buttonVerifyPhone\"\n            app:layout_constraintTop_toTopOf=\"@+id/buttonVerifyPhone\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/buttonVerifyPhone\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/verify_phone_auth\"\n            app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n            app:layout_constraintStart_toStartOf=\"@+id/guideline\"\n            app:layout_constraintTop_toBottomOf=\"@+id/fieldVerificationCode\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/buttonResend\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_weight=\"1.0\"\n            android:text=\"@string/resend_phone_auth\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/buttonVerifyPhone\"\n            app:layout_constraintStart_toEndOf=\"@+id/buttonVerifyPhone\"\n            app:layout_constraintTop_toTopOf=\"@+id/buttonVerifyPhone\" />\n\n        <androidx.constraintlayout.widget.Group\n            android:id=\"@+id/phoneAuthFields\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:constraint_referenced_ids=\"fieldPhoneNumber, fieldVerificationCode, buttonResend, buttonStartVerification, buttonVerifyPhone\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/signOutButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/sign_out\"\n            android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n            android:visibility=\"gone\"\n            app:layout_constraintEnd_toEndOf=\"@+id/fieldVerificationCode\"\n            app:layout_constraintStart_toStartOf=\"@+id/fieldPhoneNumber\"\n            app:layout_constraintTop_toBottomOf=\"@+id/buttonVerifyPhone\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</LinearLayout>\n\n"
  },
  {
    "path": "auth/app/src/main/res/layout/item_spinner_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@android:id/text1\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_vertical\"\n    android:padding=\"8dp\"\n    android:textSize=\"16sp\"\n    tools:text=\"Hello!\" />\n\n"
  },
  {
    "path": "auth/app/src/main/res/navigation/nav_graph_java.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    app:startDestination=\"@id/ChooserFragment\"\n    android:id=\"@+id/nav_graph_java\">\n\n    <fragment\n        android:id=\"@+id/ChooserFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.ChooserFragment\"\n        tools:layout=\"@layout/fragment_chooser\">\n\n        <action\n            android:id=\"@+id/action_anonymousauth\"\n            app:destination=\"@id/AnonymousAuthFragment\" />\n\n        <action\n            android:id=\"@+id/action_customauth\"\n            app:destination=\"@id/CustomAuthFragment\" />\n\n        <action\n            android:id=\"@+id/action_emailpassword\"\n            app:destination=\"@id/EmailPasswordFragment\" />\n\n        <action\n            android:id=\"@+id/action_facebook\"\n            app:destination=\"@id/FacebookLoginFragment\" />\n\n        <action\n            android:id=\"@+id/action_firebaseui\"\n            app:destination=\"@id/FirebaseUIFragment\" />\n\n        <action\n            android:id=\"@+id/action_genericidp\"\n            app:destination=\"@id/GenericIdpFragment\" />\n\n        <action\n            android:id=\"@+id/action_google\"\n            app:destination=\"@id/GoogleSignInFragment\" />\n\n        <action\n            android:id=\"@+id/action_mfa\"\n            app:destination=\"@id/MultiFactorFragment\" />\n\n        <action\n            android:id=\"@+id/action_phoneauth\"\n            app:destination=\"@id/PhoneAuthFragment\" />\n\n        <action\n            android:id=\"@+id/action_passwordless\"\n            app:destination=\"@id/PasswordlessActivity\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/AnonymousAuthFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.AnonymousAuthFragment\"\n        tools:layout=\"@layout/fragment_anonymous_auth\"/>\n\n    <fragment\n        android:id=\"@+id/CustomAuthFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.CustomAuthFragment\"\n        tools:layout=\"@layout/fragment_custom\"/>\n\n    <fragment\n        android:id=\"@+id/EmailPasswordFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.EmailPasswordFragment\"\n        tools:layout=\"@layout/fragment_emailpassword\">\n\n        <action\n            android:id=\"@+id/action_emailpassword_to_mfasignin\"\n            app:destination=\"@id/MultiFactorSignInFragment\" />\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/FacebookLoginFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.FacebookLoginFragment\"\n        tools:layout=\"@layout/fragment_emailpassword\"/>\n\n    <fragment\n        android:id=\"@+id/FirebaseUIFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.FirebaseUIFragment\"\n        tools:layout=\"@layout/fragment_firebase_ui\"/>\n\n    <fragment\n        android:id=\"@+id/GenericIdpFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.GenericIdpFragment\"\n        tools:layout=\"@layout/fragment_generic_idp\"/>\n\n    <fragment\n        android:id=\"@+id/GoogleSignInFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.GoogleSignInFragment\"\n        tools:layout=\"@layout/fragment_google\"/>\n\n    <fragment\n        android:id=\"@+id/MultiFactorEnrollFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.MultiFactorEnrollFragment\"\n        tools:layout=\"@layout/fragment_phone_auth\"/>\n\n    <fragment\n        android:id=\"@+id/MultiFactorFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.MultiFactorFragment\"\n        tools:layout=\"@layout/fragment_multi_factor\">\n\n        <action\n            android:id=\"@+id/action_mfa_to_emailpassword\"\n            app:destination=\"@id/EmailPasswordFragment\" />\n\n        <action\n            android:id=\"@+id/action_mfa_to_enroll\"\n            app:destination=\"@id/MultiFactorEnrollFragment\" />\n\n        <action\n            android:id=\"@+id/action_mfa_to_unenroll\"\n            app:destination=\"@id/MultiFactorUnenrollFragment\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/MultiFactorSignInFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.MultiFactorSignInFragment\"\n        tools:layout=\"@layout/fragment_multi_factor_sign_in\">\n\n        <action\n            android:id=\"@+id/action_mfasignin_to_mfa\"\n            app:popUpTo=\"@id/MultiFactorFragment\"\n            app:destination=\"@id/MultiFactorFragment\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/MultiFactorUnenrollFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.MultiFactorUnenrollFragment\"\n        tools:layout=\"@layout/fragment_multi_factor_sign_in\"/>\n\n    <fragment\n        android:id=\"@+id/PhoneAuthFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.java.PhoneAuthFragment\"\n        tools:layout=\"@layout/fragment_phone_auth\"/>\n\n    <activity\n        android:id=\"@+id/PasswordlessActivity\"\n        android:name=\"com.google.firebase.quickstart.auth.java.PasswordlessActivity\"\n        tools:layout=\"@layout/activity_passwordless\"/>\n\n</navigation>"
  },
  {
    "path": "auth/app/src/main/res/navigation/nav_graph_kotlin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    app:startDestination=\"@id/ChooserFragment\"\n    android:id=\"@+id/nav_graph_kotlin\">\n\n    <fragment\n        android:id=\"@+id/ChooserFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.ChooserFragment\"\n        tools:layout=\"@layout/fragment_chooser\">\n\n        <action\n            android:id=\"@+id/action_anonymousauth\"\n            app:destination=\"@id/AnonymousAuthFragment\" />\n\n        <action\n            android:id=\"@+id/action_customauth\"\n            app:destination=\"@id/CustomAuthFragment\" />\n\n        <action\n            android:id=\"@+id/action_emailpassword\"\n            app:destination=\"@id/EmailPasswordFragment\" />\n\n        <action\n            android:id=\"@+id/action_facebook\"\n            app:destination=\"@id/FacebookLoginFragment\" />\n\n        <action\n            android:id=\"@+id/action_firebaseui\"\n            app:destination=\"@id/FirebaseUIFragment\" />\n\n        <action\n            android:id=\"@+id/action_genericidp\"\n            app:destination=\"@id/GenericIdpFragment\" />\n\n        <action\n            android:id=\"@+id/action_google\"\n            app:destination=\"@id/GoogleSignInFragment\" />\n\n        <action\n            android:id=\"@+id/action_mfa\"\n            app:destination=\"@id/MultiFactorFragment\" />\n\n        <action\n            android:id=\"@+id/action_phoneauth\"\n            app:destination=\"@id/PhoneAuthFragment\" />\n\n        <action\n            android:id=\"@+id/action_passwordless\"\n            app:destination=\"@id/PasswordlessActivity\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/AnonymousAuthFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.AnonymousAuthFragment\"\n        tools:layout=\"@layout/fragment_anonymous_auth\"/>\n\n    <fragment\n        android:id=\"@+id/CustomAuthFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.CustomAuthFragment\"\n        tools:layout=\"@layout/fragment_custom\"/>\n\n    <fragment\n        android:id=\"@+id/EmailPasswordFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.EmailPasswordFragment\"\n        tools:layout=\"@layout/fragment_emailpassword\">\n\n        <action\n            android:id=\"@+id/action_emailpassword_to_mfasignin\"\n            app:destination=\"@id/MultiFactorSignInFragment\" />\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/FacebookLoginFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.FacebookLoginFragment\"\n        tools:layout=\"@layout/fragment_emailpassword\"/>\n\n    <fragment\n        android:id=\"@+id/FirebaseUIFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.FirebaseUIFragment\"\n        tools:layout=\"@layout/fragment_firebase_ui\"/>\n\n    <fragment\n        android:id=\"@+id/GenericIdpFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.GenericIdpFragment\"\n        tools:layout=\"@layout/fragment_generic_idp\"/>\n\n    <fragment\n        android:id=\"@+id/GoogleSignInFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.GoogleSignInFragment\"\n        tools:layout=\"@layout/fragment_google\"/>\n\n    <fragment\n        android:id=\"@+id/MultiFactorEnrollFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.MultiFactorEnrollFragment\"\n        tools:layout=\"@layout/fragment_phone_auth\"/>\n\n    <fragment\n        android:id=\"@+id/MultiFactorFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.MultiFactorFragment\"\n        tools:layout=\"@layout/fragment_multi_factor\">\n\n        <action\n            android:id=\"@+id/action_mfa_to_emailpassword\"\n            app:destination=\"@id/EmailPasswordFragment\" />\n\n        <action\n            android:id=\"@+id/action_mfa_to_enroll\"\n            app:destination=\"@id/MultiFactorEnrollFragment\" />\n\n        <action\n            android:id=\"@+id/action_mfa_to_unenroll\"\n            app:destination=\"@id/MultiFactorUnenrollFragment\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/MultiFactorSignInFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.MultiFactorSignInFragment\"\n        tools:layout=\"@layout/fragment_multi_factor_sign_in\">\n\n        <action\n            android:id=\"@+id/action_mfasignin_to_mfa\"\n            app:popUpTo=\"@id/MultiFactorFragment\"\n            app:destination=\"@id/MultiFactorFragment\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/MultiFactorUnenrollFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.MultiFactorUnenrollFragment\"\n        tools:layout=\"@layout/fragment_multi_factor_sign_in\"/>\n\n    <fragment\n        android:id=\"@+id/PhoneAuthFragment\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.PhoneAuthFragment\"\n        tools:layout=\"@layout/fragment_phone_auth\"/>\n\n    <activity\n        android:id=\"@+id/PasswordlessActivity\"\n        android:name=\"com.google.firebase.quickstart.auth.kotlin.PasswordlessActivity\"\n        tools:layout=\"@layout/activity_passwordless\"/>\n\n</navigation>"
  },
  {
    "path": "auth/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n\n    <color name=\"grey_100\">#F5F5F5</color>\n    <color name=\"grey_300\">#E0E0E0</color>\n    <color name=\"grey_500\">#9E9E9E</color>\n</resources>\n"
  },
  {
    "path": "auth/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n\n    <dimen name=\"icon_top_margin\">24dp</dimen>\n    <dimen name=\"icon_bottom_margin\">24dp</dimen>\n    <dimen name=\"title_bottom_margin\">16dp</dimen>\n    <dimen name=\"field_width_standard\">160dp</dimen>\n\n    <dimen name=\"button_horizontal_margin\">8dp</dimen>\n</resources>\n"
  },
  {
    "path": "auth/app/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--\n        Your Facebook App ID. See README.\n    -->\n    <!--  TODO(developer): REPLACE -->\n    <string name=\"facebook_app_id\">REPLACE_ME</string>\n    <string name=\"facebook_client_token\">REPLACE_ME</string>\n</resources>\n"
  },
  {
    "path": "auth/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase Authentication</string>\n\n    <string name=\"label_google_sign_in\">Firebase + Google Sign In</string>\n    <string name=\"desc_google_sign_in\">Use a Google Sign In credential to authenticate with Firebase.</string>\n\n    <string name=\"label_facebook_login\">Firebase + Facebook Login</string>\n    <string name=\"desc_facebook_login\">Use a Facebook Login credential to authenticate with Firebase.</string>\n\n    <string name=\"label_emailpassword\">Email/Password Authentication</string>\n    <string name=\"desc_emailpassword\">Use an email and password to authenticate with Firebase.</string>\n\n    <string name=\"label_passwordless\">Passwordless Authentication</string>\n    <string name=\"desc_passwordless\">Use only an email to authenticate with Firebase.</string>\n\n    <string name=\"label_phone_auth\">Phone Number Authentication</string>\n    <string name=\"desc_phone_auth\">Use a phone number to authenticate with Firebase.</string>\n\n    <string name=\"label_anonymous_auth\">Anonymous Authentication</string>\n    <string name=\"desc_anonymous_auth\">Sign in anonymously and then later upgrade to a full Firebase Auth user.</string>\n\n    <string name=\"label_anonymous_sign_in\">Anonymous Sign In</string>\n    <string name=\"label_account_linking\">Account Linking</string>\n    <string name=\"btn_link_account\">Link Account</string>\n\n\n    <string name=\"label_custom_auth\">Custom Authentication</string>\n    <string name=\"desc_custom_auth\">Use a custom token signed by your own server to authenticate with Firebase.</string>\n\n    <string name=\"label_firebase_ui\">FirebaseUI Auth</string>\n    <string name=\"desc_firebase_ui\">Use the FirebaseUI-Android library to authenticate with Firebase.</string>\n\n    <string name=\"label_generic_idp\">Generic OAuth</string>\n    <string name=\"desc_generic_idp\">Use a generic OAuth identity provider, like Apple, Microsoft, Yahoo, or Twitter to authenticate with Firebase.</string>\n\n    <string name=\"label_multi_factor\">Multi-Factor Authentication</string>\n    <string name=\"desc_multi_factor\">Use SMS for two-factor authentication. Note: this feature is only available for Cloud Identity Platform.</string>\n\n    <string name=\"hint_user_id\">User ID</string>\n    <string name=\"get_custom_token\">Get Token</string>\n    <string name=\"sign_in\">Sign In</string>\n    <string name=\"create_account\">Create Account</string>\n    <string name=\"send_link\">Send Link</string>\n    <string name=\"sign_out\">Sign Out</string>\n    <string name=\"verify_email\">Verify</string>\n    <string name=\"loading\">Loading…</string>\n    <string name=\"custom_token\">Custom Token</string>\n    <string name=\"signed_in\">Signed In</string>\n    <string name=\"signed_out\">Signed Out</string>\n    <string name=\"token_null\">Token: null</string>\n    <string name=\"hint_email\">Email</string>\n    <string name=\"hint_password\">Password</string>\n    <string name=\"desc_firebase_lockup\">Firebase logo and name</string>\n    <string name=\"auth_failed\">Authentication failed</string>\n    <string name=\"start_phone_auth\">Start</string>\n    <string name=\"verify_phone_auth\">Verify</string>\n    <string name=\"resend_phone_auth\">Resend</string>\n    <string name=\"hint_phone_number\">Phone Number</string>\n    <string name=\"hint_verification_code\">Verification Code</string>\n    <string name=\"status_code_sent\">Code Sent</string>\n    <string name=\"status_verification_failed\">Verification failed</string>\n    <string name=\"status_verification_succeeded\">Verification succeeded</string>\n    <string name=\"status_sign_in_failed\">Sign-in failed</string>\n    <string name=\"instant_validation\">(instant validation)</string>\n\n    <string name=\"firebase_status_fmt\">Firebase UID: %s</string>\n    <string name=\"firebase_user_management\">Firebase User Management</string>\n\n    <string name=\"google_status_fmt\">Google User: %s</string>\n    <string name=\"google_title_text\">Google Sign In</string>\n\n    <string name=\"facebook_status_fmt\">Facebook User: %s</string>\n    <string name=\"facebook_title_text\">Facebook Login</string>\n\n    <string name=\"emailpassword_status_fmt\">Email User: %1$s (verified: %2$b)</string>\n    <string name=\"emailpassword_title_text\">Email and Password</string>\n\n    <string name=\"passwordless_status_fmt\">Email User: %1$s (verified: %2$b)</string>\n    <string name=\"passwordless_title_text\">Passwordless Sign In</string>\n    <string name=\"status_email_not_sent\">Send link to your email to get started.</string>\n    <string name=\"status_email_sent\">Link sent, check your email to continue.</string>\n    <string name=\"status_link_found\">Link received, enter email to sign in.</string>\n\n    <string name=\"phone_title_text\">Phone Number</string>\n\n    <string name=\"twitter_status_fmt\">Twitter User: %s</string>\n    <string name=\"twitter_title_text\">Twitter Login</string>\n\n    <string name=\"firebaseui_status_fmt\">Firebase User: %s</string>\n    <string name=\"firebaseui_title_text\">FirebaseUI Auth</string>\n\n    <string name=\"generic_title_text\">OAuth Sign In</string>\n    <string name=\"generic_signin_fmt\">Sign In with %s</string>\n    <string name=\"generic_status_fmt\">User: %s (%s)</string>\n    <string name=\"generic_label_provider\">Provider:</string>\n\n    <string name=\"id_fmt\">User ID: %s</string>\n    <string name=\"email_fmt\">Email: %s</string>\n\n    <string name=\"enroll_mfa\">Enroll MFA</string>\n    <string name=\"unenroll_mfa\">Unenroll MFA</string>\n    <string name=\"reload\">Reload</string>\n\n    <string name=\"custom_auth_signin_status_user\">User ID: %s</string>\n    <string name=\"custom_auth_signin_status_failed\">Error: sign in failed</string>\n\n    <string name=\"error_sign_in_failed\">Sign in failed, see logs for details.</string>\n    <string name=\"multi_factor_signed_out\">You are not signed in. To use this sample, first navigate to the Email/Password example to sign in and then return here to enroll in multi-factor auth.</string>\n\n</resources>\n"
  },
  {
    "path": "auth/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <!-- Activity with no Title -->\n    <style name=\"ThemeOverlay.MyNoTitleActivity\" parent=\"AppTheme\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n    <!-- Dark Buttons -->\n    <style name=\"ThemeOverlay.MyDarkButton\" parent=\"ThemeOverlay.AppCompat.Dark\">\n        <item name=\"colorButtonNormal\">@color/grey_500</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n        <item name=\"android:layout_marginRight\">4dp</item>\n        <item name=\"android:layout_marginLeft\">4dp</item>\n        <item name=\"android:textColor\">@android:color/white</item>\n    </style>\n\n    <!-- Light EditTexts -->\n    <style name=\"ThemeOverlay.MyLightEditText\" parent=\"ThemeOverlay.AppCompat.Dark\"/>\n\n    <style name=\"ThemeOverlay.MyTextDetail\" parent=\"AppTheme\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:fadeScrollbars\">true</item>\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:maxLines\">5</item>\n        <item name=\"android:padding\">4dp</item>\n        <item name=\"android:scrollbars\">vertical</item>\n        <item name=\"android:textSize\">14sp</item>\n    </style>\n\n    <style name=\"ThemeOverlay.FirebaseIcon\" parent=\"AppTheme\">\n        <item name=\"android:layout_marginTop\">@dimen/icon_top_margin</item>\n        <item name=\"android:layout_marginBottom\">@dimen/icon_bottom_margin</item>\n    </style>\n\n    <style name=\"ThemeOverlay.MyTitleText\" parent=\"AppTheme\">\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textSize\">36sp</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "auth/app/src/main/res/values-land/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"icon_top_margin\">0dp</dimen>\n    <dimen name=\"icon_bottom_margin\">0dp</dimen>\n</resources>\n"
  },
  {
    "path": "auth/app/src/main/res/values-v21/styles.xml",
    "content": "<resources>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "auth/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "auth/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "auth/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "auth/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "auth/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "auth/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "auth/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "auth/web/auth.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=utf-8 />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Custom Token Generator Example</title>\n  <link rel=\"stylesheet\" href=\"https://storage.googleapis.com/code.getmdl.io/1.0.6/material.teal-orange.min.css\">\n  <script src=\"https://storage.googleapis.com/code.getmdl.io/1.0.6/material.min.js\"></script>\n  <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/icon?family=Material+Icons\">\n  <script language=\"JavaScript\" type=\"text/javascript\"\n        src=\"http://kjur.github.io/jsrsasign/jsrsasign-latest-all-min.js\">\n  </script>\n  <script>\n        // These values are extracted from the service account JSON.\n        var sub = \"\";\n        var sPKCS8PEM = \"\";\n        var kid = \"\"\n\n        // Generate an ID token and sign it with the private key.\n        function handleGenerateClick(evt) {\n          var uid = document.getElementById('uid').value;\n          if (uid == '') {\n            console.log(\"Blank uid\");\n            return;\n          }\n          // Header\n          var oHeader = {alg: 'RS256', kid: kid, typ: 'JWT'};\n\n          // Payload\n          var oPayload = {};\n          var tNow = KJUR.jws.IntDate.get('now');\n          var tEnd = KJUR.jws.IntDate.get('now + 1hour');\n          oPayload.aud = \"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit\";\n          oPayload.exp = tEnd;\n          oPayload.iat = tNow;\n          oPayload.iss = sub;\n          oPayload.sub = sub;\n          oPayload.user_id = uid;\n          oPayload.scope = \"https://www.googleapis.com/auth/identitytoolkit\";\n\n          var sHeader = JSON.stringify(oHeader);\n          var sPayload = JSON.stringify(oPayload);\n\n          var sJWT = KJUR.jws.JWS.sign(null, sHeader, sPayload, sPKCS8PEM, 'notasecret');\n\n          document.getElementById(\"tokenbox\").textContent = sJWT;\n          console.log(sJWT);\n\n          document.getElementById(\"droidbox\").textContent =\n              \"adb shell am broadcast \" +\n              \"-a com.google.example.ACTION_TOKEN \" +\n              \"--es key_token \" + sJWT;\n        }\n\n         function handleFileSelect(evt) {\n           console.log('handleFileSelect', evt);\n\n           evt.stopPropagation();\n           evt.preventDefault();\n           var files = evt.target.files;\n           for (var i = 0; i < files.length; i++) {\n             var f = files[i];\n             if (f.type == \"application/json\" || f.name.indexOf(\".json\") >= 0) {\n               loadJson(f);\n               return;\n             }\n           }\n           console.log(\"No JSON file found!\");\n         }\n\n         function loadJson(f) {\n           var reader = new FileReader();\n           reader.onload = function (_) {\n             data = JSON.parse(reader.result)\n             console.log(data);\n             if (data.type && data.type == \"service_account\") {\n               kid = data.private_key_id;\n               sPKCS8PEM = data.private_key;\n               sub = data.client_email;\n               document.getElementById(\"notoken\").style.display = \"none\";\n               document.getElementById(\"gettoken\").style.display = \"block\";\n               document.getElementById(\"subtext\").textContent = \"Generating Tokens For \" + sub;\n             } else {\n               console.log(\"Bad file read.\");\n             }\n           }\n           reader.readAsText(f)\n         }\n\n</script>\n</head>\n<body>\n  <div class=\"mdl-layout\" id=\"content\">\n    <header class=\"mdl-layout__header mdl-grid\">\n      <h1>Token Generator</h1>\n    </header>\n    <main class=\"mdl-layout__content\">\n      <div class=\"mdl-grid\">\n        <div class=\"mdl-layout-spacer\"></div>\n        <div class=\"mdl-cell mdl-cell-8-col-tablet mdl-cell--8-col-desktop\" id=\"notoken\">\n          <p>In order to generate a token you'll need to use a service account JSON file provided\n            by the <a href=\"https://console.developers.google.com\">Google Developers Console</a> view\n            of your Firebase project. We recommend that once you're done testing with this tool you\n            delete the service account you made, and create a fresh one for the integration with your\n            actual authentication system.\n          </p>\n          <p>\n            To get started, select your JSON file below.\n          </p>\n          <input type=\"file\" id=\"file\" name=\"file\" />\n        </div>\n        <div class=\"mdl-layout-spacer\"></div>\n      </div>\n      <div class=\"mdl-grid\">\n        <div class=\"mdl-layout-spacer\"></div>\n        <div  class=\"mdl-cell mdl-cell-8-col-tablet mdl-cell--8-col-desktop\" id=\"gettoken\" style=\"display: none;\">\n          <h4 id=\"subtext\"></h4>\n          <label>Enter User ID: <input type=\"text\" id=\"uid\" name=\"uid\" /></label> <button id=\"go\">Generate</button>\n          <h5>Token:</h5>\n          <p id=\"tokenbox\" style=\"word-wrap: break-word;\">\n          </p>\n          <h5>ADB Command:</h5>\n          <pre id=\"droidbox\" style=\"overflow-wrap: break-word;\">\n          </pre>\n        </div>\n        <div class=\"mdl-layout-spacer\"></div>\n      </div>\n  </main>\n  </div>\n  <script>\n    document.getElementById('file').addEventListener('change', handleFileSelect, false);\n    document.getElementById('go').addEventListener('click', handleGenerateClick, false);\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n    alias(libs.plugins.firebase.crashlytics) apply false\n    alias(libs.plugins.firebase.perf) apply false\n    alias(libs.plugins.navigation.safeargs) apply false\n    alias(libs.plugins.gradle.versions) apply true\n    alias(libs.plugins.compose.compiler) apply false\n}\n\nallprojects {\n    repositories {\n        google()\n        mavenLocal()\n        mavenCentral()\n    }\n}\n\nval ktlint by configurations.creating\n\ndependencies {\n    ktlint(\"com.pinterest:ktlint:0.49.1\") {\n        attributes {\n            attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))\n        }\n    }\n}\n\ntasks.register<JavaExec>(\"ktlintCheck\") {\n    val outputDir = \"${project.layout.buildDirectory}/reports/ktlint/\"\n    val inputFiles = project.fileTree(\"src\").include(\"**/*.kt\")\n    val outputFile = \"${outputDir}ktlint-checkstyle-report.xml\"\n\n    // See: https://medium.com/@vanniktech/making-your-gradle-tasks-incremental-7f26e4ef09c3\n    inputs.files(inputFiles)\n    outputs.file(outputFile)\n\n    group = LifecycleBasePlugin.VERIFICATION_GROUP\n    description = \"Check Kotlin code style\"\n    classpath = ktlint\n    mainClass.set(\"com.pinterest.ktlint.Main\")\n\n    args(\n        \"--format\",\n        \"--code-style=android_studio\",\n        \"--reporter=plain\",\n        \"--reporter=checkstyle,output=${outputFile}\",\n        \"**/*.kt\",\n        \"!**/build/**\"\n    )\n\n    jvmArgs(\"--add-opens=java.base/java.lang=ALL-UNNAMED\")\n}\n\nfun notFromFirebase(candidate: ModuleComponentIdentifier): Boolean {\n    return candidate.group != \"com.google.firebase\"\n}\n\nfun isNonStable(candidate: ModuleComponentIdentifier): Boolean {\n    return listOf(\"alpha\", \"beta\", \"rc\", \"snapshot\", \"-m\", \"final\").any { keyword ->\n        keyword in candidate.version.lowercase()\n    }\n}\n\nfun isBlockListed(candidate: ModuleComponentIdentifier): Boolean {\n    return listOf(\n            \"androidx.browser:browser\",\n            \"androidx.webkit:webkit\",\n            \"com.facebook.android\",\n            \"com.google.guava\",\n            \"com.github.bumptech.glide\"\n    ).any { keyword ->\n        keyword in candidate.toString().lowercase()\n    }\n}\n\ntasks.withType<DependencyUpdatesTask> {\n    rejectVersionIf {\n        (isNonStable(candidate) && notFromFirebase(candidate)) || isBlockListed(candidate)\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "build_pull_request.sh",
    "content": "#!/bin/bash\n\n# Exit on error\nset -e\n\n# unshallow since GitHub actions does a shallow clone\ngit fetch --unshallow\ngit fetch origin\n\n# Get all the modules that were changed\nwhile read line; do\n  module_name=${line%%/*}\n  if [[ ${MODULES} != *\"${module_name}\" ]]; then\n    MODULES=\"${MODULES} ${module_name}\"\n  fi\ndone < <(git diff --name-only origin/$GITHUB_BASE_REF)\nchanged_modules=$MODULES\n\n# Get a list of all available gradle tasks\nAVAILABLE_TASKS=$(./gradlew tasks --all)\n\n# Check if these modules have gradle tasks\nbuild_commands=\"\"\nfor module in $changed_modules\ndo\n  if [[ $AVAILABLE_TASKS =~ $module\":app:\" ]]; then\n    build_commands=${build_commands}\" :\"${module}\":app:assembleDebug :\"${module}\":app:check\"\n  fi\ndone\n\n# Build\necho \"Building Pull Request with\"\necho $build_commands\neval \"./gradlew clean ktlint ${build_commands}\"\n"
  },
  {
    "path": "config/README.md",
    "content": "Firebase Remote Config Quickstart\n==============================\n\nThe Firebase Remote Config Android quickstart app demonstrates using Remote\nConfig to define user-facing text in an Android app.\n\nIntroduction\n------------\n\nThis is a simple example of using Remote Config to override in-app default\nvalues by defining service-side parameter values in the Firebase console. This\nexample demonstrates a small subset of the capabilities of Firebase Remote\nConfig. To learn more about how you can use Firebase Remote Config in your app,\nsee\n[Firebase Remote Config Introduction](https://firebase.google.com/docs/remote-config/).\n\nGetting started\n---------------\n\n1. [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n2. [Create a Remote Config project for the quickstart sample](https://firebase.google.com/docs/remote-config/android#create_a_product_name_project_for_the_quickstart_sample),\n  defining the parameter values and parameter keys used by the sample.\n3. Run the sample on an Android device or emulator.\n4. Change one or more parameter values in the Firebase Console (the value of\n  `welcome_message`, `welcome_message_caps`, or both).\n5. Tap **Fetch Remote Config** in the app to fetch new parameter values and see\n  the resulting change in the app.\n\nBest practices\n--------------\nThis section provides some additional information about how the quickstart\nexample sets in-app default parameter values and fetches values from the Remote\nConfig service\n\n### In-app default parameter values ###\n\nIn-app default values are set using an XML file in this example, but you can\nalso set in-app default values inline using other `setDefault` methods of the\n[`FirebaseRemoteConfig` class](https://firebase.google.com/docs/reference/android/com/google/firebase/remoteconfig/FirebaseRemoteConfig#public-method-summary).\nThen, you can override only those values that you need to change from the\nFirebase console. This lets you use Remote Config for any default value that you\nmight want to override in the future, without the need to set all of those\nvalues in the Firebase console.\n\n### Fetch values from the Remote Config service ###\n\nWhen an app calls `fetch`, locally stored parameter values are used unless the\nminimum fetch interval is reached. The minimal fetch interval is determined by:\n\n1. The parameter passed to `fetch(long minFetchInterval)`.\n2. The minimum fetch interval set in Remote Config settings.\n3. The default minimum fetch interval, 12 hours.\n\nFetched values are immediately activated when retrieved using `fetchAndActivate`.\n`fetchAndActivate` returns true if the final set of key/value pairs now available\nto the application is different to the set before calling `fetchAndActivate`, false\nis returned otherwise. In the quickstart sample app, you call `fetchAndActivate`\nfrom the UI by tapping **Fetch Remote Config**.\n\nTo control when fetched values are activated and available to your app use `fetch`, the\nvalues are locally stored, but not immediately activated. To activate\nfetched values so that they take effect, call the `activate` method.\n\nYou can also create a Remote Config Setting to enable developer mode, but you\nmust remove this setting before distributing your app. Fetching Remote Config\ndata from the service is normally limited to a few requests per hour. By\nenabling developer mode, you can make many more requests per hour, so you can\ntest your app with different Remote Config parameter values during development.\n\n- To learn more about fetching data from remote config, see the Remote Config\n  Frequently Asked Question (FAQ) on\n  [fetching and activating parameter values](https://firebase.google.com/support/faq#remote-config-values).\n- To learn about parameters and conditions that you can use to change the\n  behavior and appearance of your app for segments of your userbase, see\n  [Remote Config Parameters and Conditions](https://firebase.google.com/docs/remote-config/parameters).\n- To learn more about the Remote Config API, see\n  [Remote Config API Overview](https://firebase.google.com/docs/remote-config/api-overview).\n\nResult\n-----------\n<img src=\"https://github.com/firebase/quickstart-android/raw/master/config/app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-remote-config)\n- [Firebase Support](https://firebase.google.com/support/)\n\nLicense\n-------\n\nCopyright 2016 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "config/app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.services)\n}\n\ntasks {\n    check.dependsOn(\"assembleDebugAndroidTest\")\n}\n\nandroid {\n    namespace = \"com.google.samples.quickstart.config\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.samples.quickstart.config\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    implementation(\"com.google.android.material:material:1.13.0\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Firebase Remote Config\n    implementation(\"com.google.firebase:firebase-config\")\n\n    // For an optimal experience using Remote Config, add the Firebase SDK\n    // for Google Analytics. This is recommended, but not required.\n    implementation(\"com.google.firebase:firebase-analytics\")\n\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test.ext:junit:1.3.0\")\n}\n"
  },
  {
    "path": "config/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n"
  },
  {
    "path": "config/app/src/androidTest/java/com/google/samples/quickstart/config/MainActivityTest.java",
    "content": "package com.google.samples.quickstart.config;\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.filters.LargeTest;\n\nimport com.google.samples.quickstart.config.java.MainActivity;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.assertion.ViewAssertions.matches;\nimport static androidx.test.espresso.matcher.RootMatchers.withDecorView;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static androidx.test.espresso.matcher.ViewMatchers.withText;\nimport static org.hamcrest.CoreMatchers.not;\nimport static org.hamcrest.CoreMatchers.startsWith;\nimport static org.hamcrest.Matchers.is;\n\n@LargeTest\n@RunWith(AndroidJUnit4.class)\npublic class MainActivityTest {\n\n    @Rule\n    public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);\n\n    @Test\n    public void testFetchConfig() {\n        // Click fetch config button\n        onView(withText(R.string.fetch_remote_welcome_message))\n                .check(matches(isDisplayed()))\n                .perform(click());\n\n        // Watch for success Toast\n        onView(withText(startsWith(\"Fetch Succeeded\")))\n                .inRoot(withDecorView(not(is(rule.getActivity().getWindow().getDecorView()))))\n                .check(matches(isDisplayed()));\n    }\n\n}\n"
  },
  {
    "path": "config/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\" >\n\n        <activity\n            android:name=\".java.MainActivity\"\n            android:label=\"@string/app_name\" />\n\n        <activity\n            android:name=\".kotlin.MainActivity\"\n            android:label=\"@string/app_name\" />\n\n        <activity\n            android:name=\".EntryChoiceActivity\"\n            android:label=\"@string/app_name\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "config/app/src/main/java/com/google/samples/quickstart/config/EntryChoiceActivity.kt",
    "content": "package com.google.samples.quickstart.config\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Remote Config quickstart written in Java.\",\n                Intent(\n                    this,\n                    com.google.samples.quickstart.config.java.MainActivity::class.java,\n                ),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Remote Config quickstart written in Kotlin.\",\n                Intent(\n                    this,\n                    com.google.samples.quickstart.config.kotlin.MainActivity::class.java,\n                ),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "config/app/src/main/java/com/google/samples/quickstart/config/java/MainActivity.java",
    "content": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * For more information on setting up and running this sample code, see\n * https://firebase.google.com/docs/remote-config/android\n */\n\npackage com.google.samples.quickstart.config.java;\n\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.remoteconfig.FirebaseRemoteConfig;\nimport com.google.firebase.remoteconfig.FirebaseRemoteConfigException;\nimport com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;\nimport com.google.samples.quickstart.config.R;\nimport com.google.samples.quickstart.config.databinding.ActivityMainBinding;\nimport com.google.firebase.remoteconfig.ConfigUpdateListener;\nimport com.google.firebase.remoteconfig.ConfigUpdate;\n\n\npublic class MainActivity extends AppCompatActivity {\n\n    private static final String TAG = \"MainActivity\";\n\n    // Remote Config keys\n    private static final String LOADING_PHRASE_CONFIG_KEY = \"loading_phrase\";\n    private static final String WELCOME_MESSAGE_KEY = \"welcome_message\";\n    private static final String WELCOME_MESSAGE_CAPS_KEY = \"welcome_message_caps\";\n\n    private FirebaseRemoteConfig mFirebaseRemoteConfig;\n    private ActivityMainBinding mBinding;\n    private TextView mWelcomeTextView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mBinding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(mBinding.getRoot());\n\n        mWelcomeTextView = mBinding.welcomeTextView;\n        mBinding.fetchButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                fetchWelcome();\n            }\n        });\n\n        // Get Remote Config instance.\n        // [START get_remote_config_instance]\n        mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();\n        // [END get_remote_config_instance]\n\n        // Create a Remote Config Setting to enable developer mode, which you can use to increase\n        // the number of fetches available per hour during development. Also use Remote Config\n        // Setting to set the minimum fetch interval.\n        // [START enable_dev_mode]\n        FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()\n                .setMinimumFetchIntervalInSeconds(3600)\n                .build();\n        mFirebaseRemoteConfig.setConfigSettingsAsync(configSettings);\n        // [END enable_dev_mode]\n\n        // Set default Remote Config parameter values. An app uses the in-app default values, and\n        // when you need to adjust those defaults, you set an updated value for only the values you\n        // want to change in the Firebase console. See Best Practices in the README for more\n        // information.\n        // [START set_default_values]\n        mFirebaseRemoteConfig.setDefaultsAsync(R.xml.remote_config_defaults);\n        // [END set_default_values]\n\n        // [START add_config_update_listener]\n        mFirebaseRemoteConfig.addOnConfigUpdateListener(new ConfigUpdateListener() {\n            @Override\n            public void onUpdate(ConfigUpdate configUpdate) {\n                Log.d(TAG, \"Updated keys: \" + configUpdate.getUpdatedKeys());\n\n                if (configUpdate.getUpdatedKeys().contains(WELCOME_MESSAGE_KEY)) {\n                    mFirebaseRemoteConfig.activate().addOnCompleteListener(new OnCompleteListener<Boolean>() {\n                        @Override\n                        public void onComplete(@NonNull Task<Boolean> task) {\n                            displayWelcomeMessage();\n                        }\n                    });\n                }\n            }\n\n            @Override\n            public void onError(FirebaseRemoteConfigException error) {\n                Log.w(TAG, \"Config update error with code: \" + error.getCode(), error);\n            }\n        });\n        // [END add_config_update_listener]\n\n        fetchWelcome();\n    }\n\n    /**\n     * Fetch a welcome message from the Remote Config service, and then activate it.\n     */\n    private void fetchWelcome() {\n        mWelcomeTextView.setText(mFirebaseRemoteConfig.getString(LOADING_PHRASE_CONFIG_KEY));\n\n        // [START fetch_config_with_callback]\n        mFirebaseRemoteConfig.fetchAndActivate()\n                .addOnCompleteListener(this, new OnCompleteListener<Boolean>() {\n                    @Override\n                    public void onComplete(@NonNull Task<Boolean> task) {\n                        if (task.isSuccessful()) {\n                            boolean updated = task.getResult();\n                            Log.d(TAG, \"Config params updated: \" + updated);\n                            Toast.makeText(MainActivity.this, \"Fetch and activate succeeded\",\n                                    Toast.LENGTH_SHORT).show();\n\n                        } else {\n                            Toast.makeText(MainActivity.this, \"Fetch failed\",\n                                    Toast.LENGTH_SHORT).show();\n                        }\n                        displayWelcomeMessage();\n                    }\n                });\n        // [END fetch_config_with_callback]\n    }\n\n    /**\n     * Display a welcome message in all caps if welcome_message_caps is set to true. Otherwise,\n     * display a welcome message as fetched from welcome_message.\n     */\n     // [START display_welcome_message]\n    private void displayWelcomeMessage() {\n        // [START get_config_values]\n        String welcomeMessage = mFirebaseRemoteConfig.getString(WELCOME_MESSAGE_KEY);\n        // [END get_config_values]\n        if (mFirebaseRemoteConfig.getBoolean(WELCOME_MESSAGE_CAPS_KEY)) {\n            mWelcomeTextView.setAllCaps(true);\n        } else {\n            mWelcomeTextView.setAllCaps(false);\n        }\n        mWelcomeTextView.setText(welcomeMessage);\n    }\n    // [END display_welcome_message]\n}\n"
  },
  {
    "path": "config/app/src/main/java/com/google/samples/quickstart/config/kotlin/MainActivity.kt",
    "content": "package com.google.samples.quickstart.config.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport com.google.firebase.Firebase\nimport com.google.firebase.remoteconfig.ConfigUpdate\nimport com.google.firebase.remoteconfig.ConfigUpdateListener\nimport com.google.firebase.remoteconfig.FirebaseRemoteConfig\nimport com.google.firebase.remoteconfig.FirebaseRemoteConfigException\nimport com.google.firebase.remoteconfig.get\nimport com.google.firebase.remoteconfig.remoteConfig\nimport com.google.firebase.remoteconfig.remoteConfigSettings\nimport com.google.samples.quickstart.config.R\nimport com.google.samples.quickstart.config.databinding.ActivityMainBinding\n\nclass MainActivity : AppCompatActivity() {\n\n    private lateinit var remoteConfig: FirebaseRemoteConfig\n    private lateinit var binding: ActivityMainBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        binding.fetchButton.setOnClickListener { fetchWelcome() }\n\n        // Get Remote Config instance.\n        // [START get_remote_config_instance]\n        remoteConfig = Firebase.remoteConfig\n        // [END get_remote_config_instance]\n\n        // Create a Remote Config Setting to enable developer mode, which you can use to increase\n        // the number of fetches available per hour during development. Also use Remote Config\n        // Setting to set the minimum fetch interval.\n        // [START enable_dev_mode]\n        val configSettings = remoteConfigSettings {\n            minimumFetchIntervalInSeconds = 3600\n        }\n        remoteConfig.setConfigSettingsAsync(configSettings)\n        // [END enable_dev_mode]\n\n        // Set default Remote Config parameter values. An app uses the in-app default values, and\n        // when you need to adjust those defaults, you set an updated value for only the values you\n        // want to change in the Firebase console. See Best Practices in the README for more\n        // information.\n        // [START set_default_values]\n        remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)\n        // [END set_default_values]\n\n        // [START add_config_update_listener]\n        remoteConfig.addOnConfigUpdateListener(object : ConfigUpdateListener {\n            override fun onUpdate(configUpdate: ConfigUpdate) {\n                Log.d(TAG, \"Updated keys: \" + configUpdate.updatedKeys)\n\n                if (configUpdate.updatedKeys.contains(WELCOME_MESSAGE_KEY)) {\n                    remoteConfig.activate().addOnCompleteListener {\n                        displayWelcomeMessage()\n                    }\n                }\n            }\n\n            override fun onError(error: FirebaseRemoteConfigException) {\n                Log.w(TAG, \"Config update error with code: \" + error.code, error)\n            }\n        })\n        // [END add_config_update_listener]\n\n        fetchWelcome()\n    }\n\n    /**\n     * Fetch a welcome message from the Remote Config service, and then activate it.\n     */\n    private fun fetchWelcome() {\n        binding.welcomeTextView.text = remoteConfig[LOADING_PHRASE_CONFIG_KEY].asString()\n\n        // [START fetch_config_with_callback]\n        remoteConfig.fetchAndActivate()\n            .addOnCompleteListener(this) { task ->\n                if (task.isSuccessful) {\n                    val updated = task.result\n                    Log.d(TAG, \"Config params updated: $updated\")\n                    Toast.makeText(\n                        this,\n                        \"Fetch and activate succeeded\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                } else {\n                    Toast.makeText(\n                        this,\n                        \"Fetch failed\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                }\n                displayWelcomeMessage()\n            }\n        // [END fetch_config_with_callback]\n    }\n\n    /**\n     * Display a welcome message in all caps if welcome_message_caps is set to true. Otherwise,\n     * display a welcome message as fetched from welcome_message.\n     */\n    // [START display_welcome_message]\n    private fun displayWelcomeMessage() {\n        // [START get_config_values]\n        val welcomeMessage = remoteConfig[WELCOME_MESSAGE_KEY].asString()\n        // [END get_config_values]\n        binding.welcomeTextView.isAllCaps = remoteConfig[WELCOME_MESSAGE_CAPS_KEY].asBoolean()\n        binding.welcomeTextView.text = welcomeMessage\n    }\n\n    companion object {\n\n        private const val TAG = \"MainActivity\"\n\n        // Remote Config keys\n        private const val LOADING_PHRASE_CONFIG_KEY = \"loading_phrase\"\n        private const val WELCOME_MESSAGE_KEY = \"welcome_message\"\n        private const val WELCOME_MESSAGE_CAPS_KEY = \"welcome_message_caps\"\n    }\n    // [END display_welcome_message]\n}\n"
  },
  {
    "path": "config/app/src/main/res/layout/activity_main.xml",
    "content": "<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    tools:context=\".java.MainActivity\">\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/welcomeTextView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/icon\"\n        android:layout_marginTop=\"16dp\"\n        android:text=\"Welcome...\"\n        app:layout_constraintEnd_toEndOf=\"@+id/icon\"\n        app:layout_constraintStart_toStartOf=\"@+id/icon\"\n        app:layout_constraintTop_toBottomOf=\"@+id/icon\" />\n\n    <Button\n        android:id=\"@+id/fetchButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/fetch_remote_welcome_message\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/welcomeTextView\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "config/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n</resources>\n"
  },
  {
    "path": "config/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "config/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase Remote Config</string>\n    <string name=\"fetch_remote_welcome_message\">fetch remote welcome</string>\n</resources>\n"
  },
  {
    "path": "config/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "config/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "config/app/src/main/res/xml/remote_config_defaults.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- START xml_defaults -->\n<defaultsMap>\n    <entry>\n        <key>loading_phrase</key>\n        <value>Fetching config…</value>\n    </entry>\n    <entry>\n        <key>welcome_message_caps</key>\n        <value>false</value>\n    </entry>\n    <entry>\n        <key>welcome_message</key>\n        <value>Welcome to my awesome app!</value>\n    </entry>\n</defaultsMap>\n    <!-- END xml_defaults -->\n"
  },
  {
    "path": "config/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n"
  },
  {
    "path": "config/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "config/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "config/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "config/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "config/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "copy_mock_google_services_json.sh",
    "content": "#!/bin/bash\n\n# Exit on error\nset -e\n\n# Copy mock google-services file\necho \"Using mock google-services.json\"\ncp mock-google-services.json admob/app/google-services.json\ncp mock-google-services.json analytics/app/google-services.json\ncp mock-google-services.json appdistribution/app/google-services.json\ncp mock-google-services.json auth/app/google-services.json\ncp mock-google-services.json config/app/google-services.json\ncp mock-google-services.json crash/app/google-services.json\ncp mock-google-services.json database/app/google-services.json\ncp mock-google-services.json dataconnect/app/google-services.json\ncp mock-google-services.json firebase-ai/app/google-services.json\ncp mock-google-services.json firestore/app/google-services.json\ncp mock-google-services.json functions/app/google-services.json\ncp mock-google-services.json inappmessaging/app/google-services.json\ncp mock-google-services.json perf/app/google-services.json\ncp mock-google-services.json messaging/app/google-services.json\ncp mock-google-services.json storage/app/google-services.json\n"
  },
  {
    "path": "crash/.gitignore",
    "content": "*.iml\ngoogle-services.json\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n"
  },
  {
    "path": "crash/README.md",
    "content": "Firebase Crashlytics Quickstart\n===============================\n\nIntroduction\n------------\n\n- [Read more about Firebase Crashlytics](https://firebase.google.com/docs/crashlytics)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n- Run the sample on Android device or emulator.\n\nScreenshots\n-----------\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/crashlytics)\n- [Firebase Support](https://firebase.google.com/support/)\n\nLicense\n-------\n\nCopyright 2016 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "crash/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "crash/app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.services)\n    alias(libs.plugins.firebase.crashlytics)\n}\n\ntasks {\n    check.dependsOn(\"assembleDebugAndroidTest\")\n}\n\nandroid {\n    namespace = \"com.google.samples.quickstart.crash\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.samples.quickstart.crash\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        multiDexEnabled = true\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n        getByName(\"debug\") {\n            isMinifyEnabled = false\n            testProguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"test-proguard-rules.pro\")\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.activity:activity-ktx:1.12.1\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Firebase Crashlytics\n    implementation(\"com.google.firebase:firebase-crashlytics\")\n\n    // For an optimal experience using Crashlytics, add the Firebase SDK\n    // for Google Analytics. This is recommended, but not required.\n    implementation(\"com.google.firebase:firebase-analytics\")\n\n    // For use in the CustomKeySamples -- for testing Google Api Availability.\n    implementation(\"com.google.android.gms:play-services-base:18.5.0\")\n\n    testImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test.ext:junit:1.3.0\")\n}\n"
  },
  {
    "path": "crash/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n\n-dontwarn org.xmlpull.v1.**\n-dontnote org.xmlpull.v1.**\n-keep class org.xmlpull.** { *; }\n-keepclassmembers class org.xmlpull.** { *; }\n"
  },
  {
    "path": "crash/app/src/androidTest/java/com/google/samples/quickstart/crash/MainActivityTest.java",
    "content": "package com.google.samples.quickstart.crash;\n\n\nimport androidx.test.espresso.ViewInteraction;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.filters.LargeTest;\nimport android.widget.CheckBox;\n\nimport com.google.samples.quickstart.crash.java.MainActivity;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static androidx.test.espresso.matcher.ViewMatchers.withId;\nimport static androidx.test.espresso.matcher.ViewMatchers.withText;\nimport static org.hamcrest.Matchers.allOf;\n\n@LargeTest\n@RunWith(AndroidJUnit4.class)\npublic class MainActivityTest {\n\n    @Rule\n    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);\n\n    @Test\n    public void caughtExceptionTest() {\n        // Make sure the checkbox is on screen\n        ViewInteraction al = onView(\n                allOf(withId(R.id.catchCrashCheckBox),\n                        withText(R.string.catch_crash_checkbox_label),\n                        isDisplayed()));\n\n        // Click the checkbox if it's not already checked\n        CheckBox checkBox = (CheckBox) mActivityTestRule.getActivity()\n                .findViewById(R.id.catchCrashCheckBox);\n        if (!checkBox.isChecked()) {\n            al.perform(click());\n        }\n\n        // Cause a crash\n        ViewInteraction ak = onView(\n                allOf(withId(R.id.crashButton),\n                        withText(R.string.crash_button_label),\n                        isDisplayed()));\n        ak.perform(click());\n    }\n}\n"
  },
  {
    "path": "crash/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nCopyright Google Inc. All rights reserved.\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n      http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".EntryChoiceActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity android:name=\".java.MainActivity\" />\n        <activity android:name=\".kotlin.MainActivity\" />\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "crash/app/src/main/java/com/google/samples/quickstart/crash/EntryChoiceActivity.kt",
    "content": "package com.google.samples.quickstart.crash\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Crash quickstart written in Java.\",\n                Intent(this, com.google.samples.quickstart.crash.java.MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Crash quickstart written in Kotlin.\",\n                Intent(this, com.google.samples.quickstart.crash.kotlin.MainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "crash/app/src/main/java/com/google/samples/quickstart/crash/java/CustomKeySamples.java",
    "content": "package com.google.samples.quickstart.crash.java;\n\nimport android.Manifest;\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.pm.InstallSourceInfo;\nimport android.content.pm.PackageManager;\nimport android.net.ConnectivityManager;\nimport android.net.Network;\nimport android.net.NetworkCapabilities;\nimport android.os.Build;\nimport android.telephony.TelephonyManager;\nimport android.view.View;\n\nimport androidx.core.content.ContextCompat;\n\nimport com.google.android.gms.common.GoogleApiAvailability;\nimport com.google.firebase.crashlytics.CustomKeysAndValues;\nimport com.google.firebase.crashlytics.FirebaseCrashlytics;\n\nimport static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;\n\n/**\n * The following are samples of custom keys that may be useful to record via Crashlytics prior to a Crash.\n * <p>\n * These utility methods are not meant to be comprehensive, but they are illustrative of the types of things you\n * could track using custom keys in Crashlytics.\n */\npublic class CustomKeySamples {\n\n\n    private final Context context;\n\n    // Lazily instantiated when a listener is set up.\n    private ConnectivityManager.NetworkCallback callback = null;\n\n    public CustomKeySamples(Context context) {\n        this.context = context;\n    }\n\n    /**\n     * Set a subset of custom keys simultaneously.\n     */\n    public void setSampleCustomKeys() {\n        FirebaseCrashlytics.getInstance().setCustomKeys(new CustomKeysAndValues.Builder()\n                .putString(\"Locale\", getLocale())\n                .putFloat(\"Screen Density\", getDensity())\n                .putString(\"Google Play Services Availability\", getGooglePlayServicesAvailability())\n                .putString(\"Os Version\", getOsVersion())\n                .putString(\"Install Source\", getInstallSource())\n                .putString(\"Preferred ABI\", getPreferredAbi()).build());\n    }\n\n    /**\n     * Update network state and add a hook to update network state going forward.\n     *\n     * Note: This code is executed above API level N.\n     */\n    public void updateAndTrackNetworkState() {\n        ConnectivityManager connectivityManager = (ConnectivityManager) context\n                .getSystemService(Context.CONNECTIVITY_SERVICE);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            NetworkCapabilities networkCapabilities = connectivityManager\n                    .getNetworkCapabilities(connectivityManager.getActiveNetwork());\n            if (networkCapabilities != null) {\n                updateNetworkCapabilityCustomKeys(networkCapabilities);\n            }\n            synchronized(this) {\n                if (callback == null) {\n                    // Set up a callback to match our best-practices around custom keys being up-to-date\n                    callback = new ConnectivityManager.NetworkCallback() {\n                        @Override\n                        public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {\n                            updateNetworkCapabilityCustomKeys(networkCapabilities);\n                        }\n                    };\n                    connectivityManager.registerDefaultNetworkCallback(callback);\n                }\n            }\n        }\n    }\n\n    /**\n     * Remove the handler for the network state.\n     *\n     * Note: This code is executed above API level N.\n     */\n    public void stopTrackingNetworkState() {\n        ConnectivityManager connectivityManager = (ConnectivityManager) context\n                .getSystemService(Context.CONNECTIVITY_SERVICE);\n        synchronized(this) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && callback != null) {\n                connectivityManager.unregisterNetworkCallback(callback);\n                callback = null;\n            }\n        }\n    }\n\n    private void updateNetworkCapabilityCustomKeys(NetworkCapabilities networkCapabilities) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            FirebaseCrashlytics.getInstance().setCustomKeys(new CustomKeysAndValues.Builder()\n                    .putInt(\"Network Bandwidth\", networkCapabilities.getLinkDownstreamBandwidthKbps())\n                    .putInt(\"Network Upstream\", networkCapabilities.getLinkUpstreamBandwidthKbps())\n                    .putBoolean(\"Network Metered\", networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED))\n                    // This key is long and not as easy to filter by.\n                    .putString(\"Network Capabilities\", networkCapabilities.toString()).build());\n        }\n    }\n\n    /**\n     * @see {@link com.google.samples.quickstart.crash.java.CustomKeySamples#updateAndTrackNetworkState},\n     * which does not require READ_PHONE_STATE\n     * and returns more useful information about bandwidth, metering, and capabilities.\n     *\n     * Supressed deprecation warning because that code path is only used below API Level N.\n     */\n    @Deprecated\n    @SuppressLint(\"MissingPermission\")\n    public void addPhoneStateRequiredNetworkKeys() {\n        TelephonyManager telephonyManager = ((TelephonyManager) context\n                .getSystemService(Context.TELEPHONY_SERVICE));\n\n        if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) !=\n                PackageManager.PERMISSION_GRANTED) {\n            return;\n        }\n\n        int networkType;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            networkType = telephonyManager.getDataNetworkType();\n        } else {\n            networkType = telephonyManager.getNetworkType();\n        }\n        String simOperatorId = telephonyManager.getSimOperator();\n        FirebaseCrashlytics.getInstance().setCustomKey(\"Network Type\", networkType);\n        FirebaseCrashlytics.getInstance().setCustomKey(\"Sim Operator\", simOperatorId);\n    }\n\n    /**\n     * Retrieve the locale information for the app.\n     *\n     * Supressed deprecation warning because that code path is only used below API Level N.\n     */\n    @SuppressWarnings(\"deprecation\")\n    public String getLocale() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            return context\n                    .getResources()\n                    .getConfiguration()\n                    .getLocales().get(0).toString();\n        }\n\n        return context\n                .getResources()\n                .getConfiguration().locale.toString();\n    }\n\n    /**\n     * Retrieve the screen density information for the app.\n     */\n    public float getDensity() {\n        return context\n                .getResources()\n                .getDisplayMetrics()\n                .density;\n    }\n\n    /**\n     * Retrieve the locale information for the app.\n     */\n    public String getGooglePlayServicesAvailability() {\n        return GoogleApiAvailability\n                .getInstance()\n                .isGooglePlayServicesAvailable(context) == 0 ? \"Unavailable\" : \"Available\";\n    }\n\n    /**\n     * Return the underlying kernel version of the Android device.\n     */\n    public String getOsVersion() {\n        String osVersion = System.getProperty(\"os.version\");\n        return osVersion != null ? osVersion : \"Unknown\";\n    }\n\n    /**\n     * Retrieve the preferred ABI of the device. Some devices can support\n     * multiple ABIs and the first one returned in the preferred one.\n     *\n     * Supressed deprecation warning because that code path is only used below Lollipop.\n     */\n    @SuppressWarnings(\"deprecation\")\n    public String getPreferredAbi() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            return Build.SUPPORTED_ABIS[0];\n        }\n\n        return Build.CPU_ABI;\n    }\n\n    /**\n     * Retrieve the install source and return it as a string.\n     *\n     * Supressed deprecation warning because that code path is only used below API level R.\n     */\n    public String getInstallSource() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            try {\n                InstallSourceInfo info = context\n                        .getPackageManager()\n                        .getInstallSourceInfo(context.getPackageName());\n\n                String originating = info.getOriginatingPackageName() == null ? \"None\" :\n                        info.getOriginatingPackageName();\n                String installing = info.getInstallingPackageName() == null ? \"None\" : info.getInstallingPackageName();\n                String initiating = info.getInitiatingPackageName() == null ? \"None\" : info.getInitiatingPackageName();\n\n                // This returns all three of the install source, originating source, and initiating\n                // source.\n                return \"Originating: \" + originating +\n                        \", Installing: \" + installing +\n                        \", Initiating: \" + initiating;\n            } catch (PackageManager.NameNotFoundException e) {\n                return \"Unknown\";\n            }\n        }\n\n        String installerPackageName = context\n                .getPackageManager()\n                .getInstallerPackageName(context.getPackageName());\n\n        return installerPackageName == null ? \"None\" : installerPackageName;\n    }\n\n    /**\n     * Add a focus listener that updates a custom key when this view gains the focus.\n     **/\n    public void focusListener(View view, boolean hasFocus) {\n        if (hasFocus) {\n            FirebaseCrashlytics.getInstance().setCustomKey(\"view_focus\", view.getId());\n        }\n    }\n}\n"
  },
  {
    "path": "crash/app/src/main/java/com/google/samples/quickstart/crash/java/MainActivity.java",
    "content": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.samples.quickstart.crash.java;\n\nimport android.os.Bundle;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.View;\n\nimport com.google.firebase.crashlytics.FirebaseCrashlytics;\nimport com.google.samples.quickstart.crash.databinding.ActivityMainBinding;\n\n/**\n * This Activity shows the different ways of reporting application crashes.\n * - Report non-fatal exceptions that are caught by your app.\n * - Automatically Report uncaught crashes.\n *\n * It also shows how to add log messages to crash reports using log().\n *\n * Check https://console.firebase.google.com to view and analyze your crash reports.\n *\n * Check https://firebase.google.com/docs/crashlytics for more information on Firebase Crashlytics.\n */\npublic class MainActivity extends AppCompatActivity {\n\n    private static final String TAG = \"MainActivity\";\n    private FirebaseCrashlytics mCrashlytics;\n    private CustomKeySamples samples;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        final ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        this.samples = new CustomKeySamples(this.getApplicationContext());\n        samples.setSampleCustomKeys();\n        samples.updateAndTrackNetworkState();\n\n        mCrashlytics = FirebaseCrashlytics.getInstance();\n\n        // Log the onCreate event, this will also be printed in logcat\n        mCrashlytics.log(\"onCreate\");\n\n        // Add some custom values and identifiers to be included in crash reports\n        mCrashlytics.setCustomKey(\"MeaningOfLife\", 42);\n        mCrashlytics.setCustomKey(\"LastUIAction\", \"Test value\");\n        mCrashlytics.setUserId(\"123456789\");\n\n        // Report a non-fatal exception, for demonstration purposes\n        mCrashlytics.recordException(new Exception(\"Non-fatal exception: something went wrong!\"));\n\n        // Button that causes NullPointerException to be thrown.\n        binding.crashButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                // Log that crash button was clicked.\n                mCrashlytics.log(\"Crash button clicked.\");\n\n                // If catchCrashCheckBox is checked catch the exception and report it using\n                // logException(), Otherwise throw the exception and let Crashlytics automatically\n                // report the crash.\n                if (binding.catchCrashCheckBox.isChecked()) {\n                    try {\n                        throw new NullPointerException();\n                    } catch (NullPointerException ex) {\n                        // [START crashlytics_log_and_report]\n                        mCrashlytics.log(\"NPE caught!\");\n                        mCrashlytics.recordException(ex);\n                        // [END crashlytics_log_and_report]\n                    }\n                } else {\n                    throw new NullPointerException();\n                }\n            }\n        });\n\n        // Log that the Activity was created.\n        // [START crashlytics_log_event]\n        mCrashlytics.log(\"Activity created\");\n        // [END crashlytics_log_event]\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        samples.stopTrackingNetworkState();\n    }\n}\n"
  },
  {
    "path": "crash/app/src/main/java/com/google/samples/quickstart/crash/kotlin/CustomKeySamples.kt",
    "content": "package com.google.samples.quickstart.crash.kotlin\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.net.ConnectivityManager\nimport android.net.ConnectivityManager.NetworkCallback\nimport android.net.Network\nimport android.net.NetworkCapabilities\nimport android.os.Build\nimport android.telephony.TelephonyManager\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport com.google.android.gms.common.GoogleApiAvailability\nimport com.google.firebase.crashlytics.crashlytics\nimport com.google.firebase.crashlytics.setCustomKeys\nimport com.google.firebase.Firebase\nimport kotlinx.coroutines.internal.synchronized\n\n/**\n * The following are samples of custom keys that may be useful to record via Crashlytics prior to a Crash.\n *\n * These utility methods are not meant to be comprehensive, but they are illustrative of the types of things you\n * could track using custom keys in Crashlytics.\n */\nclass CustomKeySamples(private val context: Context, private var callback: NetworkCallback? = null) {\n\n    /**\n     * Set a subset of custom keys simultaneously.\n     */\n    fun setSampleCustomKeys() {\n        Firebase.crashlytics.setCustomKeys {\n            key(\"Locale\", locale)\n            key(\"Screen Density\", density)\n            key(\"Google Play Services Availability\", googlePlayServicesAvailability)\n            key(\"Os Version\", osVersion)\n            key(\"Install Source\", installSource)\n            key(\"Preferred ABI\", preferredAbi)\n        }\n    }\n\n    /**\n     * Update network state and add a hook to update network state going forward.\n     *\n     * Note: This code is executed above API level N.\n     */\n    fun updateAndTrackNetworkState() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager\n            connectivityManager\n                .getNetworkCapabilities(connectivityManager.activeNetwork)\n                ?.let { updateNetworkCapabilityCustomKeys(it) }\n\n            kotlin.synchronized(this) {\n                if (callback == null) {\n                    // Set up a callback to match our best-practices around custom keys being up-to-date\n                    val newCallback: NetworkCallback = object : NetworkCallback() {\n                        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {\n                            updateNetworkCapabilityCustomKeys(networkCapabilities)\n                        }\n                    }\n                    callback = newCallback\n                    connectivityManager.registerDefaultNetworkCallback(newCallback)\n                }\n            }\n        }\n    }\n\n    /**\n     * Remove the handler for the network state.\n     *\n     * Note: This code is executed above API level N.\n     */\n    fun stopTrackingNetworkState() {\n        val connectivityManager = context\n            .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager\n        val oldCallback = callback\n        kotlin.synchronized(this) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && oldCallback != null) {\n                connectivityManager.unregisterNetworkCallback(oldCallback)\n                callback = null\n            }\n        }\n    }\n\n    private fun updateNetworkCapabilityCustomKeys(networkCapabilities: NetworkCapabilities) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            Firebase.crashlytics.setCustomKeys {\n                key(\"Network Bandwidth\", networkCapabilities.linkDownstreamBandwidthKbps)\n                key(\"Network Upstream\", networkCapabilities.linkUpstreamBandwidthKbps)\n                key(\n                    \"Network Metered\",\n                    networkCapabilities\n                        .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED),\n                )\n                // This key is long and not as easy to filter by.\n                key(\"Network Capabilities\", networkCapabilities.toString())\n            }\n        }\n    }\n\n    /**\n     * @see {@link com.google.samples.quickstart.crash.java.CustomKeySamples.updateAndTrackNetworkState}, which does not require READ_PHONE_STATE\n     * and returns more useful information about bandwidth, metering, and capabilities.\n     *\n     * Supressed deprecation warning because that code path is only used below API Level N.\n     *\n     * Supressed Lint warning because READ_PHONE_STATE is a high priority permission and\n     * we don't want to enforce needing it for this code example.\n     */\n    @Suppress(\"DEPRECATION\")\n    @Deprecated(\"Prefer updateAndTrackNetworkState, which does not require READ_PHONE_STATE\")\n    @SuppressLint(\"MissingPermission\")\n    fun addPhoneStateRequiredNetworkKeys() {\n        val telephonyManager = context\n            .getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager\n        if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)\n            != PackageManager.PERMISSION_GRANTED\n        ) {\n            return\n        }\n\n        val networkType: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            telephonyManager.dataNetworkType\n        } else {\n            telephonyManager.networkType\n        }\n\n        Firebase.crashlytics.setCustomKeys {\n            key(\"Network Type\", networkType)\n            key(\"Sim Operator\", telephonyManager.simOperator)\n        }\n    }\n\n    /**\n     * Retrieve the locale information for the app.\n     *\n     * Supressed deprecation warning because that code path is only used below API Level N.\n     */\n    @Suppress(\"DEPRECATION\")\n    val locale: String\n        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            context\n                .resources\n                .configuration\n                .locales[0].toString()\n        } else {\n            context\n                .resources\n                .configuration.locale.toString()\n        }\n\n    /**\n     * Retrieve the screen density information for the app.\n     */\n    val density: Float\n        get() = context\n            .resources\n            .displayMetrics.density\n\n    /**\n     * Retrieve the locale information for the app.\n     */\n    val googlePlayServicesAvailability: String\n        get() = if (GoogleApiAvailability\n                .getInstance()\n                .isGooglePlayServicesAvailable(context) == 0\n        ) {\n            \"Unavailable\"\n        } else {\n            \"Available\"\n        }\n\n    /**\n     * Return the underlying kernel version of the Android device.\n     */\n    val osVersion: String\n        get() = System.getProperty(\"os.version\") ?: \"Unknown\"\n\n    /**\n     * Retrieve the preferred ABI of the device. Some devices can support\n     * multiple ABIs and the first one returned in the preferred one.\n     *\n     * Supressed deprecation warning because that code path is only used below Lollipop.\n     */\n    @Suppress(\"DEPRECATION\")\n    val preferredAbi: String\n        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            Build.SUPPORTED_ABIS[0]\n        } else Build.CPU_ABI\n\n    /**\n     * Retrieve the install source and return it as a string.\n     *\n     * Supressed deprecation warning because that code path is only used below API level R.\n     */\n    @Suppress(\"DEPRECATION\")\n    val installSource: String\n        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            try {\n                val info = context\n                    .packageManager\n                    .getInstallSourceInfo(context.packageName)\n\n                // This returns all three of the install source, originating source, and initiating\n                // source.\n                \"Originating: ${info.originatingPackageName ?: \"None\"}, \" +\n                    \"Installing: ${info.installingPackageName ?: \"None\"}, \" +\n                    \"Initiating: ${info.initiatingPackageName ?: \"None\"}\"\n            } catch (e: PackageManager.NameNotFoundException) {\n                \"Unknown\"\n            }\n        } else {\n            context.packageManager.getInstallerPackageName(context.packageName) ?: \"None\"\n        }\n\n    /**\n     * Add a focus listener that updates a custom key when this view gains the focus.\n     */\n    fun focusListener(view: View, hasFocus: Boolean) {\n        if (hasFocus) {\n            Firebase.crashlytics.setCustomKey(\"view_focus\", view.id)\n        }\n    }\n}\n"
  },
  {
    "path": "crash/app/src/main/java/com/google/samples/quickstart/crash/kotlin/MainActivity.kt",
    "content": "package com.google.samples.quickstart.crash.kotlin\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport com.google.firebase.crashlytics.FirebaseCrashlytics\nimport com.google.firebase.crashlytics.crashlytics\nimport com.google.firebase.crashlytics.setCustomKeys\nimport com.google.firebase.Firebase\nimport com.google.samples.quickstart.crash.databinding.ActivityMainBinding\n\n/**\n * This Activity shows the different ways of reporting application crashes.\n * - Report non-fatal exceptions that are caught by your app.\n * - Automatically Report uncaught crashes.\n *\n * It also shows how to add log messages to crash reports using log().\n *\n * Check https://console.firebase.google.com to view and analyze your crash reports.\n *\n * Check https://firebase.google.com/docs/crashlytics for more information on Firebase Crashlytics.\n */\nclass MainActivity : AppCompatActivity() {\n\n    private lateinit var crashlytics: FirebaseCrashlytics\n    private lateinit var customKeySamples: CustomKeySamples\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        customKeySamples = CustomKeySamples(applicationContext)\n        customKeySamples.setSampleCustomKeys()\n        customKeySamples.updateAndTrackNetworkState()\n\n        crashlytics = Firebase.crashlytics\n\n        // Log the onCreate event, this will also be printed in logcat\n        crashlytics.log(\"onCreate\")\n\n        // Add some custom values and identifiers to be included in crash reports\n        crashlytics.setCustomKeys {\n            key(\"MeaningOfLife\", 42)\n            key(\"LastUIAction\", \"Test value\")\n        }\n        crashlytics.setUserId(\"123456789\")\n\n        // Report a non-fatal exception, for demonstration purposes\n        crashlytics.recordException(Exception(\"Non-fatal exception: something went wrong!\"))\n\n        // Button that causes NullPointerException to be thrown.\n        binding.crashButton.setOnClickListener {\n            // Log that crash button was clicked.\n            crashlytics.log(\"Crash button clicked.\")\n\n            // If catchCrashCheckBox is checked catch the exception and report it using\n            // logException(), Otherwise throw the exception and let Crashlytics automatically\n            // report the crash.\n            if (binding.catchCrashCheckBox.isChecked) {\n                try {\n                    throw NullPointerException()\n                } catch (ex: NullPointerException) {\n                    // [START crashlytics_log_and_report]\n                    crashlytics.log(\"NPE caught!\")\n                    crashlytics.recordException(ex)\n                    // [END crashlytics_log_and_report]\n                }\n            } else {\n                throw NullPointerException()\n            }\n        }\n\n        // Log that the Activity was created.\n        // [START crashlytics_log_event]\n        crashlytics.log(\"Activity created\")\n        // [END crashlytics_log_event]\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        customKeySamples.stopTrackingNetworkState()\n    }\n\n    companion object {\n        private const val TAG = \"MainActivity\"\n    }\n}\n"
  },
  {
    "path": "crash/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\nCopyright Google Inc. All rights reserved.\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n      http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    tools:context=\"com.google.samples.quickstart.crash.java.MainActivity\">\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <Button\n        android:id=\"@+id/crashButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/crash_button_label\"\n        android:layout_marginTop=\"16dp\"\n        app:layout_constraintEnd_toEndOf=\"@+id/icon\"\n        app:layout_constraintStart_toStartOf=\"@+id/icon\"\n        app:layout_constraintTop_toBottomOf=\"@+id/icon\" />\n\n    <CheckBox\n        android:id=\"@+id/catchCrashCheckBox\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/catch_crash_checkbox_label\"\n        android:layout_marginTop=\"10dp\"\n        app:layout_constraintEnd_toEndOf=\"@+id/crashButton\"\n        app:layout_constraintStart_toStartOf=\"@+id/crashButton\"\n        app:layout_constraintTop_toBottomOf=\"@+id/crashButton\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "crash/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n</resources>\n"
  },
  {
    "path": "crash/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "crash/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase Crashlytics</string>\n    <string name=\"crash_button_label\">Cause Crash</string>\n    <string name=\"catch_crash_checkbox_label\">Catch Crash</string>\n</resources>\n"
  },
  {
    "path": "crash/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "crash/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "crash/app/test-proguard-rules.pro",
    "content": "-dontobfuscate\n-dontwarn\n\n-dontwarn org.xmlpull.v1.**\n-dontnote org.xmlpull.v1.**\n-keep class org.xmlpull.** { *; }\n-keepclassmembers class org.xmlpull.** { *; }\n"
  },
  {
    "path": "crash/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n    alias(libs.plugins.firebase.crashlytics) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "crash/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "crash/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "crash/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "crash/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "crash/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "database/.gitignore",
    "content": ".gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n"
  },
  {
    "path": "database/README.md",
    "content": "Firebase Database Quickstart\n=============================\n\nIntroduction\n------------\n\n- [Read more about Firebase Database](https://firebase.google.com/docs/database)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n- Log in to the [Firebase Console](https://console.firebase.google.com).\n- Go to **Auth** tab and enable **Email/Password** authentication.\n- Run the sample on Android device or emulator.\n\nResult\n-----------\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\n\nData Model\n-----------\nThis quickstart demonstrates a simple data model for a social application.\nWhile this data model uses some of the Firebase best practices, it has some\nknown tradeoffs made for simplicity that would not scale to very large numbers\nof users.\n\nThe database has four \"root\" nodes:\n\n  * `users` - a list of `User` objects, keyed by user ID. So\n    `/users/<ID>/email` is the email address of the user with id=`<ID>`.\n  * `posts` - a list of `Post` objects, keyed by randomly generated push ID.\n    Each `Post` contains the `uid` and `author` properties to determine the\n    identity of the author without a JOIN-style query.\n    * Posts contain a `stars` property which is a `Map` of user IDs to boolean\n      values.  If `/posts/<POST-ID>/stars/<USER-ID>` is `true`, this means\n      the user with ID `<USER-ID>` has starred the post with ID `<POST-ID>`.\n      This data nesting makes it easy to tell if a specific user has already\n      starred a specific post, but would not scale to large numbers of stars\n      per post as it would make loading the Post data more expensive.\n  * `user-posts` - a list of posts by the user.  `/user-posts/<USER-ID>` is a list\n     of all posts made by a specific user, keyed by the same push ID used in\n     the `posts` tree. This makes it easy to query \"all posts by a specific\n     user\" without filtering through all Post objects.\n  * `post-comments` - comments on a particular posts, where\n    `/post-comments/<POST-ID>` is a list of all comments on post with id\n    `<POST-ID>`.  Each comment has a randomly generated push key. By keeping\n    this data in its own tree rather than nesting it under `posts`, we make it\n    possible to load a post without loading all comments while still\n    having a known path to access all comments for a particular post.\n\nDatabase Rules\n---------------\nBelow are some samples rules that limit access and validate data:\n\n```javascript\n\n{\n  \"rules\": {\n    // User profiles are only readable/writable by the user who owns it\n    \"users\": {\n      \"$UID\": {\n        \".read\": \"auth.uid == $UID\",\n        \".write\": \"auth.uid == $UID\"\n      }\n    },\n\n    // Posts can be read by anyone but only written by logged-in users.\n    \"posts\": {\n      \".read\": true,\n      \".write\": \"auth.uid != null\",\n\n      \"$POSTID\": {\n        // UID must match logged in user and is fixed once set\n        \"uid\": {\n          \".validate\": \"(data.exists() && data.val() == newData.val()) || newData.val() == auth.uid\"\n        },\n\n        // User can only update own stars\n        \"stars\": {\n          \"$UID\": {\n              \".validate\": \"auth.uid == $UID\"\n          }\n        }\n      }\n    },\n\n    // User posts can be read by anyone but only written by the user that owns it,\n    // and with a matching UID\n    \"user-posts\": {\n      \".read\": true,\n\n      \"$UID\": {\n        \"$POSTID\": {\n          \".write\": \"auth.uid == $UID\",\n        \t\".validate\": \"data.exists() || newData.child('uid').val() == auth.uid\"\n        }\n      }\n    },\n\n\n    // Comments can be read by anyone but only written by a logged in user\n    \"post-comments\": {\n      \".read\": true,\n      \".write\": \"auth.uid != null\",\n\n      \"$POSTID\": {\n        \"$COMMENTID\": {\n          // UID must match logged in user and is fixed once set\n          \"uid\": {\n              \".validate\": \"(data.exists() && data.val() == newData.val()) || newData.val() == auth.uid\"\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-database)\n- [Firebase Support](https://firebase.google.com/support/)\n\n\nLicense\n-------\n\nCopyright 2016 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "database/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "database/app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.services)\n}\n\ntasks {\n    check.dependsOn(\"assembleDebugAndroidTest\")\n}\n\nandroid {\n    namespace = \"com.google.firebase.quickstart.database\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.quickstart.database\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n            signingConfig = signingConfigs.getByName(\"debug\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    implementation(\"androidx.appcompat:appcompat:1.7.1\")\n    implementation(\"androidx.recyclerview:recyclerview:1.4.0\")\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.navigation:navigation-fragment-ktx:2.9.6\")\n    implementation(\"androidx.navigation:navigation-ui-ktx:2.9.6\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Firebase Realtime Database\n    implementation(\"com.google.firebase:firebase-database\")\n\n    // Firebase Authentication\n    implementation(\"com.google.firebase:firebase-auth\")\n\n    implementation(\"com.firebaseui:firebase-ui-database:9.1.1\")\n\n    // Needed to fix a dependency conflict with FirebaseUI'\n    implementation(\"androidx.arch.core:core-runtime:2.2.0\")\n\n    testImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n}\n"
  },
  {
    "path": "database/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes Signature\n-keepattributes *Annotation*\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n\n-keep class com.google.firebase.quickstart.database.java.viewholder.** {\n    *;\n}\n\n-keepclassmembers class com.google.firebase.quickstart.database.java.models.** {\n    *;\n}\n\n-keepclassmembers class com.google.firebase.quickstart.database.kotlin.models.** {\n    *;\n}\n"
  },
  {
    "path": "database/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity android:name=\".EntryChoiceActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".java.MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme.NoActionBar\" />\n\n        <activity\n            android:name=\".kotlin.MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme.NoActionBar\" />\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.quickstart.database\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Realtime Database quickstart written in Java.\",\n                Intent(this, com.google.firebase.quickstart.database.java.MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Realtime Database quickstart written in Kotlin.\",\n                Intent(this, com.google.firebase.quickstart.database.kotlin.MainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/BaseFragment.java",
    "content": "package com.google.firebase.quickstart.database.java;\n\nimport android.view.View;\nimport android.widget.ProgressBar;\n\nimport androidx.fragment.app.Fragment;\n\nimport com.google.firebase.auth.FirebaseAuth;\n\npublic class BaseFragment extends Fragment {\n    private ProgressBar mProgressBar;\n\n    public void setProgressBar(int resId) {\n        mProgressBar = getView().findViewById(resId);\n    }\n\n    public void showProgressBar() {\n        if (mProgressBar != null) {\n            mProgressBar.setVisibility(View.VISIBLE);\n        }\n    }\n\n    public void hideProgressBar() {\n        if (mProgressBar != null) {\n            mProgressBar.setVisibility(View.INVISIBLE);\n        }\n    }\n\n    public String getUid() {\n        return FirebaseAuth.getInstance().getCurrentUser().getUid();\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/MainActivity.java",
    "content": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.database.java;\n\nimport android.os.Bundle;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.navigation.NavController;\nimport androidx.navigation.NavDestination;\nimport androidx.navigation.Navigation;\n\nimport com.google.android.material.floatingactionbutton.FloatingActionButton;\nimport com.google.firebase.quickstart.database.R;\nimport com.google.firebase.quickstart.database.databinding.ActivityMainBinding;\n\npublic class  MainActivity extends AppCompatActivity {\n\n    private FloatingActionButton fab;\n    private NavController navController;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        setSupportActionBar(binding.toolbar);\n\n        fab = binding.fab;\n\n        navController = Navigation.findNavController(this, R.id.nav_host_fragment);\n        navController.setGraph(R.navigation.nav_graph_java);\n        navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {\n            @Override\n            public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {\n                if (destination.getId() == R.id.MainFragment) {\n                    fab.setVisibility(View.VISIBLE);\n                    fab.setOnClickListener(new View.OnClickListener() {\n                        @Override\n                        public void onClick(View view) {\n                            navController.navigate(R.id.action_MainFragment_to_NewPostFragment);\n                        }\n                    });\n                } else {\n                    fab.setVisibility(View.GONE);\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/MainFragment.java",
    "content": "package com.google.firebase.quickstart.database.java;\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.view.MenuHost;\nimport androidx.core.view.MenuProvider;\nimport androidx.fragment.app.Fragment;\nimport androidx.navigation.fragment.NavHostFragment;\nimport androidx.viewpager2.adapter.FragmentStateAdapter;\n\nimport com.google.android.material.tabs.TabLayoutMediator;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.quickstart.database.R;\nimport com.google.firebase.quickstart.database.databinding.FragmentMainBinding;\nimport com.google.firebase.quickstart.database.java.listfragments.MyPostsFragment;\nimport com.google.firebase.quickstart.database.java.listfragments.MyTopPostsFragment;\nimport com.google.firebase.quickstart.database.java.listfragments.RecentPostsFragment;\n\npublic class MainFragment extends Fragment implements MenuProvider {\n\n    private FragmentMainBinding binding;\n\n    private MenuHost menuHost;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        binding = FragmentMainBinding.inflate(inflater, container, false);\n        return binding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        // MenuProvider\n        menuHost = requireActivity();\n        menuHost.addMenuProvider(this);\n\n        // Create the adapter that will return a fragment for each section\n        FragmentStateAdapter mPagerAdapter = new FragmentStateAdapter(getParentFragmentManager(),\n                getViewLifecycleOwner().getLifecycle()) {\n            private final Fragment[] mFragments = new Fragment[]{\n                    new RecentPostsFragment(),\n                    new MyPostsFragment(),\n                    new MyTopPostsFragment(),\n            };\n\n            @NonNull\n            @Override\n            public Fragment createFragment(int position) {\n                return mFragments[position];\n            }\n\n            @Override\n            public int getItemCount() {\n                return mFragments.length;\n            }\n        };\n        // Set up the ViewPager with the sections adapter.\n        binding.container.setAdapter(mPagerAdapter);\n        String[] mFragmentNames = new String[]{\n                getString(R.string.heading_recent),\n                getString(R.string.heading_my_posts),\n                getString(R.string.heading_my_top_posts)\n        };\n        new TabLayoutMediator(binding.tabs, binding.container,\n                (tab, position) -> tab.setText(mFragmentNames[position])\n        ).attach();\n    }\n\n    @Override\n    public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {\n        menuInflater.inflate(R.menu.menu_main, menu);\n    }\n\n    @Override\n    public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {\n        int i = menuItem.getItemId();\n        if (i == R.id.action_logout) {\n            FirebaseAuth.getInstance().signOut();\n            NavHostFragment.findNavController(this)\n                    .navigate(R.id.action_MainFragment_to_SignInFragment);\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/NewPostFragment.java",
    "content": "package com.google.firebase.quickstart.database.java;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.firebase.database.DataSnapshot;\nimport com.google.firebase.database.DatabaseError;\nimport com.google.firebase.database.DatabaseReference;\nimport com.google.firebase.database.FirebaseDatabase;\nimport com.google.firebase.database.ValueEventListener;\nimport com.google.firebase.quickstart.database.R;\nimport com.google.firebase.quickstart.database.databinding.FragmentNewPostBinding;\nimport com.google.firebase.quickstart.database.java.models.Post;\nimport com.google.firebase.quickstart.database.java.models.User;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class NewPostFragment extends BaseFragment {\n    private static final String TAG = \"NewPostFragment\";\n    private static final String REQUIRED = \"Required\";\n\n    private DatabaseReference mDatabase;\n\n    private FragmentNewPostBinding binding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        binding = FragmentNewPostBinding.inflate(inflater, container, false);\n        return binding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mDatabase = FirebaseDatabase.getInstance().getReference();\n\n        binding.fabSubmitPost.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                submitPost();\n            }\n        });\n    }\n\n    private void submitPost() {\n        final String title = binding.fieldTitle.getText().toString();\n        final String body = binding.fieldBody.getText().toString();\n\n        // Title is required\n        if (TextUtils.isEmpty(title)) {\n            binding.fieldTitle.setError(REQUIRED);\n            return;\n        }\n\n        // Body is required\n        if (TextUtils.isEmpty(body)) {\n            binding.fieldBody.setError(REQUIRED);\n            return;\n        }\n\n        // Disable button so there are no multi-posts\n        setEditingEnabled(false);\n        Toast.makeText(getContext(), \"Posting...\", Toast.LENGTH_SHORT).show();\n\n        final String userId = getUid();\n        mDatabase.child(\"users\").child(userId).addListenerForSingleValueEvent(\n                new ValueEventListener() {\n                    @Override\n                    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {\n                        // Get user value\n                        User user = dataSnapshot.getValue(User.class);\n\n                        if (user == null) {\n                            // User is null, error out\n                            Log.e(TAG, \"User \" + userId + \" is unexpectedly null\");\n                            Toast.makeText(getContext(),\n                                    \"Error: could not fetch user.\",\n                                    Toast.LENGTH_SHORT).show();\n                        } else {\n                            // Write new post\n                            writeNewPost(userId, user.username, title, body);\n                        }\n\n                        setEditingEnabled(true);\n                        NavHostFragment.findNavController(NewPostFragment.this)\n                                .navigate(R.id.action_NewPostFragment_to_MainFragment);\n                    }\n\n                    @Override\n                    public void onCancelled(@NonNull DatabaseError databaseError) {\n                        Log.w(TAG, \"getUser:onCancelled\", databaseError.toException());\n                        setEditingEnabled(true);\n                    }\n                });\n    }\n\n    private void setEditingEnabled(boolean enabled) {\n        binding.fieldTitle.setEnabled(enabled);\n        binding.fieldBody.setEnabled(enabled);\n        if (enabled) {\n            binding.fabSubmitPost.show();\n        } else {\n            binding.fabSubmitPost.hide();\n        }\n    }\n\n    private void writeNewPost(String userId, String username, String title, String body) {\n        // Create new post at /user-posts/$userid/$postid and at\n        // /posts/$postid simultaneously\n        String key = mDatabase.child(\"posts\").push().getKey();\n        Post post = new Post(userId, username, title, body);\n        Map<String, Object> postValues = post.toMap();\n\n        Map<String, Object> childUpdates = new HashMap<>();\n        childUpdates.put(\"/posts/\" + key, postValues);\n        childUpdates.put(\"/user-posts/\" + userId + \"/\" + key, postValues);\n\n        mDatabase.updateChildren(childUpdates);\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/PostDetailFragment.java",
    "content": "package com.google.firebase.quickstart.database.java;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.google.firebase.database.ChildEventListener;\nimport com.google.firebase.database.DataSnapshot;\nimport com.google.firebase.database.DatabaseError;\nimport com.google.firebase.database.DatabaseReference;\nimport com.google.firebase.database.FirebaseDatabase;\nimport com.google.firebase.database.ValueEventListener;\nimport com.google.firebase.quickstart.database.R;\nimport com.google.firebase.quickstart.database.databinding.FragmentPostDetailBinding;\nimport com.google.firebase.quickstart.database.java.models.Comment;\nimport com.google.firebase.quickstart.database.java.models.Post;\nimport com.google.firebase.quickstart.database.java.models.User;\nimport com.google.firebase.quickstart.database.java.viewholder.CommentViewHolder;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class PostDetailFragment extends BaseFragment {\n\n    private static final String TAG = \"PostDetailFragment\";\n\n    public static final String EXTRA_POST_KEY = \"post_key\";\n\n    private DatabaseReference mPostReference;\n    private DatabaseReference mCommentsReference;\n    private ValueEventListener mPostListener;\n    private String mPostKey;\n    private CommentAdapter mAdapter;\n\n    private FragmentPostDetailBinding binding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        binding = FragmentPostDetailBinding.inflate(inflater, container, false);\n        return binding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        // Get post key from arguments\n        mPostKey = requireArguments().getString(EXTRA_POST_KEY);\n        if (mPostKey == null) {\n            throw new IllegalArgumentException(\"Must pass EXTRA_POST_KEY\");\n        }\n\n        // Initialize Database\n        mPostReference = FirebaseDatabase.getInstance().getReference()\n                .child(\"posts\").child(mPostKey);\n        mCommentsReference = FirebaseDatabase.getInstance().getReference()\n                .child(\"post-comments\").child(mPostKey);\n\n        binding.buttonPostComment.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                postComment();\n            }\n        });\n        binding.recyclerPostComments.setLayoutManager(new LinearLayoutManager(getContext()));\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n\n        // Add value event listener to the post\n        ValueEventListener postListener = new ValueEventListener() {\n            @Override\n            public void onDataChange(DataSnapshot dataSnapshot) {\n                // Get Post object and use the values to update the UI\n                Post post = dataSnapshot.getValue(Post.class);\n                binding.postAuthorLayout.postAuthor.setText(post.author);\n                binding.postTextLayout.postTitle.setText(post.title);\n                binding.postTextLayout.postBody.setText(post.body);\n            }\n\n            @Override\n            public void onCancelled(DatabaseError databaseError) {\n                // Getting Post failed, log a message\n                Log.w(TAG, \"loadPost:onCancelled\", databaseError.toException());\n                Toast.makeText(getContext(), \"Failed to load post.\",\n                        Toast.LENGTH_SHORT).show();\n            }\n        };\n        mPostReference.addValueEventListener(postListener);\n\n        // Keep copy of post listener so we can remove it when app stops\n        mPostListener = postListener;\n\n        // Listen for comments\n        mAdapter = new CommentAdapter(getContext(), mCommentsReference);\n        binding.recyclerPostComments.setAdapter(mAdapter);\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n\n        // Remove post value event listener\n        if (mPostListener != null) {\n            mPostReference.removeEventListener(mPostListener);\n        }\n\n        // Clean up comments listener\n        mAdapter.cleanupListener();\n    }\n\n    private void postComment() {\n        final String uid = getUid();\n        FirebaseDatabase.getInstance().getReference().child(\"users\").child(uid)\n                .addListenerForSingleValueEvent(new ValueEventListener() {\n                    @Override\n                    public void onDataChange(DataSnapshot dataSnapshot) {\n                        // Get user information\n                        User user = dataSnapshot.getValue(User.class);\n                        String authorName = user.username;\n\n                        // Create new comment object\n                        String commentText = binding.fieldCommentText.getText().toString();\n                        Comment comment = new Comment(uid, authorName, commentText);\n\n                        // Push the comment, it will appear in the list\n                        mCommentsReference.push().setValue(comment);\n\n                        // Clear the field\n                        binding.fieldCommentText.setText(null);\n                    }\n\n                    @Override\n                    public void onCancelled(DatabaseError databaseError) {\n\n                    }\n                });\n    }\n\n    private static class CommentAdapter extends RecyclerView.Adapter<CommentViewHolder> {\n\n        private Context mContext;\n        private DatabaseReference mDatabaseReference;\n        private ChildEventListener mChildEventListener;\n\n        private List<String> mCommentIds = new ArrayList<>();\n        private List<Comment> mComments = new ArrayList<>();\n\n        public CommentAdapter(final Context context, DatabaseReference ref) {\n            mContext = context;\n            mDatabaseReference = ref;\n\n            // Create child event listener\n            ChildEventListener childEventListener = new ChildEventListener() {\n                @Override\n                public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {\n                    Log.d(TAG, \"onChildAdded:\" + dataSnapshot.getKey());\n\n                    // A new comment has been added, add it to the displayed list\n                    Comment comment = dataSnapshot.getValue(Comment.class);\n\n                    // Update RecyclerView\n                    mCommentIds.add(dataSnapshot.getKey());\n                    mComments.add(comment);\n                    notifyItemInserted(mComments.size() - 1);\n                }\n\n                @Override\n                public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {\n                    Log.d(TAG, \"onChildChanged:\" + dataSnapshot.getKey());\n\n                    // A comment has changed, use the key to determine if we are displaying this\n                    // comment and if so displayed the changed comment.\n                    Comment newComment = dataSnapshot.getValue(Comment.class);\n                    String commentKey = dataSnapshot.getKey();\n\n                    int commentIndex = mCommentIds.indexOf(commentKey);\n                    if (commentIndex > -1) {\n                        // Replace with the new data\n                        mComments.set(commentIndex, newComment);\n\n                        // Update the RecyclerView\n                        notifyItemChanged(commentIndex);\n                    } else {\n                        Log.w(TAG, \"onChildChanged:unknown_child:\" + commentKey);\n                    }\n                }\n\n                @Override\n                public void onChildRemoved(DataSnapshot dataSnapshot) {\n                    Log.d(TAG, \"onChildRemoved:\" + dataSnapshot.getKey());\n\n                    // A comment has changed, use the key to determine if we are displaying this\n                    // comment and if so remove it.\n                    String commentKey = dataSnapshot.getKey();\n\n                    int commentIndex = mCommentIds.indexOf(commentKey);\n                    if (commentIndex > -1) {\n                        // Remove data from the list\n                        mCommentIds.remove(commentIndex);\n                        mComments.remove(commentIndex);\n\n                        // Update the RecyclerView\n                        notifyItemRemoved(commentIndex);\n                    } else {\n                        Log.w(TAG, \"onChildRemoved:unknown_child:\" + commentKey);\n                    }\n                }\n\n                @Override\n                public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {\n                    Log.d(TAG, \"onChildMoved:\" + dataSnapshot.getKey());\n\n                    // A comment has changed position, use the key to determine if we are\n                    // displaying this comment and if so move it.\n                    Comment movedComment = dataSnapshot.getValue(Comment.class);\n                    String commentKey = dataSnapshot.getKey();\n\n                    // ...\n                }\n\n                @Override\n                public void onCancelled(DatabaseError databaseError) {\n                    Log.w(TAG, \"postComments:onCancelled\", databaseError.toException());\n                    Toast.makeText(mContext, \"Failed to load comments.\",\n                            Toast.LENGTH_SHORT).show();\n                }\n            };\n            ref.addChildEventListener(childEventListener);\n\n            // Store reference to listener so it can be removed on app stop\n            mChildEventListener = childEventListener;\n        }\n\n        @Override\n        public CommentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n            LayoutInflater inflater = LayoutInflater.from(mContext);\n            View view = inflater.inflate(R.layout.item_comment, parent, false);\n            return new CommentViewHolder(view);\n        }\n\n        @Override\n        public void onBindViewHolder(CommentViewHolder holder, int position) {\n            Comment comment = mComments.get(position);\n            holder.authorView.setText(comment.author);\n            holder.bodyView.setText(comment.text);\n        }\n\n        @Override\n        public int getItemCount() {\n            return mComments.size();\n        }\n\n        public void cleanupListener() {\n            if (mChildEventListener != null) {\n                mDatabaseReference.removeEventListener(mChildEventListener);\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/SignInFragment.java",
    "content": "package com.google.firebase.quickstart.database.java;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.navigation.Navigation;\nimport androidx.navigation.fragment.NavHostFragment;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.database.DatabaseReference;\nimport com.google.firebase.database.FirebaseDatabase;\nimport com.google.firebase.quickstart.database.R;\nimport com.google.firebase.quickstart.database.databinding.FragmentSignInBinding;\nimport com.google.firebase.quickstart.database.java.models.User;\n\npublic class SignInFragment extends BaseFragment implements View.OnClickListener {\n\n    private static final String TAG = \"SignInFragment\";\n\n    private DatabaseReference mDatabase;\n    private FirebaseAuth mAuth;\n\n    private FragmentSignInBinding binding;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        binding = FragmentSignInBinding.inflate(inflater, container, false);\n        return binding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mDatabase = FirebaseDatabase.getInstance().getReference();\n        mAuth = FirebaseAuth.getInstance();\n\n        // Views\n        setProgressBar(R.id.progressBar);\n\n        // Click listeners\n        binding.buttonSignIn.setOnClickListener(this);\n        binding.buttonSignUp.setOnClickListener(this);\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n\n        // Check auth on Activity start\n        if (mAuth.getCurrentUser() != null) {\n            onAuthSuccess(mAuth.getCurrentUser());\n        }\n    }\n\n    private void signIn() {\n        Log.d(TAG, \"signIn\");\n        if (!validateForm()) {\n            return;\n        }\n\n        showProgressBar();\n        String email = binding.fieldEmail.getText().toString();\n        String password = binding.fieldPassword.getText().toString();\n\n        mAuth.signInWithEmailAndPassword(email, password)\n                .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        Log.d(TAG, \"signIn:onComplete:\" + task.isSuccessful());\n                        hideProgressBar();\n\n                        if (task.isSuccessful()) {\n                            onAuthSuccess(task.getResult().getUser());\n                        } else {\n                            Toast.makeText(getContext(), \"Sign In Failed\",\n                                    Toast.LENGTH_SHORT).show();\n                        }\n                    }\n                });\n    }\n\n    private void signUp() {\n        Log.d(TAG, \"signUp\");\n        if (!validateForm()) {\n            return;\n        }\n\n        showProgressBar();\n        String email = binding.fieldEmail.getText().toString();\n        String password = binding.fieldPassword.getText().toString();\n\n        mAuth.createUserWithEmailAndPassword(email, password)\n                .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {\n                    @Override\n                    public void onComplete(@NonNull Task<AuthResult> task) {\n                        Log.d(TAG, \"createUser:onComplete:\" + task.isSuccessful());\n                        hideProgressBar();\n\n                        if (task.isSuccessful()) {\n                            onAuthSuccess(task.getResult().getUser());\n                        } else {\n                            Toast.makeText(getContext(), \"Sign Up Failed\",\n                                    Toast.LENGTH_SHORT).show();\n                        }\n                    }\n                });\n    }\n\n    private void onAuthSuccess(FirebaseUser user) {\n        String username = usernameFromEmail(user.getEmail());\n\n        // Write new user\n        writeNewUser(user.getUid(), username, user.getEmail());\n\n        // Go to MainFragment\n        NavHostFragment.findNavController(this).navigate(R.id.action_SignInFragment_to_MainFragment);\n    }\n\n    private String usernameFromEmail(String email) {\n        if (email.contains(\"@\")) {\n            return email.split(\"@\")[0];\n        } else {\n            return email;\n        }\n    }\n\n    private boolean validateForm() {\n        boolean result = true;\n        if (TextUtils.isEmpty(binding.fieldEmail.getText().toString())) {\n            binding.fieldEmail.setError(\"Required\");\n            result = false;\n        } else {\n            binding.fieldEmail.setError(null);\n        }\n\n        if (TextUtils.isEmpty(binding.fieldPassword.getText().toString())) {\n            binding.fieldPassword.setError(\"Required\");\n            result = false;\n        } else {\n            binding.fieldPassword.setError(null);\n        }\n\n        return result;\n    }\n\n    private void writeNewUser(String userId, String name, String email) {\n        User user = new User(name, email);\n\n        mDatabase.child(\"users\").child(userId).setValue(user);\n    }\n\n    public void onClick(View v) {\n        int i = v.getId();\n        if (i == R.id.buttonSignIn) {\n            signIn();\n        } else if (i == R.id.buttonSignUp) {\n            signUp();\n        }\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/listfragments/MyPostsFragment.java",
    "content": "package com.google.firebase.quickstart.database.java.listfragments;\n\nimport com.google.firebase.database.DatabaseReference;\nimport com.google.firebase.database.Query;\n\npublic class MyPostsFragment extends PostListFragment {\n\n    public MyPostsFragment() {}\n\n    @Override\n    public Query getQuery(DatabaseReference databaseReference) {\n        // All my posts\n        return databaseReference.child(\"user-posts\")\n                .child(getUid());\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/listfragments/MyTopPostsFragment.java",
    "content": "package com.google.firebase.quickstart.database.java.listfragments;\n\nimport com.google.firebase.database.DatabaseReference;\nimport com.google.firebase.database.Query;\n\npublic class MyTopPostsFragment extends PostListFragment {\n\n    public MyTopPostsFragment() {}\n\n    @Override\n    public Query getQuery(DatabaseReference databaseReference) {\n        // My top posts by number of stars\n        String myUserId = getUid();\n        Query myTopPostsQuery = databaseReference.child(\"user-posts\").child(myUserId)\n                .orderByChild(\"starCount\");\n\n        return myTopPostsQuery;\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/listfragments/PostListFragment.java",
    "content": "package com.google.firebase.quickstart.database.java.listfragments;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.fragment.app.Fragment;\nimport androidx.navigation.NavController;\nimport androidx.navigation.Navigation;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.firebase.ui.database.FirebaseRecyclerAdapter;\nimport com.firebase.ui.database.FirebaseRecyclerOptions;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.database.DataSnapshot;\nimport com.google.firebase.database.DatabaseError;\nimport com.google.firebase.database.DatabaseReference;\nimport com.google.firebase.database.FirebaseDatabase;\nimport com.google.firebase.database.MutableData;\nimport com.google.firebase.database.Query;\nimport com.google.firebase.database.Transaction;\nimport com.google.firebase.quickstart.database.R;\nimport com.google.firebase.quickstart.database.java.PostDetailFragment;\nimport com.google.firebase.quickstart.database.java.models.Post;\nimport com.google.firebase.quickstart.database.java.viewholder.PostViewHolder;\n\npublic abstract class PostListFragment extends Fragment {\n\n    private static final String TAG = \"PostListFragment\";\n\n    // [START define_database_reference]\n    private DatabaseReference mDatabase;\n    // [END define_database_reference]\n\n    private FirebaseRecyclerAdapter<Post, PostViewHolder> mAdapter;\n    private RecyclerView mRecycler;\n    private LinearLayoutManager mManager;\n\n    public PostListFragment() {}\n\n    @Override\n    public View onCreateView (LayoutInflater inflater, ViewGroup container,\n                              Bundle savedInstanceState) {\n        super.onCreateView(inflater, container, savedInstanceState);\n        View rootView = inflater.inflate(R.layout.fragment_all_posts, container, false);\n\n        // [START create_database_reference]\n        mDatabase = FirebaseDatabase.getInstance().getReference();\n        // [END create_database_reference]\n\n        mRecycler = rootView.findViewById(R.id.messagesList);\n        mRecycler.setHasFixedSize(true);\n\n        return rootView;\n    }\n\n    @Override\n    public void onActivityCreated(Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n\n        // Set up Layout Manager, reverse layout\n        mManager = new LinearLayoutManager(getActivity());\n        mManager.setReverseLayout(true);\n        mManager.setStackFromEnd(true);\n        mRecycler.setLayoutManager(mManager);\n\n        // Set up FirebaseRecyclerAdapter with the Query\n        Query postsQuery = getQuery(mDatabase);\n\n        FirebaseRecyclerOptions options = new FirebaseRecyclerOptions.Builder<Post>()\n                .setQuery(postsQuery, Post.class)\n                .build();\n\n        mAdapter = new FirebaseRecyclerAdapter<Post, PostViewHolder>(options) {\n\n            @Override\n            public PostViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n                LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());\n                return new PostViewHolder(inflater.inflate(R.layout.item_post, viewGroup, false));\n            }\n\n            @Override\n            protected void onBindViewHolder(PostViewHolder viewHolder, int position, final Post model) {\n                final DatabaseReference postRef = getRef(position);\n\n                // Set click listener for the whole post view\n                final String postKey = postRef.getKey();\n                viewHolder.itemView.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        // Launch PostDetailFragment\n                        NavController navController = Navigation.findNavController(requireActivity(),\n                                R.id.nav_host_fragment);\n                        Bundle args = new Bundle();\n                        args.putString(PostDetailFragment.EXTRA_POST_KEY, postKey);\n                        navController.navigate(R.id.action_MainFragment_to_PostDetailFragment, args);\n                    }\n                });\n\n                // Determine if the current user has liked this post and set UI accordingly\n                if (model.stars.containsKey(getUid())) {\n                    viewHolder.starView.setImageResource(R.drawable.ic_toggle_star_24);\n                } else {\n                    viewHolder.starView.setImageResource(R.drawable.ic_toggle_star_outline_24);\n                }\n\n                // Bind Post to ViewHolder, setting OnClickListener for the star button\n                viewHolder.bindToPost(model, new View.OnClickListener() {\n                    @Override\n                    public void onClick(View starView) {\n                        // Need to write to both places the post is stored\n                        DatabaseReference globalPostRef = mDatabase.child(\"posts\").child(postRef.getKey());\n                        DatabaseReference userPostRef = mDatabase.child(\"user-posts\").child(model.uid).child(postRef.getKey());\n\n                        // Run two transactions\n                        onStarClicked(globalPostRef);\n                        onStarClicked(userPostRef);\n                    }\n                });\n            }\n        };\n        mRecycler.setAdapter(mAdapter);\n    }\n\n    // [START post_stars_transaction]\n    private void onStarClicked(DatabaseReference postRef) {\n        postRef.runTransaction(new Transaction.Handler() {\n            @Override\n            public Transaction.Result doTransaction(MutableData mutableData) {\n                Post p = mutableData.getValue(Post.class);\n                if (p == null) {\n                    return Transaction.success(mutableData);\n                }\n\n                if (p.stars.containsKey(getUid())) {\n                    // Unstar the post and remove self from stars\n                    p.starCount = p.starCount - 1;\n                    p.stars.remove(getUid());\n                } else {\n                    // Star the post and add self to stars\n                    p.starCount = p.starCount + 1;\n                    p.stars.put(getUid(), true);\n                }\n\n                // Set value and report transaction success\n                mutableData.setValue(p);\n                return Transaction.success(mutableData);\n            }\n\n            @Override\n            public void onComplete(DatabaseError databaseError, boolean committed,\n                                   DataSnapshot currentData) {\n                // Transaction completed\n                Log.d(TAG, \"postTransaction:onComplete:\" + databaseError);\n            }\n        });\n    }\n    // [END post_stars_transaction]\n\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (mAdapter != null) {\n            mAdapter.startListening();\n        }\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        if (mAdapter != null) {\n            mAdapter.stopListening();\n        }\n    }\n\n    public String getUid() {\n        return FirebaseAuth.getInstance().getCurrentUser().getUid();\n    }\n\n    public abstract Query getQuery(DatabaseReference databaseReference);\n\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/listfragments/RecentPostsFragment.java",
    "content": "package com.google.firebase.quickstart.database.java.listfragments;\n\nimport com.google.firebase.database.DatabaseReference;\nimport com.google.firebase.database.Query;\n\npublic class RecentPostsFragment extends PostListFragment {\n\n    public RecentPostsFragment() {}\n\n    @Override\n    public Query getQuery(DatabaseReference databaseReference) {\n        // [START recent_posts_query]\n        // Last 100 posts, these are automatically the 100 most recent\n        // due to sorting by push() keys\n        Query recentPostsQuery = databaseReference.child(\"posts\")\n                .limitToFirst(100);\n        // [END recent_posts_query]\n\n        return recentPostsQuery;\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/models/Comment.java",
    "content": "package com.google.firebase.quickstart.database.java.models;\n\nimport com.google.firebase.database.IgnoreExtraProperties;\n\n@IgnoreExtraProperties\npublic class Comment {\n\n    public String uid;\n    public String author;\n    public String text;\n\n    public Comment() {\n        // Default constructor required for calls to DataSnapshot.getValue(Comment.class)\n    }\n\n    public Comment(String uid, String author, String text) {\n        this.uid = uid;\n        this.author = author;\n        this.text = text;\n    }\n\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/models/Post.java",
    "content": "package com.google.firebase.quickstart.database.java.models;\n\nimport com.google.firebase.database.Exclude;\nimport com.google.firebase.database.IgnoreExtraProperties;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@IgnoreExtraProperties\npublic class Post {\n\n    public String uid;\n    public String author;\n    public String title;\n    public String body;\n    public int starCount = 0;\n    public Map<String, Boolean> stars = new HashMap<>();\n\n    public Post() {\n        // Default constructor required for calls to DataSnapshot.getValue(Post.class)\n    }\n\n    public Post(String uid, String author, String title, String body) {\n        this.uid = uid;\n        this.author = author;\n        this.title = title;\n        this.body = body;\n    }\n\n    @Exclude\n    public Map<String, Object> toMap() {\n        HashMap<String, Object> result = new HashMap<>();\n        result.put(\"uid\", uid);\n        result.put(\"author\", author);\n        result.put(\"title\", title);\n        result.put(\"body\", body);\n        result.put(\"starCount\", starCount);\n        result.put(\"stars\", stars);\n\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/models/User.java",
    "content": "package com.google.firebase.quickstart.database.java.models;\n\nimport com.google.firebase.database.IgnoreExtraProperties;\n\n@IgnoreExtraProperties\npublic class User {\n\n    public String username;\n    public String email;\n\n    public User() {\n        // Default constructor required for calls to DataSnapshot.getValue(User.class)\n    }\n\n    public User(String username, String email) {\n        this.username = username;\n        this.email = email;\n    }\n\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/viewholder/CommentViewHolder.java",
    "content": "package com.google.firebase.quickstart.database.java.viewholder;\n\nimport android.view.View;\nimport android.widget.TextView;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.google.firebase.quickstart.database.R;\n\npublic class CommentViewHolder extends RecyclerView.ViewHolder {\n    public TextView authorView;\n    public TextView bodyView;\n\n    public CommentViewHolder(View itemView) {\n        super(itemView);\n        authorView = itemView.findViewById(R.id.commentAuthor);\n        bodyView = itemView.findViewById(R.id.commentBody);\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/java/viewholder/PostViewHolder.java",
    "content": "package com.google.firebase.quickstart.database.java.viewholder;\n\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.google.firebase.quickstart.database.R;\nimport com.google.firebase.quickstart.database.java.models.Post;\n\npublic class PostViewHolder extends RecyclerView.ViewHolder {\n\n    public TextView titleView;\n    public TextView authorView;\n    public ImageView starView;\n    public TextView numStarsView;\n    public TextView bodyView;\n\n    public PostViewHolder(View itemView) {\n        super(itemView);\n\n        titleView = itemView.findViewById(R.id.postTitle);\n        authorView = itemView.findViewById(R.id.postAuthor);\n        starView = itemView.findViewById(R.id.star);\n        numStarsView = itemView.findViewById(R.id.postNumStars);\n        bodyView = itemView.findViewById(R.id.postBody);\n    }\n\n    public void bindToPost(Post post, View.OnClickListener starClickListener) {\n        titleView.setText(post.title);\n        authorView.setText(post.author);\n        numStarsView.setText(String.valueOf(post.starCount));\n        bodyView.setText(post.body);\n\n        starView.setOnClickListener(starClickListener);\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/BaseFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin\n\nimport android.view.View\nimport android.widget.ProgressBar\nimport androidx.fragment.app.Fragment\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\n\nopen class BaseFragment : Fragment() {\n    private var progressBar: ProgressBar? = null\n\n    val uid: String\n        get() = Firebase.auth.currentUser!!.uid\n\n    fun setProgressBar(resId: Int) {\n        progressBar = view?.findViewById(resId)\n    }\n\n    fun showProgressBar() {\n        progressBar?.visibility = View.VISIBLE\n    }\n\n    fun hideProgressBar() {\n        progressBar?.visibility = View.INVISIBLE\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainActivity.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.navigation.findNavController\nimport com.google.firebase.quickstart.database.R\nimport com.google.firebase.quickstart.database.databinding.ActivityMainBinding\n\nclass MainActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityMainBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        val toolbar = binding.toolbar\n        setSupportActionBar(toolbar)\n\n        val fab = binding.fab\n        val navController = findNavController(R.id.nav_host_fragment)\n        navController.setGraph(R.navigation.nav_graph_kotlin)\n        navController.addOnDestinationChangedListener { _, destination, _ ->\n            if (destination.id == R.id.MainFragment) {\n                fab.isVisible = true\n                fab.setOnClickListener {\n                    navController.navigate(R.id.action_MainFragment_to_NewPostFragment)\n                }\n            } else {\n                fab.isGone = true\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/MainFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.MenuHost\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.Fragment\nimport androidx.navigation.fragment.findNavController\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport com.google.android.material.tabs.TabLayoutMediator\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.database.R\nimport com.google.firebase.quickstart.database.databinding.FragmentMainBinding\nimport com.google.firebase.quickstart.database.kotlin.listfragments.MyPostsFragment\nimport com.google.firebase.quickstart.database.kotlin.listfragments.MyTopPostsFragment\nimport com.google.firebase.quickstart.database.kotlin.listfragments.RecentPostsFragment\n\nclass MainFragment : Fragment(), MenuProvider {\n    private var _binding: FragmentMainBinding? = null\n    private val binding get() = _binding!!\n\n    private lateinit var pagerAdapter: FragmentStateAdapter\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        _binding = FragmentMainBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // MenuProvider\n        val menuHost: MenuHost = requireActivity() as MenuHost\n        menuHost.addMenuProvider(this)\n\n        // Create the adapter that will return a fragment for each section\n        pagerAdapter = object : FragmentStateAdapter(parentFragmentManager, viewLifecycleOwner.lifecycle) {\n            private val fragments = arrayOf<Fragment>(\n                RecentPostsFragment(),\n                MyPostsFragment(),\n                MyTopPostsFragment(),\n            )\n\n            override fun createFragment(position: Int) = fragments[position]\n\n            override fun getItemCount() = fragments.size\n        }\n\n        // Set up the ViewPager with the sections adapter.\n        with(binding) {\n            container.adapter = pagerAdapter\n            TabLayoutMediator(tabs, container) { tab, position ->\n                tab.text = when (position) {\n                    0 -> getString(R.string.heading_recent)\n                    1 -> getString(R.string.heading_my_posts)\n                    else -> getString(R.string.heading_my_top_posts)\n                }\n            }.attach()\n        }\n    }\n\n    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n        menuInflater.inflate(R.menu.menu_main, menu)\n    }\n\n    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n        return if (menuItem.itemId == R.id.action_logout) {\n            Firebase.auth.signOut()\n            findNavController().navigate(R.id.action_MainFragment_to_SignInFragment)\n            true\n        } else {\n            false\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        _binding = null\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/NewPostFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.navigation.fragment.findNavController\nimport com.google.firebase.database.DataSnapshot\nimport com.google.firebase.database.DatabaseError\nimport com.google.firebase.database.DatabaseReference\nimport com.google.firebase.database.ValueEventListener\nimport com.google.firebase.database.database\nimport com.google.firebase.database.getValue\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.database.R\nimport com.google.firebase.quickstart.database.databinding.FragmentNewPostBinding\nimport com.google.firebase.quickstart.database.kotlin.models.Post\nimport com.google.firebase.quickstart.database.kotlin.models.User\n\nclass NewPostFragment : BaseFragment() {\n    private var _binding: FragmentNewPostBinding? = null\n    private val binding get() = _binding!!\n\n    private lateinit var database: DatabaseReference\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        _binding = FragmentNewPostBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        database = Firebase.database.reference\n\n        binding.fabSubmitPost.setOnClickListener { submitPost() }\n    }\n\n    private fun submitPost() {\n        val title = binding.fieldTitle.text.toString()\n        val body = binding.fieldBody.text.toString()\n\n        // Title is required\n        if (TextUtils.isEmpty(title)) {\n            binding.fieldTitle.error = REQUIRED\n            return\n        }\n\n        // Body is required\n        if (TextUtils.isEmpty(body)) {\n            binding.fieldBody.error = REQUIRED\n            return\n        }\n\n        // Disable button so there are no multi-posts\n        setEditingEnabled(false)\n        Toast.makeText(context, \"Posting...\", Toast.LENGTH_SHORT).show()\n\n        val userId = uid\n        database.child(\"users\").child(userId).addListenerForSingleValueEvent(\n            object : ValueEventListener {\n                override fun onDataChange(dataSnapshot: DataSnapshot) {\n                    // Get user value\n                    val user = dataSnapshot.getValue<User>()\n\n                    if (user == null) {\n                        // User is null, error out\n                        Log.e(TAG, \"User $userId is unexpectedly null\")\n                        Toast.makeText(\n                            context,\n                            \"Error: could not fetch user.\",\n                            Toast.LENGTH_SHORT,\n                        ).show()\n                    } else {\n                        // Write new post\n                        writeNewPost(userId, user.username.toString(), title, body)\n                    }\n\n                    setEditingEnabled(true)\n                    findNavController().navigate(R.id.action_NewPostFragment_to_MainFragment)\n                }\n\n                override fun onCancelled(databaseError: DatabaseError) {\n                    Log.w(TAG, \"getUser:onCancelled\", databaseError.toException())\n                    setEditingEnabled(true)\n                }\n            },\n        )\n    }\n\n    private fun setEditingEnabled(enabled: Boolean) {\n        with(binding) {\n            fieldTitle.isEnabled = enabled\n            fieldBody.isEnabled = enabled\n            if (enabled) {\n                fabSubmitPost.show()\n            } else {\n                fabSubmitPost.hide()\n            }\n        }\n    }\n\n    private fun writeNewPost(userId: String, username: String, title: String, body: String) {\n        // Create new post at /user-posts/$userid/$postid and at\n        // /posts/$postid simultaneously\n        val key = database.child(\"posts\").push().key\n        if (key == null) {\n            Log.w(TAG, \"Couldn't get push key for posts\")\n            return\n        }\n\n        val post = Post(userId, username, title, body)\n        val postValues = post.toMap()\n\n        val childUpdates = hashMapOf<String, Any>(\n            \"/posts/$key\" to postValues,\n            \"/user-posts/$userId/$key\" to postValues,\n        )\n\n        database.updateChildren(childUpdates)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"NewPostFragment\"\n        private const val REQUIRED = \"Required\"\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/PostDetailFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.firebase.database.ChildEventListener\nimport com.google.firebase.database.DataSnapshot\nimport com.google.firebase.database.DatabaseError\nimport com.google.firebase.database.DatabaseReference\nimport com.google.firebase.database.ValueEventListener\nimport com.google.firebase.database.database\nimport com.google.firebase.database.getValue\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.database.R\nimport com.google.firebase.quickstart.database.databinding.FragmentPostDetailBinding\nimport com.google.firebase.quickstart.database.kotlin.models.Comment\nimport com.google.firebase.quickstart.database.kotlin.models.Post\nimport com.google.firebase.quickstart.database.kotlin.models.User\nimport com.google.firebase.quickstart.database.kotlin.viewholder.CommentViewHolder\nimport java.lang.IllegalArgumentException\nimport java.util.ArrayList\n\nclass PostDetailFragment : BaseFragment() {\n\n    private lateinit var postKey: String\n    private lateinit var postReference: DatabaseReference\n    private lateinit var commentsReference: DatabaseReference\n\n    private var postListener: ValueEventListener? = null\n    private var adapter: CommentAdapter? = null\n\n    private var _binding: FragmentPostDetailBinding? = null\n    private val binding get() = _binding!!\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        _binding = FragmentPostDetailBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // Get post key from arguments\n        postKey = requireArguments().getString(EXTRA_POST_KEY)\n            ?: throw IllegalArgumentException(\"Must pass EXTRA_POST_KEY\")\n\n        // Initialize Database\n        postReference = Firebase.database.reference\n            .child(\"posts\").child(postKey)\n        commentsReference = Firebase.database.reference\n            .child(\"post-comments\").child(postKey)\n\n        // Initialize Views\n        with(binding) {\n            buttonPostComment.setOnClickListener { postComment() }\n            recyclerPostComments.layoutManager = LinearLayoutManager(context)\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n\n        // Add value event listener to the post\n        val postListener = object : ValueEventListener {\n            override fun onDataChange(dataSnapshot: DataSnapshot) {\n                // Get Post object and use the values to update the UI\n                val post = dataSnapshot.getValue<Post>()\n                post?.let {\n                    binding.postAuthorLayout.postAuthor.text = it.author\n                    with(binding.postTextLayout) {\n                        postTitle.text = it.title\n                        postBody.text = it.body\n                    }\n                }\n            }\n\n            override fun onCancelled(databaseError: DatabaseError) {\n                // Getting Post failed, log a message\n                Log.w(TAG, \"loadPost:onCancelled\", databaseError.toException())\n                Toast.makeText(\n                    context,\n                    \"Failed to load post.\",\n                    Toast.LENGTH_SHORT,\n                ).show()\n            }\n        }\n        postReference.addValueEventListener(postListener)\n\n        // Keep copy of post listener so we can remove it when app stops\n        this.postListener = postListener\n\n        // Listen for comments\n        adapter = CommentAdapter(requireContext(), commentsReference)\n        binding.recyclerPostComments.adapter = adapter\n    }\n\n    override fun onStop() {\n        super.onStop()\n\n        // Remove post value event listener\n        postListener?.let {\n            postReference.removeEventListener(it)\n        }\n\n        // Clean up comments listener\n        adapter?.cleanupListener()\n    }\n\n    private fun postComment() {\n        val uid = uid\n        Firebase.database.reference.child(\"users\").child(uid)\n            .addListenerForSingleValueEvent(object : ValueEventListener {\n                override fun onDataChange(dataSnapshot: DataSnapshot) {\n                    // Get user information\n                    val user = dataSnapshot.getValue<User>() ?: return\n\n                    val authorName = user.username\n\n                    // Create new comment object\n                    val commentText = binding.fieldCommentText.text.toString()\n                    val comment = Comment(uid, authorName, commentText)\n\n                    // Push the comment, it will appear in the list\n                    commentsReference.push().setValue(comment)\n\n                    // Clear the field\n                    binding.fieldCommentText.text = null\n                }\n\n                override fun onCancelled(databaseError: DatabaseError) {\n                }\n            })\n    }\n\n    private class CommentAdapter(\n        private val context: Context,\n        private val databaseReference: DatabaseReference,\n    ) : RecyclerView.Adapter<CommentViewHolder>() {\n\n        private val childEventListener: ChildEventListener?\n\n        private val commentIds = ArrayList<String>()\n        private val comments = ArrayList<Comment>()\n\n        init {\n\n            // Create child event listener\n            val childEventListener = object : ChildEventListener {\n                override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) {\n                    Log.d(TAG, \"onChildAdded:\" + dataSnapshot.key!!)\n\n                    // A new comment has been added, add it to the displayed list\n                    val comment = dataSnapshot.getValue<Comment>()\n\n                    // Update RecyclerView\n                    commentIds.add(dataSnapshot.key!!)\n                    comments.add(comment!!)\n                    notifyItemInserted(comments.size - 1)\n                }\n\n                override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) {\n                    Log.d(TAG, \"onChildChanged: ${dataSnapshot.key}\")\n\n                    // A comment has changed, use the key to determine if we are displaying this\n                    // comment and if so displayed the changed comment.\n                    val newComment = dataSnapshot.getValue<Comment>()\n                    val commentKey = dataSnapshot.key\n\n                    val commentIndex = commentIds.indexOf(commentKey)\n                    if (commentIndex > -1 && newComment != null) {\n                        // Replace with the new data\n                        comments[commentIndex] = newComment\n\n                        // Update the RecyclerView\n                        notifyItemChanged(commentIndex)\n                    } else {\n                        Log.w(TAG, \"onChildChanged:unknown_child: $commentKey\")\n                    }\n                }\n\n                override fun onChildRemoved(dataSnapshot: DataSnapshot) {\n                    Log.d(TAG, \"onChildRemoved:\" + dataSnapshot.key!!)\n\n                    // A comment has changed, use the key to determine if we are displaying this\n                    // comment and if so remove it.\n                    val commentKey = dataSnapshot.key\n\n                    val commentIndex = commentIds.indexOf(commentKey)\n                    if (commentIndex > -1) {\n                        // Remove data from the list\n                        commentIds.removeAt(commentIndex)\n                        comments.removeAt(commentIndex)\n\n                        // Update the RecyclerView\n                        notifyItemRemoved(commentIndex)\n                    } else {\n                        Log.w(TAG, \"onChildRemoved:unknown_child:\" + commentKey!!)\n                    }\n                }\n\n                override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) {\n                    Log.d(TAG, \"onChildMoved:\" + dataSnapshot.key!!)\n\n                    // A comment has changed position, use the key to determine if we are\n                    // displaying this comment and if so move it.\n                    val movedComment = dataSnapshot.getValue<Comment>()\n                    val commentKey = dataSnapshot.key\n\n                    // ...\n                }\n\n                override fun onCancelled(databaseError: DatabaseError) {\n                    Log.w(TAG, \"postComments:onCancelled\", databaseError.toException())\n                    Toast.makeText(\n                        context,\n                        \"Failed to load comments.\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                }\n            }\n            databaseReference.addChildEventListener(childEventListener)\n\n            // Store reference to listener so it can be removed on app stop\n            this.childEventListener = childEventListener\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder {\n            val inflater = LayoutInflater.from(context)\n            val view = inflater.inflate(R.layout.item_comment, parent, false)\n            return CommentViewHolder(view)\n        }\n\n        override fun onBindViewHolder(holder: CommentViewHolder, position: Int) {\n            holder.bind(comments[position])\n        }\n\n        override fun getItemCount(): Int = comments.size\n\n        fun cleanupListener() {\n            childEventListener?.let {\n                databaseReference.removeEventListener(it)\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"PostDetailFragment\"\n        const val EXTRA_POST_KEY = \"post_key\"\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/SignInFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.navigation.fragment.findNavController\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.auth\nimport com.google.firebase.database.DatabaseReference\nimport com.google.firebase.database.database\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.database.R\nimport com.google.firebase.quickstart.database.databinding.FragmentSignInBinding\nimport com.google.firebase.quickstart.database.kotlin.models.User\n\nclass SignInFragment : BaseFragment() {\n    private var _binding: FragmentSignInBinding? = null\n    private val binding get() = _binding!!\n\n    private lateinit var database: DatabaseReference\n    private lateinit var auth: FirebaseAuth\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        _binding = FragmentSignInBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        database = Firebase.database.reference\n        auth = Firebase.auth\n\n        setProgressBar(R.id.progressBar)\n\n        // Click listeners\n        with(binding) {\n            buttonSignIn.setOnClickListener { signIn() }\n            buttonSignUp.setOnClickListener { signUp() }\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n\n        // Check auth on Fragment start\n        auth.currentUser?.let {\n            onAuthSuccess(it)\n        }\n    }\n\n    private fun signIn() {\n        Log.d(TAG, \"signIn\")\n        if (!validateForm()) {\n            return\n        }\n\n        showProgressBar()\n        val email = binding.fieldEmail.text.toString()\n        val password = binding.fieldPassword.text.toString()\n\n        auth.signInWithEmailAndPassword(email, password)\n            .addOnCompleteListener(requireActivity()) { task ->\n                Log.d(TAG, \"signIn:onComplete:\" + task.isSuccessful)\n                hideProgressBar()\n\n                if (task.isSuccessful) {\n                    onAuthSuccess(task.result?.user!!)\n                } else {\n                    Toast.makeText(\n                        context,\n                        \"Sign In Failed\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                }\n            }\n    }\n\n    private fun signUp() {\n        Log.d(TAG, \"signUp\")\n        if (!validateForm()) {\n            return\n        }\n\n        showProgressBar()\n        val email = binding.fieldEmail.text.toString()\n        val password = binding.fieldPassword.text.toString()\n\n        auth.createUserWithEmailAndPassword(email, password)\n            .addOnCompleteListener(requireActivity()) { task ->\n                Log.d(TAG, \"createUser:onComplete:\" + task.isSuccessful)\n                hideProgressBar()\n\n                if (task.isSuccessful) {\n                    onAuthSuccess(task.result?.user!!)\n                } else {\n                    Toast.makeText(\n                        context,\n                        \"Sign Up Failed\",\n                        Toast.LENGTH_SHORT,\n                    ).show()\n                }\n            }\n    }\n\n    private fun onAuthSuccess(user: FirebaseUser) {\n        val username = usernameFromEmail(user.email!!)\n\n        // Write new user\n        writeNewUser(user.uid, username, user.email)\n\n        // Go to MainFragment\n        findNavController().navigate(R.id.action_SignInFragment_to_MainFragment)\n    }\n\n    private fun usernameFromEmail(email: String): String {\n        return if (email.contains(\"@\")) {\n            email.split(\"@\".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]\n        } else {\n            email\n        }\n    }\n\n    private fun validateForm(): Boolean {\n        var result = true\n        if (TextUtils.isEmpty(binding.fieldEmail.text.toString())) {\n            binding.fieldEmail.error = \"Required\"\n            result = false\n        } else {\n            binding.fieldEmail.error = null\n        }\n\n        if (TextUtils.isEmpty(binding.fieldPassword.text.toString())) {\n            binding.fieldPassword.error = \"Required\"\n            result = false\n        } else {\n            binding.fieldPassword.error = null\n        }\n\n        return result\n    }\n\n    private fun writeNewUser(userId: String, name: String, email: String?) {\n        val user = User(name, email)\n        database.child(\"users\").child(userId).setValue(user)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        _binding = null\n    }\n\n    companion object {\n        private const val TAG = \"SignInFragment\"\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyPostsFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.listfragments\n\nimport com.google.firebase.database.DatabaseReference\nimport com.google.firebase.database.Query\n\nclass MyPostsFragment : PostListFragment() {\n\n    override fun getQuery(databaseReference: DatabaseReference): Query {\n        // All my posts\n        return databaseReference.child(\"user-posts\")\n            .child(uid)\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/MyTopPostsFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.listfragments\n\nimport com.google.firebase.database.DatabaseReference\nimport com.google.firebase.database.Query\n\nclass MyTopPostsFragment : PostListFragment() {\n\n    override fun getQuery(databaseReference: DatabaseReference): Query {\n        // My top posts by number of stars\n        val myUserId = uid\n\n        return databaseReference.child(\"user-posts\").child(myUserId)\n            .orderByChild(\"starCount\")\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/PostListFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.listfragments\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.Fragment\nimport androidx.navigation.findNavController\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.firebase.ui.database.FirebaseRecyclerAdapter\nimport com.firebase.ui.database.FirebaseRecyclerOptions\nimport com.google.firebase.auth.auth\nimport com.google.firebase.database.DataSnapshot\nimport com.google.firebase.database.DatabaseError\nimport com.google.firebase.database.DatabaseReference\nimport com.google.firebase.database.MutableData\nimport com.google.firebase.database.Query\nimport com.google.firebase.database.Transaction\nimport com.google.firebase.database.database\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.database.R\nimport com.google.firebase.quickstart.database.kotlin.PostDetailFragment\nimport com.google.firebase.quickstart.database.kotlin.models.Post\nimport com.google.firebase.quickstart.database.kotlin.viewholder.PostViewHolder\n\nabstract class PostListFragment : Fragment() {\n\n    // [START define_database_reference]\n    private lateinit var database: DatabaseReference\n    // [END define_database_reference]\n\n    private lateinit var recycler: RecyclerView\n    private lateinit var manager: LinearLayoutManager\n    private var adapter: FirebaseRecyclerAdapter<Post, PostViewHolder>? = null\n\n    val uid: String\n        get() = Firebase.auth.currentUser!!.uid\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View? {\n        super.onCreateView(inflater, container, savedInstanceState)\n        val rootView = inflater.inflate(R.layout.fragment_all_posts, container, false)\n\n        // [START create_database_reference]\n        database = Firebase.database.reference\n        // [END create_database_reference]\n\n        recycler = rootView.findViewById(R.id.messagesList)\n        recycler.setHasFixedSize(true)\n\n        return rootView\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n\n        // Set up Layout Manager, reverse layout\n        manager = LinearLayoutManager(activity)\n        manager.reverseLayout = true\n        manager.stackFromEnd = true\n        recycler.layoutManager = manager\n\n        // Set up FirebaseRecyclerAdapter with the Query\n        val postsQuery = getQuery(database)\n\n        val options = FirebaseRecyclerOptions.Builder<Post>()\n            .setQuery(postsQuery, Post::class.java)\n            .build()\n\n        adapter = object : FirebaseRecyclerAdapter<Post, PostViewHolder>(options) {\n\n            override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PostViewHolder {\n                val inflater = LayoutInflater.from(viewGroup.context)\n                return PostViewHolder(inflater.inflate(R.layout.item_post, viewGroup, false))\n            }\n\n            override fun onBindViewHolder(viewHolder: PostViewHolder, position: Int, model: Post) {\n                val postRef = getRef(position)\n\n                // Set click listener for the whole post view\n                val postKey = postRef.key\n                viewHolder.itemView.setOnClickListener {\n                    // Launch PostDetailFragment\n                    val navController = requireActivity().findNavController(R.id.nav_host_fragment)\n                    val args = bundleOf(PostDetailFragment.EXTRA_POST_KEY to postKey)\n                    navController.navigate(R.id.action_MainFragment_to_PostDetailFragment, args)\n                }\n\n                // Determine if the current user has liked this post and set UI accordingly\n                viewHolder.setLikedState(model.stars.containsKey(uid))\n\n                // Bind Post to ViewHolder, setting OnClickListener for the star button\n                viewHolder.bindToPost(model) {\n                    // Need to write to both places the post is stored\n                    val globalPostRef = database.child(\"posts\").child(postRef.key!!)\n                    val userPostRef = database.child(\"user-posts\").child(model.uid!!).child(postRef.key!!)\n\n                    // Run two transactions\n                    onStarClicked(globalPostRef)\n                    onStarClicked(userPostRef)\n                }\n            }\n        }\n        recycler.adapter = adapter\n    }\n\n    // [START post_stars_transaction]\n    private fun onStarClicked(postRef: DatabaseReference) {\n        postRef.runTransaction(object : Transaction.Handler {\n            override fun doTransaction(mutableData: MutableData): Transaction.Result {\n                val p = mutableData.getValue(Post::class.java)\n                    ?: return Transaction.success(mutableData)\n\n                if (p.stars.containsKey(uid)) {\n                    // Unstar the post and remove self from stars\n                    p.starCount = p.starCount - 1\n                    p.stars.remove(uid)\n                } else {\n                    // Star the post and add self to stars\n                    p.starCount = p.starCount + 1\n                    p.stars[uid] = true\n                }\n\n                // Set value and report transaction success\n                mutableData.value = p\n                return Transaction.success(mutableData)\n            }\n\n            override fun onComplete(\n                databaseError: DatabaseError?,\n                committed: Boolean,\n                currentData: DataSnapshot?,\n            ) {\n                // Transaction completed\n                Log.d(TAG, \"postTransaction:onComplete:\" + databaseError!!)\n            }\n        })\n    }\n    // [END post_stars_transaction]\n\n    override fun onStart() {\n        super.onStart()\n        adapter?.startListening()\n    }\n\n    override fun onStop() {\n        super.onStop()\n        adapter?.stopListening()\n    }\n\n    abstract fun getQuery(databaseReference: DatabaseReference): Query\n\n    companion object {\n\n        private const val TAG = \"PostListFragment\"\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/listfragments/RecentPostsFragment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.listfragments\n\nimport com.google.firebase.database.DatabaseReference\nimport com.google.firebase.database.Query\n\nclass RecentPostsFragment : PostListFragment() {\n\n    override fun getQuery(databaseReference: DatabaseReference): Query {\n        // [START recent_posts_query]\n        // Last 100 posts, these are automatically the 100 most recent\n        // due to sorting by push() keys.\n        return databaseReference.child(\"posts\")\n            .limitToFirst(100)\n        // [END recent_posts_query]\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/models/Comment.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.models\n\nimport com.google.firebase.database.IgnoreExtraProperties\n\n@IgnoreExtraProperties\ndata class Comment(\n    var uid: String? = \"\",\n    var author: String? = \"\",\n    var text: String? = \"\",\n)\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/models/Post.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.models\n\nimport com.google.firebase.database.Exclude\nimport com.google.firebase.database.IgnoreExtraProperties\nimport java.util.HashMap\n\n@IgnoreExtraProperties\ndata class Post(\n    var uid: String? = \"\",\n    var author: String? = \"\",\n    var title: String? = \"\",\n    var body: String? = \"\",\n    var starCount: Int = 0,\n    var stars: MutableMap<String, Boolean> = HashMap(),\n) {\n\n    @Exclude\n    fun toMap(): Map<String, Any?> {\n        return mapOf(\n            \"uid\" to uid,\n            \"author\" to author,\n            \"title\" to title,\n            \"body\" to body,\n            \"starCount\" to starCount,\n            \"stars\" to stars,\n        )\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/models/User.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.models\n\nimport com.google.firebase.database.IgnoreExtraProperties\n\n@IgnoreExtraProperties\ndata class User(\n    var username: String? = \"\",\n    var email: String? = \"\",\n)\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/CommentViewHolder.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.viewholder\n\nimport android.view.View\nimport android.widget.TextView\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.firebase.quickstart.database.R\nimport com.google.firebase.quickstart.database.kotlin.models.Comment\n\nclass CommentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {\n    fun bind(comment: Comment) {\n        itemView.findViewById<TextView>(R.id.commentAuthor).text = comment.author\n        itemView.findViewById<TextView>(R.id.commentBody).text = comment.text\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/java/com/google/firebase/quickstart/database/kotlin/viewholder/PostViewHolder.kt",
    "content": "package com.google.firebase.quickstart.database.kotlin.viewholder\n\nimport android.view.View\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.firebase.quickstart.database.R\nimport com.google.firebase.quickstart.database.kotlin.models.Post\n\nclass PostViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {\n    private val postTitle: TextView = itemView.findViewById(R.id.postTitle)\n    private val postAuthor: TextView = itemView.findViewById(R.id.postAuthor)\n    private val postNumStars: TextView = itemView.findViewById(R.id.postNumStars)\n    private val postBody: TextView = itemView.findViewById(R.id.postBody)\n    private val star: ImageView = itemView.findViewById(R.id.star)\n\n    fun bindToPost(post: Post, starClickListener: View.OnClickListener) {\n        postTitle.text = post.title\n        postAuthor.text = post.author\n        postNumStars.text = post.starCount.toString()\n        postBody.text = post.body\n\n        star.setOnClickListener(starClickListener)\n    }\n\n    fun setLikedState(liked: Boolean) {\n        if (liked) {\n            star.setImageResource(R.drawable.ic_toggle_star_24)\n        } else {\n            star.setImageResource(R.drawable.ic_toggle_star_outline_24)\n        }\n    }\n}\n"
  },
  {
    "path": "database/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/AppTheme.AppBarOverlay\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"?attr/colorPrimary\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fab\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|end\"\n        android:layout_margin=\"@dimen/fab_margin\"\n        app:srcCompat=\"@drawable/ic_image_edit\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n        <fragment\n            android:id=\"@+id/nav_host_fragment\"\n            android:name=\"androidx.navigation.fragment.NavHostFragment\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:defaultNavHost=\"true\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"/>\n    </FrameLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "database/app/src/main/res/layout/fragment_all_posts.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:viewBindingIgnore=\"true\">\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/messagesList\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:padding=\"5dp\"\n        android:scrollbars=\"vertical\"\n        tools:listitem=\"@layout/item_post\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "database/app/src/main/res/layout/fragment_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tabs\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n    <androidx.viewpager2.widget.ViewPager2\n        android:id=\"@+id/container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tabs\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "database/app/src/main/res/layout/fragment_new_post.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <EditText\n        android:id=\"@+id/fieldTitle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:hint=\"Title\"\n        android:maxLines=\"1\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n    <EditText\n        android:id=\"@+id/fieldBody\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:hint=\"Write your post...\"\n        android:inputType=\"textMultiLine\"\n        android:maxLines=\"10\"\n        android:scrollHorizontally=\"false\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/fieldTitle\"\n        />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fabSubmitPost\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:src=\"@drawable/ic_navigation_check_24\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "database/app/src/main/res/layout/fragment_post_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\">\n\n    <include\n        android:id=\"@+id/postAuthorLayout\"\n        layout=\"@layout/include_post_author\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <include\n        android:id=\"@+id/postTextLayout\"\n        layout=\"@layout/include_post_text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/postAuthorLayout\" />\n\n    <EditText\n        android:id=\"@+id/fieldCommentText\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:hint=\"Write a comment...\"\n        android:maxLines=\"1\"\n        app:layout_constraintEnd_toStartOf=\"@+id/buttonPostComment\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintHorizontal_weight=\"8\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/postTextLayout\" />\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/buttonPostComment\"\n        style=\"@style/Widget.MaterialComponents.Button.TextButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Post\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintHorizontal_weight=\"2\"\n        app:layout_constraintStart_toEndOf=\"@+id/fieldCommentText\"\n        app:layout_constraintTop_toTopOf=\"@+id/fieldCommentText\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recyclerPostComments\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/buttonPostComment\"\n        tools:listitem=\"@layout/item_comment\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "database/app/src/main/res/layout/fragment_sign_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\">\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:indeterminate=\"true\"\n        android:visibility=\"invisible\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerHorizontal=\"true\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/progressBar\" />\n\n    <EditText\n        android:id=\"@+id/fieldEmail\"\n        android:layout_width=\"150dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:ellipsize=\"end\"\n        android:hint=\"@string/hint_email\"\n        android:inputType=\"textEmailAddress\"\n        android:maxLines=\"1\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintEnd_toStartOf=\"@+id/fieldPassword\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/icon\" />\n\n    <EditText\n        android:id=\"@+id/fieldPassword\"\n        android:layout_width=\"150dp\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"end\"\n        android:hint=\"@string/hint_password\"\n        android:inputType=\"textPassword\"\n        android:maxLines=\"1\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toEndOf=\"@+id/fieldEmail\"\n        app:layout_constraintTop_toTopOf=\"@+id/fieldEmail\" />\n\n    <Button\n        android:id=\"@+id/buttonSignIn\"\n        android:layout_width=\"150dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:text=\"@string/sign_in\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintEnd_toStartOf=\"@+id/buttonSignUp\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/fieldEmail\" />\n\n    <Button\n        android:id=\"@+id/buttonSignUp\"\n        android:layout_width=\"150dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/sign_up\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toEndOf=\"@+id/buttonSignIn\"\n        app:layout_constraintTop_toTopOf=\"@+id/buttonSignIn\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "database/app/src/main/res/layout/include_post_author.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\">\n\n    <ImageView\n        android:id=\"@+id/postAuthorPhoto\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/ic_action_account_circle_40\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/postAuthor\"\n        style=\"@style/Base.TextAppearance.AppCompat.Small\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:gravity=\"center_vertical\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/postAuthorPhoto\"\n        app:layout_constraintStart_toEndOf=\"@+id/postAuthorPhoto\"\n        app:layout_constraintTop_toTopOf=\"@+id/postAuthorPhoto\"\n        tools:text=\"someauthor@email.com\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "database/app/src/main/res/layout/include_post_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <TextView\n        android:id=\"@+id/postTitle\"\n        style=\"@style/TextAppearance.AppCompat.Medium\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"end\"\n        android:maxLines=\"1\"\n        android:textStyle=\"bold\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"My First Post\" />\n\n    <TextView\n        android:id=\"@+id/postBody\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/postTitle\"\n        tools:text=\"@string/lorem\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "database/app/src/main/res/layout/item_comment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingTop=\"8dp\"\n    android:paddingBottom=\"8dp\"\n    tools:viewBindingIgnore=\"true\">\n\n    <ImageView\n        android:id=\"@+id/commentPhoto\"\n        android:layout_width=\"32dp\"\n        android:layout_height=\"32dp\"\n        android:layout_centerVertical=\"true\"\n        android:src=\"@drawable/ic_action_account_circle_40\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/commentAuthor\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintStart_toEndOf=\"@+id/commentPhoto\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"John Doe\" />\n\n    <TextView\n        android:id=\"@+id/commentBody\"\n        style=\"@style/TextAppearance.AppCompat.Small\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintStart_toStartOf=\"@+id/commentAuthor\"\n        app:layout_constraintTop_toBottomOf=\"@+id/commentAuthor\"\n        tools:text=\"This is the comment text..\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "database/app/src/main/res/layout/item_post.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"5dp\"\n    tools:viewBindingIgnore=\"true\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"10dp\">\n\n        <include\n            android:id=\"@+id/postAuthorLayout\"\n            layout=\"@layout/include_post_author\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <ImageView\n            android:id=\"@+id/star\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?attr/selectableItemBackground\"\n            android:src=\"@drawable/ic_toggle_star_outline_24\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/postAuthorLayout\"\n            app:layout_constraintEnd_toStartOf=\"@+id/postNumStars\"\n            app:layout_constraintTop_toTopOf=\"@+id/postAuthorLayout\" />\n\n        <TextView\n            android:id=\"@+id/postNumStars\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/star\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@+id/star\"\n            tools:text=\"7\" />\n\n        <include\n            layout=\"@layout/include_post_text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginTop=\"16dp\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/postAuthorLayout\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "database/app/src/main/res/menu/menu_main.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <item\n        android:id=\"@+id/action_logout\"\n        android:title=\"@string/menu_logout\"\n        android:visible=\"true\"\n        app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "database/app/src/main/res/navigation/nav_graph_java.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/nav_graph_java\"\n    app:startDestination=\"@id/SignInFragment\">\n\n    <fragment\n        android:id=\"@+id/SignInFragment\"\n        android:name=\"com.google.firebase.quickstart.database.java.SignInFragment\"\n        android:label=\"@string/title_sign_in\"\n        tools:layout=\"@layout/fragment_sign_in\">\n\n        <action\n            android:id=\"@+id/action_SignInFragment_to_MainFragment\"\n            app:popUpTo=\"@id/MainFragment\"\n            app:destination=\"@id/MainFragment\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/MainFragment\"\n        android:name=\"com.google.firebase.quickstart.database.java.MainFragment\"\n        android:label=\"@string/app_name\"\n        tools:layout=\"@layout/fragment_main\">\n        <action\n            android:id=\"@+id/action_MainFragment_to_NewPostFragment\"\n            app:destination=\"@id/NewPostFragment\" />\n        <action\n            android:id=\"@+id/action_MainFragment_to_SignInFragment\"\n            app:popUpTo=\"@id/SignInFragment\"\n            app:destination=\"@id/SignInFragment\" />\n        <action\n            android:id=\"@+id/action_MainFragment_to_PostDetailFragment\"\n            app:destination=\"@id/PostDetailFragment\" >\n            <argument android:name=\"post_key\" app:nullable=\"false\" app:argType=\"string\" android:defaultValue=\"\"/>\n        </action>\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/PostDetailFragment\"\n        android:name=\"com.google.firebase.quickstart.database.java.PostDetailFragment\"\n        android:label=\"@string/app_name\"\n        tools:layout=\"@layout/fragment_post_detail\">\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/NewPostFragment\"\n        android:name=\"com.google.firebase.quickstart.database.java.NewPostFragment\"\n        android:label=\"@string/app_name\"\n        tools:layout=\"@layout/fragment_new_post\">\n        <action\n            android:id=\"@+id/action_NewPostFragment_to_MainFragment\"\n            app:destination=\"@id/MainFragment\"\n            app:popUpTo=\"@id/MainFragment\">\n        </action>\n    </fragment>\n</navigation>"
  },
  {
    "path": "database/app/src/main/res/navigation/nav_graph_kotlin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/nav_graph_kotlin\"\n    app:startDestination=\"@id/SignInFragment\">\n\n    <fragment\n        android:id=\"@+id/SignInFragment\"\n        android:name=\"com.google.firebase.quickstart.database.kotlin.SignInFragment\"\n        android:label=\"@string/title_sign_in\"\n        tools:layout=\"@layout/fragment_sign_in\">\n\n        <action\n            android:id=\"@+id/action_SignInFragment_to_MainFragment\"\n            app:popUpTo=\"@id/MainFragment\"\n            app:destination=\"@id/MainFragment\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/MainFragment\"\n        android:name=\"com.google.firebase.quickstart.database.kotlin.MainFragment\"\n        android:label=\"@string/app_name\"\n        tools:layout=\"@layout/fragment_main\">\n        <action\n            android:id=\"@+id/action_MainFragment_to_NewPostFragment\"\n            app:destination=\"@id/NewPostFragment\" />\n        <action\n            android:id=\"@+id/action_MainFragment_to_SignInFragment\"\n            app:popUpTo=\"@id/SignInFragment\"\n            app:destination=\"@id/SignInFragment\" />\n        <action\n            android:id=\"@+id/action_MainFragment_to_PostDetailFragment\"\n            app:destination=\"@id/PostDetailFragment\" >\n            <argument android:name=\"post_key\" app:nullable=\"false\" app:argType=\"string\" android:defaultValue=\"\"/>\n        </action>\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/PostDetailFragment\"\n        android:name=\"com.google.firebase.quickstart.database.kotlin.PostDetailFragment\"\n        android:label=\"@string/app_name\"\n        tools:layout=\"@layout/fragment_post_detail\">\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/NewPostFragment\"\n        android:name=\"com.google.firebase.quickstart.database.kotlin.NewPostFragment\"\n        android:label=\"@string/app_name\"\n        tools:layout=\"@layout/fragment_new_post\">\n        <action\n            android:id=\"@+id/action_NewPostFragment_to_MainFragment\"\n            app:destination=\"@id/MainFragment\"\n            app:popUpTo=\"@id/MainFragment\">\n        </action>\n    </fragment>\n</navigation>"
  },
  {
    "path": "database/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n\n    <color name=\"primary_text\">#212121</color>\n    <color name=\"secondary_text\">#727272</color>\n    <color name=\"icons\">#212121</color>\n    <color name=\"divider\">#B6B6B6</color>\n</resources>\n"
  },
  {
    "path": "database/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"fab_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "database/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase Database</string>\n    <string name=\"action_settings\">Settings</string>\n    <string name=\"menu_login\">Log in</string>\n    <string name=\"menu_logout\">Log out</string>\n    <string name=\"login_label\">Log In</string>\n    <string name=\"email_label\">Email</string>\n    <string name=\"password_label\">Password</string>\n    <string name=\"send_text\">Send</string>\n    <string name=\"lorem\">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec condimentum ante, quis mattis mi. Proin ut rhoncus libero, ac euismod neque. Ut suscipit, enim a facilisis consequat, orci lacus venenatis orci, at ultricies orci nulla sed ex. In condimentum viverra velit, eget feugiat libero imperdiet vel. Aenean pretium, lectus vitae bibendum bibendum, diam turpis convallis eros, quis tristique leo sapien at ipsum. Praesent eu odio id nulla lobortis auctor. Donec eget dolor eget nisl euismod commodo vel a lacus. Quisque ut justo vitae urna pharetra accumsan. Etiam dignissim neque iaculis mauris viverra, blandit rhoncus odio porttitor. Phasellus nibh arcu.</string>\n    <string name=\"title_sign_in\">Welcome</string>\n    <string name=\"sign_up\">Sign Up</string>\n    <string name=\"hint_password\">password</string>\n    <string name=\"hint_email\">email</string>\n    <string name=\"sign_in\">Sign In</string>\n    <string name=\"heading_recent\">Recent</string>\n    <string name=\"heading_my_posts\">My Posts</string>\n    <string name=\"heading_my_top_posts\">My Top Posts</string>\n</resources>\n"
  },
  {
    "path": "database/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n    <style name=\"AppTheme.AppBarOverlay\" parent=\"ThemeOverlay.AppCompat.Dark.ActionBar\"/>\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Light\"/>\n\n</resources>\n"
  },
  {
    "path": "database/app/src/main/res/values-v21/styles.xml",
    "content": "<resources>\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "database/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "database/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "database/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "database/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "database/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "database/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "database/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "dataconnect/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n.dataconnect/\n.firebaserc\n.firebase/\n*.log\n"
  },
  {
    "path": "dataconnect/README.md",
    "content": "# Firebase Data Connect Quickstart\n\n## Introduction\n\nThis quickstart is a movie review app to demonstrate the use of Firebase Data Connect\n with a Cloud SQL database.\nFor more information about Firebase Data Connect visit [the docs](https://firebase.google.com/docs/data-connect/).\n\n## Getting Started\n\nFollow these steps to get up and running with Firebase Data Connect. For more detailed instructions,\ncheck out the [official documentation](https://firebase.google.com/docs/data-connect/quickstart).\n\n### 0. Prerequisites\n- Latest version of [Android Studio](https://developer.android.com/studio)\n- Latest version of [Visual Studio Code](https://code.visualstudio.com/)\n- The [Firebase Data Connect VS Code Extension](https://marketplace.visualstudio.com/items?itemName=GoogleCloudTools.firebase-dataconnect-vscode)\n\n### 1. Connect to your Firebase project\n\n1. If you haven't already, create a Firebase project.\n    1. In the [Firebase console](https://console.firebase.google.com), click\n        **Add project**, then follow the on-screen instructions.\n\n2. Upgrade your project to the Blaze plan. This lets you create a Cloud SQL\n    for PostgreSQL instance.\n\n    > Note: Though you set up billing in your Blaze upgrade, you won't be\n    charged for usage of Firebase Data Connect or the\n    [default Cloud SQL for PostgreSQL configuration](https://firebase.google.com/docs/data-connect/#pricing)\n    during the preview.\n\n3. Navigate to the [Data Connect section](https://console.firebase.google.com/u/0/project/_/dataconnect)\n    of the Firebase console, click on the \"Get Started\" button and follow the setup workflow:\n     - Select a location for your Cloud SQL for PostgreSQL database (this sample uses `us-central1`). If you choose a different location, you'll also need to change the `quickstart-android/dataconnect/dataconnect/dataconnect.yaml` file. \n     - Select the option to create a new Cloud SQL instance and fill in the following fields:\n       - Service ID: `dataconnect`\n       - Cloud SQL Instance ID: `fdc-sql`\n       - Database name: `fdcdb`\n4. Allow some time for the Cloud SQL instance to be provisioned. After it's provisioned, the instance\n   can be managed in the [Cloud Console](https://console.cloud.google.com/sql).\n\n5. If you haven’t already, add an Android app to your Firebase project, with the android package name `com.google.firebase.example.dataconnect`.\n Click **Download google-services.json** to obtain your Firebase Android config file.\n\n### 2. Cloning the repository\n\n1. Clone this repository to your local machine:\n   ```sh\n   git clone https://github.com/firebase/quickstart-android.git\n   ```\n\n2. Move the `google-services.json` config file (downloaded in the previous step) into the\n  `quickstart-android/dataconnect/app/` directory.\n\n### 3. Open in Visual Studio Code (VS Code)\n\n1. Open the `quickstart-android/dataconnect` directory in VS Code.\n2. Click on the Firebase Data Connect icon on the VS Code sidebar to load the Extension.\n   a. Sign in with your Google Account if you haven't already.\n3. Click on \"Connect a Firebase project\" and choose the project where you have set up Data Connect.\n4. Click on \"Start Emulators\" - this should generate the Kotlin SDK for you and start the emulators.\n\n### 4. Populate the database\nIn VS Code, open the `quickstart-android/dataconnect/dataconnect/moviedata_insert.gql` file and click the\n `Run (local)` button at the top of the file.\n\nIf you’d like to confirm that the data was correctly inserted,\nopen `quickstart-android/dataconnect/movie-connector/queries.gql` and run the `ListMovies` query.\n\n### 5. Running the app\n\nPress the Run button in Android Studio to run the sample app on your device.\n"
  },
  {
    "path": "dataconnect/app/.gitignore",
    "content": "/build"
  },
  {
    "path": "dataconnect/app/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.kotlin.serialization)\n    alias(libs.plugins.google.services)\n    alias(libs.plugins.compose.compiler)\n}\n\nandroid {\n    namespace = \"com.google.firebase.example.dataconnect\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.example.dataconnect\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        vectorDrawables {\n            useSupportLibrary = true\n        }\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = false\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n    buildFeatures {\n        compose = true\n    }\n    composeOptions {\n        kotlinCompilerExtensionVersion = \"1.5.13\"\n    }\n    packaging {\n        resources {\n            excludes += \"/META-INF/{AL2.0,LGPL2.1}\"\n        }\n    }\n    sourceSets.getByName(\"main\") {\n        kotlin.directories.add(\"build/generated/sources\")\n    }\n}\n\ndependencies {\n\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.lifecycle.runtime.ktx)\n    implementation(libs.androidx.lifecycle.viewmodel.compose)\n    implementation(libs.androidx.activity.compose)\n    implementation(platform(libs.androidx.compose.bom))\n    implementation(libs.androidx.ui)\n    implementation(libs.androidx.ui.graphics)\n    implementation(libs.androidx.ui.tooling.preview)\n    implementation(libs.androidx.material3)\n    implementation(libs.compose.material.icons)\n    implementation(libs.compose.navigation)\n    implementation(libs.androidx.lifecycle.runtime.compose.android)\n    implementation(libs.coil.compose)\n\n    // Data Connect dependencies\n    implementation(libs.kotlinx.serialization.core)\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(libs.firebase.bom))\n\n    // Data Connect\n    implementation(\"com.google.firebase:firebase-dataconnect\")\n\n    // Firebase Authentication\n    implementation(\"com.google.firebase:firebase-auth\")\n\n    testImplementation(libs.junit)\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core)\n    androidTestImplementation(platform(libs.androidx.compose.bom))\n    androidTestImplementation(libs.androidx.ui.test.junit4)\n    debugImplementation(libs.androidx.ui.tooling)\n    debugImplementation(libs.androidx.ui.test.manifest)\n}\n"
  },
  {
    "path": "dataconnect/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "dataconnect/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n        android:fullBackupContent=\"@xml/backup_rules\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.FirebaseDataConnect\"\n        tools:targetApi=\"31\">\n        <activity\n            android:name=\".MainActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/Theme.FirebaseDataConnect\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt",
    "content": "package com.google.firebase.example.dataconnect\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.layout.consumeWindowInsets\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Home\nimport androidx.compose.material.icons.filled.Person\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.NavigationBar\nimport androidx.compose.material3.NavigationBarItem\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.res.stringResource\nimport androidx.navigation.NavDestination.Companion.hasRoute\nimport androidx.navigation.NavDestination.Companion.hierarchy\nimport androidx.navigation.compose.NavHost\nimport androidx.navigation.compose.composable\nimport androidx.navigation.compose.currentBackStackEntryAsState\nimport androidx.navigation.compose.rememberNavController\nimport com.google.firebase.dataconnect.movies.MoviesConnector\nimport com.google.firebase.dataconnect.movies.instance\nimport com.google.firebase.example.dataconnect.feature.actordetail.ActorDetailRoute\nimport com.google.firebase.example.dataconnect.feature.actordetail.ActorDetailScreen\nimport com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailRoute\nimport com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailScreen\nimport com.google.firebase.example.dataconnect.feature.movies.MoviesRoute\nimport com.google.firebase.example.dataconnect.feature.movies.MoviesScreen\nimport com.google.firebase.example.dataconnect.feature.profile.ProfileRoute\nimport com.google.firebase.example.dataconnect.feature.profile.ProfileScreen\nimport com.google.firebase.example.dataconnect.feature.search.searchScreen\nimport com.google.firebase.example.dataconnect.ui.theme.FirebaseDataConnectTheme\n\ndata class TopLevelRoute<T : Any>(val labelResId: Int, val route: T, val icon: ImageVector)\n\nval TOP_LEVEL_ROUTES = listOf(\n    TopLevelRoute(R.string.label_movies, MoviesRoute, Icons.Filled.Home),\n    TopLevelRoute(R.string.label_profile, ProfileRoute, Icons.Filled.Person)\n)\n\nclass MainActivity : ComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        enableEdgeToEdge()\n        // Comment the line below to use a production environment instead\n        MoviesConnector.instance.dataConnect.useEmulator(\"10.0.2.2\", 9399)\n        setContent {\n            FirebaseDataConnectTheme {\n                val navController = rememberNavController()\n                Scaffold(\n                    modifier = Modifier.fillMaxSize(),\n                    bottomBar = {\n                        NavigationBar {\n                            val navBackStackEntry by navController.currentBackStackEntryAsState()\n                            val currentDestination = navBackStackEntry?.destination\n\n                            TOP_LEVEL_ROUTES.forEach { topLevelRoute ->\n                                val label = stringResource(topLevelRoute.labelResId)\n                                NavigationBarItem(\n                                    icon = { Icon(topLevelRoute.icon, contentDescription = label) },\n                                    label = { Text(label) },\n                                    selected = currentDestination?.hierarchy?.any {\n                                        it.hasRoute(topLevelRoute.route::class)\n                                    } == true,\n                                    onClick = {\n                                        navController.navigate(\n                                            topLevelRoute.route,\n                                            { launchSingleTop = true }\n                                        )\n                                    }\n                                )\n                            }\n                        }\n                    }\n                ) { innerPadding ->\n                    NavHost(\n                        navController,\n                        startDestination = MoviesRoute,\n                        Modifier\n                            .padding(innerPadding)\n                            .consumeWindowInsets(innerPadding),\n                    ) {\n                        composable<MoviesRoute>() {\n                            MoviesScreen(\n                                onMovieClicked = { movieId ->\n                                    navController.navigate(\n                                        route = MovieDetailRoute(movieId),\n                                        builder = {\n                                            launchSingleTop = true\n                                        }\n                                    )\n                                }\n                            )\n                        }\n                        composable<MovieDetailRoute> {\n                            MovieDetailScreen(\n                                onActorClicked = { actorId ->\n                                    navController.navigate(\n                                        ActorDetailRoute(actorId),\n                                        { launchSingleTop = true }\n                                    )\n                                }\n                            )\n                        }\n                        composable<ActorDetailRoute>() {\n                            ActorDetailScreen(\n                                onMovieClicked = { movieId ->\n                                    navController.navigate(\n                                        MovieDetailRoute(movieId),\n                                        { launchSingleTop = true }\n                                    )\n                                }\n                            )\n                        }\n                        searchScreen()\n                        composable<ProfileRoute> { ProfileScreen(\n                            onMovieClicked = { movieId ->\n                                navController.navigate(\n                                    MovieDetailRoute(movieId),\n                                    { launchSingleTop = true }\n                                )\n                            }\n                        ) }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.actordetail\n\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport coil3.compose.AsyncImage\nimport com.google.firebase.dataconnect.movies.GetActorByIdQuery\nimport com.google.firebase.example.dataconnect.R\nimport com.google.firebase.example.dataconnect.ui.components.ErrorCard\nimport com.google.firebase.example.dataconnect.ui.components.LoadingScreen\nimport com.google.firebase.example.dataconnect.ui.components.Movie\nimport com.google.firebase.example.dataconnect.ui.components.MoviesList\nimport kotlinx.serialization.Serializable\n\n\n@Serializable\ndata class ActorDetailRoute(val actorId: String)\n\n@Composable\nfun ActorDetailScreen(\n    actorDetailViewModel: ActorDetailViewModel = viewModel(),\n    onMovieClicked: (actorId: String) -> Unit\n) {\n    val uiState by actorDetailViewModel.uiState.collectAsState()\n    ActorDetailScreen(\n        uiState = uiState,\n        onMovieClicked = onMovieClicked\n    )\n}\n\n@Composable\nfun ActorDetailScreen(\n    uiState: ActorDetailUIState,\n    onMovieClicked: (actorId: String) -> Unit\n) {\n    when (uiState) {\n        is ActorDetailUIState.Error -> ErrorCard(uiState.errorMessage)\n\n        is ActorDetailUIState.Loading -> LoadingScreen()\n\n        is ActorDetailUIState.Success -> {\n            Scaffold { innerPadding ->\n                val scrollState = rememberScrollState()\n                Column(\n                    modifier = Modifier\n                        .padding(innerPadding)\n                        .verticalScroll(scrollState)\n                ) {\n                    ActorInformation(\n                        actor = uiState.actor,\n                    )\n                    MoviesList(\n                        listTitle = stringResource(R.string.title_main_roles),\n                        movies = uiState.actor?.mainActors?.mapNotNull {\n                            Movie(it.id.toString(), it.imageUrl, it.title)\n                        },\n                        onMovieClicked = onMovieClicked\n                    )\n                    Spacer(modifier = Modifier.height(8.dp))\n                    MoviesList(\n                        listTitle = stringResource(R.string.title_supporting_actors),\n                        movies = uiState.actor?.supportingActors?.mapNotNull {\n                            Movie(it.id.toString(), it.imageUrl, it.title)\n                        },\n                        onMovieClicked = onMovieClicked\n                    )\n                }\n            }\n\n        }\n    }\n}\n\n@Composable\nfun ActorInformation(\n    actor: GetActorByIdQuery.Data.Actor?\n) {\n    if (actor == null) {\n        ErrorCard(stringResource(R.string.error_actor_not_found))\n    } else {\n        Row(\n            modifier = Modifier.padding(horizontal = 16.dp),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            AsyncImage(\n                model = actor.imageUrl,\n                contentDescription = null,\n                contentScale = ContentScale.Crop,\n                modifier = Modifier\n                    .width(120.dp)\n                    .padding(vertical = 8.dp)\n                    .clip(CircleShape)\n            )\n            Spacer(modifier = Modifier.width(16.dp))\n            Text(\n                text = actor.name,\n                style = MaterialTheme.typography.headlineLarge\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.actordetail\n\nimport com.google.firebase.dataconnect.movies.GetActorByIdQuery\nimport com.google.firebase.dataconnect.movies.GetMovieByIdQuery\n\n\nsealed class ActorDetailUIState {\n    data object Loading: ActorDetailUIState()\n\n    data class Error(val errorMessage: String?): ActorDetailUIState()\n\n    data class Success(\n        // Actor is null if it can't be found on the DB\n        val actor: GetActorByIdQuery.Data.Actor?,\n        val isUserSignedIn: Boolean = false,\n    ) : ActorDetailUIState()\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.actordetail\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.navigation.toRoute\nimport com.google.firebase.Firebase\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.auth\nimport com.google.firebase.dataconnect.movies.MoviesConnector\nimport com.google.firebase.dataconnect.movies.execute\nimport com.google.firebase.dataconnect.movies.instance\nimport java.util.UUID\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.launch\n\nclass ActorDetailViewModel(\n    savedStateHandle: SavedStateHandle\n) : ViewModel() {\n    private val actorDetailRoute = savedStateHandle.toRoute<ActorDetailRoute>()\n    private val actorId: String = actorDetailRoute.actorId\n\n    private val firebaseAuth: FirebaseAuth = Firebase.auth\n    private val moviesConnector: MoviesConnector = MoviesConnector.instance\n\n    private val _uiState = MutableStateFlow<ActorDetailUIState>(ActorDetailUIState.Loading)\n    val uiState: StateFlow<ActorDetailUIState>\n        get() = _uiState\n\n    init {\n        fetchActor()\n    }\n\n    private fun fetchActor() {\n        viewModelScope.launch {\n            try {\n                val user = firebaseAuth.currentUser\n                val actor = moviesConnector.getActorById.execute(\n                    id = UUID.fromString(actorId)\n                ).data.actor\n\n                _uiState.value = ActorDetailUIState.Success(\n                    actor = actor,\n                    isUserSignedIn = user != null\n                )\n            } catch (e: Exception) {\n                _uiState.value = ActorDetailUIState.Error(e.message)\n            }\n        }\n    }\n}"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.moviedetail\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.CheckCircle\nimport androidx.compose.material.icons.filled.Favorite\nimport androidx.compose.material.icons.outlined.Check\nimport androidx.compose.material.icons.outlined.FavoriteBorder\nimport androidx.compose.material.icons.outlined.Star\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Slider\nimport androidx.compose.material3.SuggestionChip\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextField\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport coil3.compose.AsyncImage\nimport com.google.firebase.dataconnect.movies.GetMovieByIdQuery\nimport com.google.firebase.example.dataconnect.R\nimport com.google.firebase.example.dataconnect.ui.components.Actor\nimport com.google.firebase.example.dataconnect.ui.components.ActorsList\nimport com.google.firebase.example.dataconnect.ui.components.ErrorCard\nimport com.google.firebase.example.dataconnect.ui.components.LoadingScreen\nimport com.google.firebase.example.dataconnect.ui.components.ReviewCard\nimport com.google.firebase.example.dataconnect.ui.components.ToggleButton\nimport kotlinx.serialization.Serializable\n\n@Serializable\ndata class MovieDetailRoute(val movieId: String)\n\n@Composable\nfun MovieDetailScreen(\n    onActorClicked: (actorId: String) -> Unit,\n    movieDetailViewModel: MovieDetailViewModel = viewModel()\n) {\n    val uiState by movieDetailViewModel.uiState.collectAsState()\n    Scaffold { padding ->\n        when (uiState) {\n            is MovieDetailUIState.Error -> {\n                ErrorCard((uiState as MovieDetailUIState.Error).errorMessage)\n            }\n\n            MovieDetailUIState.Loading -> LoadingScreen()\n\n            is MovieDetailUIState.Success -> {\n                val ui = uiState as MovieDetailUIState.Success\n                val movie = ui.movie\n                val scrollState = rememberScrollState()\n                Column(\n                    modifier = Modifier.verticalScroll(scrollState)\n                ) {\n                    MovieInformation(\n                        modifier = Modifier.padding(padding),\n                        movie = movie,\n                        isMovieFavorite = ui.isFavorite,\n                        onFavoriteToggled = { newValue ->\n                            movieDetailViewModel.toggleFavorite(newValue)\n                        },\n                    )\n                    // Main Actors list\n                    ActorsList(\n                        listTitle = stringResource(R.string.title_main_actors),\n                        actors = movie?.mainActors?.mapNotNull {\n                            Actor(it.id.toString(), it.name, it.imageUrl)\n                        },\n                        onActorClicked = { onActorClicked(it) }\n                    )\n                    // Supporting Actors list\n                    ActorsList(\n                        listTitle = stringResource(R.string.title_supporting_actors),\n                        actors = movie?.supportingActors?.mapNotNull {\n                            Actor(it.id.toString(), it.name, it.imageUrl)\n                        },\n                        onActorClicked = { onActorClicked(it) }\n                    )\n                    UserReviews(\n                        onReviewSubmitted = { rating, text ->\n                            movieDetailViewModel.addRating(rating, text)\n                        },\n                        movie?.reviews\n                    )\n                }\n\n            }\n        }\n    }\n}\n\n@Composable\nfun MovieInformation(\n    modifier: Modifier = Modifier,\n    movie: GetMovieByIdQuery.Data.Movie?,\n    isMovieFavorite: Boolean,\n    onFavoriteToggled: (newValue: Boolean) -> Unit\n) {\n    if (movie == null) {\n        ErrorCard(stringResource(R.string.error_movie_not_found))\n    } else {\n        Column(\n            modifier = modifier\n                .padding(16.dp)\n        ) {\n            Text(\n                text = movie.title,\n                style = MaterialTheme.typography.headlineLarge\n            )\n            Row(\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                Text(\n                    text = movie.releaseYear.toString(),\n                    style = MaterialTheme.typography.bodyLarge,\n                    modifier = Modifier.padding(end = 4.dp)\n                )\n                Spacer(modifier = Modifier.width(8.dp))\n                Icon(Icons.Outlined.Star, \"Favorite\")\n                Text(\n                    text = movie.rating?.toString() ?: \"0.0\",\n                    style = MaterialTheme.typography.bodyLarge,\n                    modifier = Modifier.padding(start = 2.dp)\n                )\n            }\n            Row {\n                AsyncImage(\n                    model = movie.imageUrl,\n                    contentDescription = null,\n                    contentScale = ContentScale.Crop,\n                    modifier = Modifier\n                        .width(150.dp)\n                        .aspectRatio(9f / 16f)\n                        .padding(vertical = 8.dp)\n                )\n                Column(\n                    modifier = Modifier.padding(horizontal = 16.dp)\n                ) {\n                    Row {\n                        movie.tags?.let { movieTags ->\n                            movieTags.filterNotNull().forEach { tag ->\n                                SuggestionChip(\n                                    onClick = { },\n                                    label = { Text(tag) },\n                                    modifier = Modifier\n                                        .padding(horizontal = 4.dp)\n                                )\n                            }\n                        }\n                    }\n                    Text(\n                        text = movie.description ?: stringResource(R.string.description_not_available),\n                        modifier = Modifier.fillMaxWidth()\n                    )\n                }\n            }\n            Spacer(modifier = Modifier.height(8.dp))\n            ToggleButton(\n                iconEnabled = Icons.Filled.Favorite,\n                iconDisabled = Icons.Outlined.FavoriteBorder,\n                textEnabled = stringResource(R.string.button_remove_favorite),\n                textDisabled = stringResource(R.string.button_favorite),\n                isEnabled = isMovieFavorite,\n                onToggle = onFavoriteToggled\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.moviedetail\n\nimport com.google.firebase.dataconnect.movies.GetMovieByIdQuery\n\n\nsealed class MovieDetailUIState {\n    data object Loading: MovieDetailUIState()\n\n    data class Error(val errorMessage: String?): MovieDetailUIState()\n\n    data class Success(\n        // Movie is null if it can't be found on the DB\n        val movie: GetMovieByIdQuery.Data.Movie?,\n        val isUserSignedIn: Boolean = false,\n        var isFavorite: Boolean = false\n    ) : MovieDetailUIState()\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.moviedetail\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport androidx.navigation.toRoute\nimport com.google.firebase.Firebase\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.auth\nimport com.google.firebase.dataconnect.movies.MoviesConnector\nimport com.google.firebase.dataconnect.movies.execute\nimport com.google.firebase.dataconnect.movies.instance\nimport java.util.UUID\nimport kotlin.math.roundToInt\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\nclass MovieDetailViewModel(\n    savedStateHandle: SavedStateHandle\n) : ViewModel() {\n    private val movieDetailRoute = savedStateHandle.toRoute<MovieDetailRoute>()\n    private val movieId: String = movieDetailRoute.movieId\n\n    private val firebaseAuth: FirebaseAuth = Firebase.auth\n    private val moviesConnector: MoviesConnector = MoviesConnector.instance\n\n    private val _uiState = MutableStateFlow<MovieDetailUIState>(MovieDetailUIState.Loading)\n    val uiState: StateFlow<MovieDetailUIState>\n        get() = _uiState\n\n    init {\n        fetchMovie()\n    }\n\n    private fun fetchMovie() {\n        viewModelScope.launch {\n            try {\n                val user = firebaseAuth.currentUser\n                val movie = moviesConnector.getMovieById.execute(\n                    id = UUID.fromString(movieId)\n                ).data.movie\n\n                _uiState.value = if (user == null) {\n                    MovieDetailUIState.Success(movie, isUserSignedIn = false)\n                } else {\n                    val isFavorite = moviesConnector.getIfFavoritedMovie.execute(\n                        movieId = UUID.fromString(movieId)\n                    ).data.favorite_movie != null\n\n                    MovieDetailUIState.Success(\n                        movie = movie,\n                        isUserSignedIn = true,\n                        isFavorite = isFavorite\n                    )\n                }\n            } catch (e: Exception) {\n                _uiState.value = MovieDetailUIState.Error(e.message)\n            }\n        }\n    }\n\n    fun toggleFavorite(newValue: Boolean) {\n        // TODO(thatfiredev): hide the button if there's no user logged in\n        val uid = firebaseAuth.currentUser?.uid ?: return\n        viewModelScope.launch {\n            try {\n                if (newValue) {\n                    moviesConnector.addFavoritedMovie.execute(UUID.fromString(movieId))\n                } else {\n                    moviesConnector.deleteFavoritedMovie.execute(\n                        movieId = UUID.fromString(movieId)\n                    )\n                }\n                // Re-run the query to fetch movie\n                fetchMovie()\n            } catch (e: Exception) {\n                _uiState.value = MovieDetailUIState.Error(e.message)\n            }\n        }\n    }\n\n    fun addRating(rating: Float, text: String) {\n        // TODO(thatfiredev): hide the button if there's no user logged in\n        if (firebaseAuth.currentUser?.uid == null) return\n        viewModelScope.launch {\n            try {\n                moviesConnector.addReview.execute(\n                    movieId = UUID.fromString(movieId),\n                    rating = rating.roundToInt(),\n                    reviewText = text\n                )\n                // Re-run the query to fetch movie\n                fetchMovie()\n            } catch (e: Exception) {\n                _uiState.value = MovieDetailUIState.Error(e.message)\n            }\n        }\n    }\n}"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.moviedetail\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Slider\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextField\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport com.google.firebase.dataconnect.movies.GetMovieByIdQuery\nimport com.google.firebase.example.dataconnect.R\nimport com.google.firebase.example.dataconnect.ui.components.ReviewCard\n\n@Composable\nfun UserReviews(\n    onReviewSubmitted: (rating: Float, text: String) -> Unit,\n    reviews: List<GetMovieByIdQuery.Data.Movie.ReviewsItem>? = emptyList()\n) {\n    var reviewText by remember { mutableStateOf(\"\") }\n    Text(\n        text = \"User Reviews\",\n        style = MaterialTheme.typography.headlineMedium,\n        modifier = Modifier.padding(horizontal = 16.dp)\n    )\n    Spacer(modifier = Modifier.height(8.dp))\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n            .padding(16.dp),\n        horizontalAlignment = Alignment.CenterHorizontally\n    ) {\n        var rating by remember { mutableFloatStateOf(5f) }\n        Text(\"Rating: ${rating}\")\n        Slider(\n            value = rating,\n            onValueChange = { rating = Math.round(it).toFloat() },\n            steps = 10,\n            valueRange = 1f..10f\n        )\n        TextField(\n            value = reviewText,\n            onValueChange = { if (it.length <= 280) reviewText = it },\n            label = { Text(stringResource(R.string.hint_write_review)) },\n            supportingText = {\n                Text(\n                    \"${reviewText.length} / 280\",\n                    modifier = Modifier.fillMaxWidth(),\n                    textAlign = TextAlign.End\n                )\n            },\n            modifier = Modifier.fillMaxWidth()\n        )\n\n        Spacer(modifier = Modifier.height(16.dp))\n\n        Button(\n            onClick = {\n                if (!reviewText.isNullOrEmpty()) {\n                    onReviewSubmitted(rating, reviewText)\n                    reviewText = \"\"\n                }\n            }\n        ) {\n            Text(stringResource(R.string.button_submit_review))\n        }\n    }\n    Column {\n        // TODO(thatfiredev): Handle cases where the list is too long to display\n        reviews.orEmpty().forEach {\n            ReviewCard(\n                userName = it.user.username,\n                date = it.reviewDate,\n                rating = it.rating?.toDouble() ?: 0.0,\n                text = it.reviewText ?: \"\",\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.movies\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.google.firebase.example.dataconnect.R\nimport com.google.firebase.example.dataconnect.ui.components.ErrorCard\nimport com.google.firebase.example.dataconnect.ui.components.LoadingScreen\nimport com.google.firebase.example.dataconnect.ui.components.Movie\nimport com.google.firebase.example.dataconnect.ui.components.MoviesList\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject MoviesRoute\n\n@Composable\nfun MoviesScreen(\n    onMovieClicked: (movie: String) -> Unit,\n    moviesViewModel: MoviesViewModel = viewModel()\n) {\n    val movies by moviesViewModel.uiState.collectAsState()\n    MoviesScreen(movies, onMovieClicked)\n}\n\n@Composable\nfun MoviesScreen(\n    uiState: MoviesUIState,\n    onMovieClicked: (movie: String) -> Unit\n) {\n    when (uiState) {\n        MoviesUIState.Loading -> LoadingScreen()\n        is MoviesUIState.Error -> ErrorCard(uiState.errorMessage)\n        is MoviesUIState.Success -> {\n            val scrollState = rememberScrollState()\n            Column(\n                modifier = Modifier\n                    .verticalScroll(scrollState)\n            ) {\n                MoviesList(\n                    listTitle = stringResource(R.string.title_top_10_movies),\n                    movies = uiState.top10movies.mapNotNull {\n                        Movie(it.id.toString(), it.imageUrl, it.title, it.rating?.toFloat())\n                    },\n                    onMovieClicked = onMovieClicked\n                )\n                Spacer(modifier = Modifier.height(16.dp))\n                MoviesList(\n                    listTitle = stringResource(R.string.title_latest_movies),\n                    movies = uiState.latestMovies.mapNotNull {\n                        Movie(it.id.toString(), it.imageUrl, it.title, it.rating?.toFloat())\n                    },\n                    onMovieClicked = onMovieClicked\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.movies\n\nimport com.google.firebase.dataconnect.movies.ListMoviesQuery\n\nsealed class MoviesUIState {\n\n    data object Loading: MoviesUIState()\n\n    data class Error(val errorMessage: String?): MoviesUIState()\n\n    data class Success(\n        val top10movies: List<ListMoviesQuery.Data.MoviesItem>,\n        val latestMovies: List<ListMoviesQuery.Data.MoviesItem>\n    ) : MoviesUIState()\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.movies\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.firebase.dataconnect.movies.MoviesConnector\nimport com.google.firebase.dataconnect.movies.OrderDirection\nimport com.google.firebase.dataconnect.movies.execute\nimport com.google.firebase.dataconnect.movies.instance\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.launch\n\nclass MoviesViewModel(\n    private val moviesConnector: MoviesConnector = MoviesConnector.instance\n) : ViewModel() {\n\n    private val _uiState = MutableStateFlow<MoviesUIState>(MoviesUIState.Loading)\n    val uiState: StateFlow<MoviesUIState>\n        get() = _uiState\n\n    init {\n        viewModelScope.launch {\n            try {\n                val top10Movies = moviesConnector.listMovies.execute {\n                    orderByRating = OrderDirection.DESC\n                    limit = 10\n                }.data.movies\n                val latestMovies = moviesConnector.listMovies.execute {\n                    orderByReleaseYear = OrderDirection.DESC\n                }.data.movies\n\n                _uiState.value = MoviesUIState.Success(top10Movies, latestMovies)\n            } catch (e: Exception) {\n                _uiState.value = MoviesUIState.Error(e.localizedMessage)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/AuthScreen.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.profile\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.input.PasswordVisualTransformation\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun AuthScreen(\n    onSignUp: (email: String, password: String, displayName: String) -> Unit,\n    onSignIn: (email: String, password: String) -> Unit,\n) {\n    var isSignUp by remember { mutableStateOf(false) }\n    var email by remember { mutableStateOf(\"\") }\n    var password by remember { mutableStateOf(\"\") }\n    var displayName by remember { mutableStateOf(\"\") }\n\n    Column(\n        modifier = Modifier.fillMaxSize(),\n        verticalArrangement = Arrangement.Center,\n        horizontalAlignment = Alignment.CenterHorizontally\n    ) {\n        OutlinedTextField(\n            value = email,\n            onValueChange = { email = it },\n            label = { Text(\"Email\") }\n        )\n        Spacer(modifier = Modifier.height(8.dp))\n        OutlinedTextField(\n            value = password,\n            onValueChange = { password = it },\n            label = { Text(\"Password\") },\n            visualTransformation = PasswordVisualTransformation()\n        )\n        Spacer(modifier = Modifier.height(8.dp))\n        if (isSignUp) {\n            OutlinedTextField(\n                value = displayName,\n                onValueChange = { displayName = it },\n                label = { Text(\"Name\") }\n            )\n        }\n        Spacer(modifier = Modifier.height(16.dp))\n        Button(onClick = {\n            if (isSignUp) {\n                onSignUp(email, password, displayName)\n            } else {\n                onSignIn(email, password)\n            }\n        }) {\n            Text(\n                text = if (isSignUp) {\n                    \"Sign up\"\n                } else {\n                    \"Sign in\"\n                }\n            )\n        }\n\n        Spacer(modifier = Modifier.height(16.dp))\n        Text(\n            text = if (isSignUp) {\n                \"Already have an account?\"\n            } else {\n                \"Don't have an account?\"\n            }\n        )\n        Spacer(modifier = Modifier.height(8.dp))\n        Button(onClick = {\n            isSignUp = !isSignUp\n        }) {\n            Text(\n                text = if (isSignUp) {\n                    \"Sign in\"\n                } else {\n                    \"Sign up\"\n                }\n            )\n        }\n    }\n}"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.profile\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.google.firebase.dataconnect.movies.GetCurrentUserQuery\nimport com.google.firebase.example.dataconnect.R\nimport com.google.firebase.example.dataconnect.ui.components.Actor\nimport com.google.firebase.example.dataconnect.ui.components.ActorsList\nimport com.google.firebase.example.dataconnect.ui.components.ErrorCard\nimport com.google.firebase.example.dataconnect.ui.components.LoadingScreen\nimport com.google.firebase.example.dataconnect.ui.components.Movie\nimport com.google.firebase.example.dataconnect.ui.components.MoviesList\nimport com.google.firebase.example.dataconnect.ui.components.ReviewCard\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject ProfileRoute\n\n@Composable\nfun ProfileScreen(\n    profileViewModel: ProfileViewModel = viewModel(),\n    onMovieClicked: (String) -> Unit,\n) {\n    val uiState by profileViewModel.uiState.collectAsState()\n    when (uiState) {\n        is ProfileUIState.Error -> {\n            ErrorCard((uiState as ProfileUIState.Error).errorMessage)\n        }\n\n        is ProfileUIState.AuthState -> {\n            AuthScreen(\n                onSignUp = { email, password, displayName ->\n                    profileViewModel.signUp(email, password, displayName)\n                },\n                onSignIn = { email, password ->\n                    profileViewModel.signIn(email, password)\n                }\n            )\n        }\n\n        is ProfileUIState.ProfileState -> {\n            val ui = uiState as ProfileUIState.ProfileState\n            ProfileScreen(\n                ui.username ?: \"User\",\n                ui.reviews.orEmpty(),\n                ui.favoriteMovies.orEmpty(),\n                onMovieClicked = onMovieClicked,\n                onSignOut = {\n                    profileViewModel.signOut()\n                }\n            )\n        }\n\n        ProfileUIState.Loading -> LoadingScreen()\n    }\n}\n\n@Composable\nfun ProfileScreen(\n    name: String,\n    reviews: List<GetCurrentUserQuery.Data.User.ReviewsItem>,\n    favoriteMovies: List<GetCurrentUserQuery.Data.User.FavoriteMoviesItem>,\n    onMovieClicked: (String) -> Unit,\n    onSignOut: () -> Unit\n) {\n    val scrollState = rememberScrollState()\n    Column(\n        modifier = Modifier\n            .padding(vertical = 16.dp)\n            .verticalScroll(scrollState)\n    ) {\n        Text(\n            text = \"Welcome back, $name!\",\n            style = MaterialTheme.typography.displaySmall,\n            modifier = Modifier.padding(horizontal = 16.dp)\n        )\n        TextButton(\n            onClick = {\n                onSignOut()\n            },\n            modifier = Modifier.padding(horizontal = 16.dp)\n        ) {\n            Text(\"Sign out\")\n        }\n        Spacer(modifier = Modifier.height(16.dp))\n\n        MoviesList(\n            listTitle = stringResource(R.string.title_favorite_movies),\n            movies = favoriteMovies.mapNotNull {\n                Movie(it.movie.id.toString(), it.movie.imageUrl, it.movie.title, it.movie.rating?.toFloat())\n            },\n            onMovieClicked = onMovieClicked\n        )\n        Spacer(modifier = Modifier.height(16.dp))\n\n        ProfileSection(title = \"Reviews\", content = { ReviewsList(name, reviews) })\n        Spacer(modifier = Modifier.height(16.dp))\n    }\n}\n\n@Composable\nfun ProfileSection(title: String, content: @Composable () -> Unit) {\n    Column {\n        Text(\n            text = title,\n            style = MaterialTheme.typography.headlineMedium,\n            modifier = Modifier.padding(horizontal = 16.dp)\n        )\n        Spacer(modifier = Modifier.height(8.dp))\n        content()\n    }\n}\n\n@Composable\nfun ReviewsList(\n    userName: String,\n    reviews: List<GetCurrentUserQuery.Data.User.ReviewsItem>\n) {\n    Column {\n        // TODO(thatfiredev): Handle cases where the list is too long to display\n        reviews.forEach { review ->\n            ReviewCard(\n                userName = userName,\n                date = review.reviewDate,\n                rating = review.rating?.toDouble() ?: 0.0,\n                text = review.reviewText ?: \"\",\n                movieName = review.movie.title\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.profile\n\nimport com.google.firebase.dataconnect.movies.GetCurrentUserQuery\n\nsealed class ProfileUIState {\n    data object Loading: ProfileUIState()\n\n    data class Error(val errorMessage: String?): ProfileUIState()\n\n    data object AuthState: ProfileUIState()\n\n    data class ProfileState(\n        val username: String?,\n        val reviews: List<GetCurrentUserQuery.Data.User.ReviewsItem>? = emptyList(),\n        val favoriteMovies: List<GetCurrentUserQuery.Data.User.FavoriteMoviesItem>? = emptyList(),\n    ) : ProfileUIState()\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.profile\n\nimport android.util.Log\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.firebase.Firebase\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseAuth.AuthStateListener\nimport com.google.firebase.auth.UserProfileChangeRequest\nimport com.google.firebase.auth.auth\nimport com.google.firebase.dataconnect.movies.MoviesConnector\nimport com.google.firebase.dataconnect.movies.execute\nimport com.google.firebase.dataconnect.movies.instance\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.tasks.await\n\nclass ProfileViewModel(\n    private val auth: FirebaseAuth = Firebase.auth,\n    private val moviesConnector: MoviesConnector = MoviesConnector.instance\n) : ViewModel() {\n    private val _uiState = MutableStateFlow<ProfileUIState>(ProfileUIState.Loading)\n    val uiState: StateFlow<ProfileUIState>\n        get() = _uiState\n\n    private val authStateListener: AuthStateListener\n\n    init {\n        authStateListener = AuthStateListener {\n            val currentUser = auth.currentUser\n            if (currentUser != null) {\n                displayUser(currentUser.uid)\n            } else {\n                _uiState.value = ProfileUIState.AuthState\n            }\n        }\n        auth.addAuthStateListener(authStateListener)\n    }\n\n    fun signUp(\n        email: String,\n        password: String,\n        displayName: String\n    ) {\n        viewModelScope.launch {\n            try {\n                val signInResult = auth.createUserWithEmailAndPassword(email, password).await()\n                signInResult.user?.updateProfile(\n                    UserProfileChangeRequest.Builder()\n                        .setDisplayName(displayName)\n                        .build()\n                )?.await()\n                moviesConnector.upsertUser.execute(username = displayName)\n            } catch (e: Exception) {\n                _uiState.value = ProfileUIState.Error(e.message)\n                e.printStackTrace()\n            }\n        }\n    }\n\n    fun signIn(email: String, password: String) {\n        viewModelScope.launch {\n            try {\n                auth.signInWithEmailAndPassword(email, password).await()\n            } catch (e: Exception) {\n                _uiState.value = ProfileUIState.Error(e.message)\n            }\n        }\n    }\n\n    fun signOut() {\n        auth.signOut()\n    }\n\n    private fun displayUser(\n        userId: String\n    ) {\n        viewModelScope.launch {\n            try {\n                val user = moviesConnector.getCurrentUser.execute().data.user\n                _uiState.value = ProfileUIState.ProfileState(\n                    user?.username,\n                    favoriteMovies = user?.favoriteMovies,\n                    reviews = user?.reviews\n                )\n                Log.d(\"DisplayUser\", \"$user\")\n            } catch (e: Exception) {\n                _uiState.value = ProfileUIState.Error(e.message)\n            }\n        }\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        auth.removeAuthStateListener(authStateListener)\n    }\n}"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/search/Navigation.kt",
    "content": "package com.google.firebase.example.dataconnect.feature.search\n\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavOptionsBuilder\nimport androidx.navigation.compose.composable\n\nconst val SEARCH_ROUTE = \"search_route\"\n\nfun NavController.navigateToSearch(navOptions: NavOptionsBuilder.() -> Unit) =\n    navigate(SEARCH_ROUTE, navOptions)\n\nfun NavGraphBuilder.searchScreen(\n\n) {\n    composable(route = SEARCH_ROUTE) {\n        // TODO: Call composable\n    }\n}\n\n\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.components\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.sizeIn\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport coil3.compose.AsyncImage\n\nval ACTOR_CARD_SIZE = 64.dp\n\n/**\n * Used to represent an actor in a list UI\n */\ndata class Actor(\n    val id: String,\n    val name: String,\n    val imageUrl: String\n)\n\n/**\n * Displays a scrollable horizontal list of actors.\n */\n@Composable\nfun ActorsList(\n    modifier: Modifier = Modifier,\n    listTitle: String,\n    actors: List<Actor>? = emptyList(),\n    onActorClicked: (actorId: String) -> Unit\n) {\n    Column(\n        modifier = modifier.padding(horizontal = 16.dp)\n            .fillMaxWidth()\n    ) {\n        Text(\n            text = listTitle,\n            style = MaterialTheme.typography.headlineMedium\n        )\n        Spacer(modifier = Modifier.height(8.dp))\n        LazyRow {\n            items(actors.orEmpty()) { actor ->\n                ActorTile(actor, onActorClicked)\n            }\n        }\n    }\n}\n\n/**\n * Used to display each actor item in the list.\n */\n@Composable\nfun ActorTile(\n    actor: Actor,\n    onActorClicked: (actorId: String) -> Unit\n) {\n    Card(\n        modifier = Modifier\n            .padding(end = 8.dp)\n            .clickable {\n                onActorClicked(actor.id)\n            }\n    ) {\n        Row(\n            verticalAlignment = Alignment.CenterVertically,\n            modifier = Modifier\n                .sizeIn(\n                    maxWidth = 160.dp,\n                    maxHeight = ACTOR_CARD_SIZE + 16.dp\n                )\n                .padding(8.dp)\n                .fillMaxWidth()\n        ) {\n            AsyncImage(\n                model = actor.imageUrl,\n                contentDescription = null,\n                contentScale = ContentScale.Crop,\n                modifier = Modifier\n                    .padding(end = 8.dp)\n                    .size(ACTOR_CARD_SIZE)\n                    .clip(CircleShape)\n            )\n            Text(\n                text = actor.name,\n                style = MaterialTheme.typography.bodyLarge,\n                maxLines = 2,\n                overflow = TextOverflow.Ellipsis,\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.components\n\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport com.google.firebase.example.dataconnect.R\n\n@Composable\nfun ErrorCard(\n    errorMessage: String?\n) {\n    Card(\n        colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),\n        modifier = Modifier.padding(16.dp)\n            .fillMaxWidth()\n    ) {\n        Text(\n            text = errorMessage ?: stringResource(R.string.unknown_error),\n            modifier = Modifier.padding(16.dp)\n                .fillMaxWidth()\n        )\n    }\n}\n\n@Composable\n@Preview\nfun ErrorCardPreview() {\n    ErrorCard(\"Something went terribly wrong :(\")\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/LoadingScreen.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.components\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\n\n/**\n * A screen that displays a loading spinner in the center.\n */\n@Composable\nfun LoadingScreen() {\n    Box(\n        contentAlignment = Alignment.Center,\n        modifier = Modifier.fillMaxSize()\n    ) {\n        CircularProgressIndicator()\n    }\n}"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.components\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.sizeIn\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport coil3.compose.AsyncImage\n\n/**\n * Used to represent a movie in a list UI\n */\ndata class Movie(\n    val id: String,\n    val imageUrl: String,\n    val title: String,\n    val rating: Float? = null\n)\n\n/**\n * Displays a scrollable horizontal list of movies.\n */\n@Composable\nfun MoviesList(\n    modifier: Modifier = Modifier,\n    listTitle: String,\n    movies: List<Movie>? = emptyList(),\n    onMovieClicked: (movieId: String) -> Unit\n) {\n    Column(\n        modifier = modifier.padding(horizontal = 16.dp)\n            .fillMaxWidth()\n    ) {\n        Text(\n            text = listTitle,\n            style = MaterialTheme.typography.headlineMedium,\n            modifier = Modifier.padding(bottom = 8.dp)\n        )\n        LazyRow {\n            items(movies.orEmpty()) { movie ->\n                MovieTile(\n                    movie = movie,\n                    onMovieClicked = {\n                        onMovieClicked(movie.id.toString())\n                    }\n                )\n            }\n        }\n    }\n}\n\n/**\n * Used to display each movie item in the list.\n */\n@Composable\nfun MovieTile(\n    modifier: Modifier = Modifier,\n    tileWidth: Dp = 150.dp,\n    movie: Movie,\n    onMovieClicked: (movieId: String) -> Unit\n) {\n    Card(\n        modifier = modifier\n            .padding(4.dp)\n            .sizeIn(maxWidth = tileWidth)\n            .clickable {\n                onMovieClicked(movie.id)\n            },\n    ) {\n        AsyncImage(\n            model = movie.imageUrl,\n            contentDescription = null,\n            contentScale = ContentScale.Crop,\n            modifier = Modifier.aspectRatio(9f / 16f)\n        )\n        Text(\n            text = movie.title,\n            style = MaterialTheme.typography.titleMedium,\n            modifier = Modifier.padding(8.dp),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n        movie.rating?.let {\n            Text(\n                text = \"Rating: $it\",\n                modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp),\n                style = MaterialTheme.typography.bodySmall\n            )\n        }\n    }\n}"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.components\n\nimport android.os.Build\nimport android.widget.Space\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.semantics.text\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport com.google.firebase.dataconnect.LocalDate\nimport com.google.firebase.dataconnect.toJavaLocalDate\nimport java.text.SimpleDateFormat\nimport java.time.format.DateTimeFormatter\nimport java.util.Locale\n\n\n@Composable\nfun ReviewCard(\n    userName: String,\n    date: LocalDate,\n    rating: Double,\n    text: String,\n    movieName: String? = null\n) {\n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(8.dp)\n    ) {\n        Column(\n            modifier = Modifier\n                .background(color = MaterialTheme.colorScheme.secondaryContainer)\n                .padding(16.dp)\n        ) {\n            Text(\n                text = if (movieName != null) {\n                    userName + \" on \" + movieName\n                } else {\n                    userName\n                },\n                fontWeight = FontWeight.Bold,\n                style = MaterialTheme.typography.titleLarge\n            )\n            Row(\n                modifier = Modifier.padding(bottom = 8.dp)\n            ) {\n                Text(\n                    text =\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                            val dateFormatter = DateTimeFormatter.ofPattern(\"dd MMM, yyyy\", Locale.getDefault())\n                            date.toJavaLocalDate().format(dateFormatter)\n                        } else {\n                            val parseableDateString = date.run {\n                                val year = \"$year\".padStart(4, '0')\n                                val month = \"$month\".padStart(2, '0')\n                                val day = \"$day\".padStart(2, '0')\n                                \"$year-$month-$day\"\n                            }\n                            val dateParser = SimpleDateFormat(\"y-M-d\", Locale.US)\n                            val parsedDate = dateParser.parse(parseableDateString) ?:\n                              throw Exception(\"INTERNAL ERROR: unparseable date string: $parseableDateString\")\n                            val dateFormatter = SimpleDateFormat(\"dd MMM, yyyy\", Locale.getDefault())\n                            dateFormatter.format(parsedDate)\n                        },\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Spacer(modifier = Modifier.width(8.dp))\n                Text(\n                    text = \"Rating: \",\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Text(\n                    text = \"$rating\",\n                    style = MaterialTheme.typography.titleMedium\n                )\n            }\n            Text(\n                text = text,\n                modifier = Modifier.fillMaxWidth()\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ToggleButton.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.components\n\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.OutlinedButton\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun ToggleButton(\n    iconEnabled: ImageVector,\n    iconDisabled: ImageVector,\n    textEnabled: String,\n    textDisabled: String,\n    isEnabled: Boolean,\n    onToggle: (newValue: Boolean) -> Unit\n) {\n    val onClick = {\n        onToggle(!isEnabled)\n    }\n    if (isEnabled) {\n        FilledTonalButton(onClick) {\n            Icon(iconEnabled, textEnabled)\n            Text(textEnabled, modifier = Modifier.padding(horizontal = 4.dp))\n        }\n    } else {\n        OutlinedButton(onClick) {\n            Icon(iconDisabled, textDisabled)\n            Text(textDisabled, modifier = Modifier.padding(horizontal = 4.dp))\n        }\n    }\n}\n"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Color.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.theme\n\nimport androidx.compose.ui.graphics.Color\n\nval Purple80 = Color(0xFFD0BCFF)\nval PurpleGrey80 = Color(0xFFCCC2DC)\nval Pink80 = Color(0xFFEFB8C8)\n\nval Purple40 = Color(0xFF6650a4)\nval PurpleGrey40 = Color(0xFF625b71)\nval Pink40 = Color(0xFF7D5260)"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Theme.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.theme\n\nimport android.app.Activity\nimport android.os.Build\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.dynamicDarkColorScheme\nimport androidx.compose.material3.dynamicLightColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.platform.LocalContext\n\nprivate val DarkColorScheme = darkColorScheme(\n    primary = Purple80,\n    secondary = PurpleGrey80,\n    tertiary = Pink80\n)\n\nprivate val LightColorScheme = lightColorScheme(\n    primary = Purple40,\n    secondary = PurpleGrey40,\n    tertiary = Pink40\n\n    /* Other default colors to override\n    background = Color(0xFFFFFBFE),\n    surface = Color(0xFFFFFBFE),\n    onPrimary = Color.White,\n    onSecondary = Color.White,\n    onTertiary = Color.White,\n    onBackground = Color(0xFF1C1B1F),\n    onSurface = Color(0xFF1C1B1F),\n    */\n)\n\n@Composable\nfun FirebaseDataConnectTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    // Dynamic color is available on Android 12+\n    dynamicColor: Boolean = true,\n    content: @Composable () -> Unit\n) {\n    val colorScheme = when {\n        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {\n            val context = LocalContext.current\n            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)\n        }\n\n        darkTheme -> DarkColorScheme\n        else -> LightColorScheme\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}"
  },
  {
    "path": "dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Type.kt",
    "content": "package com.google.firebase.example.dataconnect.ui.theme\n\nimport androidx.compose.material3.Typography\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.sp\n\n// Set of Material typography styles to start with\nval Typography = Typography(\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp,\n        letterSpacing = 0.5.sp\n    )\n    /* Other default text styles to override\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 22.sp,\n        lineHeight = 28.sp,\n        letterSpacing = 0.sp\n    ),\n    labelSmall = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Medium,\n        fontSize = 11.sp,\n        lineHeight = 16.sp,\n        letterSpacing = 0.5.sp\n    )\n    */\n)"
  },
  {
    "path": "dataconnect/app/src/main/res/drawable/firebase_data_connect.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"192dp\"\n    android:height=\"192dp\"\n    android:viewportWidth=\"192\"\n    android:viewportHeight=\"192\">\n  <group>\n    <clip-path\n        android:pathData=\"M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0\"/>\n  </group>\n  <path\n      android:pathData=\"M97.07,126.87l-22.22,-22.22c3.8,-6.69 2.85,-15.35 -2.86,-21.06c-6.85,-6.85 -17.95,-6.85 -24.8,0c-6.85,6.85 -6.85,17.95 0,24.8c5.71,5.71 14.36,6.66 21.06,2.86l22.22,22.22c6.65,6.66 15.37,6.71 21.03,2.06c5.69,-4.67 7.18,-13.16 1.67,-20.75l-27,-42.48l-0.1,-0.13c-2.81,-3.81 -1.65,-6.82 0.17,-8.32c1.87,-1.54 5.26,-2.16 8.68,1.27l15.97,15.97c1.82,1.82 4.78,1.82 6.6,0c1.82,-1.82 1.82,-4.78 0,-6.6l-15.97,-15.97c-6.73,-6.73 -15.55,-6.55 -21.22,-1.87c-5.69,4.69 -7.41,13.25 -1.84,20.92l27,42.48l0.1,0.13c2.88,3.9 1.65,6.79 0.02,8.13c-1.69,1.39 -5.01,2.06 -8.51,-1.44zM53.8,101.8c3.2,3.2 8.4,3.2 11.6,0c3.2,-3.2 3.2,-8.4 0,-11.6c-3.2,-3.2 -8.4,-3.2 -11.6,0c-3.2,3.2 -3.2,8.4 0,11.6zM120.27,83.87c-6.7,6.7 -6.7,17.57 0,24.27c6.7,6.7 17.57,6.7 24.27,0c6.7,-6.7 6.7,-17.57 0,-24.27c-6.7,-6.7 -17.57,-6.7 -24.27,0z\"\n      android:strokeLineJoin=\"miter\"\n      android:strokeWidth=\"1\"\n      android:fillColor=\"#ffffff\"\n      android:fillType=\"evenOdd\"\n      android:strokeColor=\"#00000000\"\n      android:strokeLineCap=\"butt\"/>\n</vector>\n"
  },
  {
    "path": "dataconnect/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/firebase_data_connect\" />\n    <monochrome android:drawable=\"@drawable/firebase_data_connect\" />\n</adaptive-icon>"
  },
  {
    "path": "dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/firebase_data_connect\" />\n    <monochrome android:drawable=\"@drawable/firebase_data_connect\" />\n</adaptive-icon>"
  },
  {
    "path": "dataconnect/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n</resources>"
  },
  {
    "path": "dataconnect/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase Data Connect</string>\n    <string name=\"unknown_error\">An unknown error occurred</string>\n\n    <!-- NavBar labels -->\n    <string name=\"label_movies\">Movies</string>\n    <string name=\"label_genres\">Genres</string>\n    <string name=\"label_search\">Search</string>\n    <string name=\"label_profile\">Profile</string>\n\n    <!-- Movies Screen -->\n    <string name=\"title_top_10_movies\">Top 10 Movies</string>\n    <string name=\"title_latest_movies\">Latest Movies</string>\n\n    <!-- Genre Detail Screen -->\n    <string name=\"title_genre_detail\">%s Movies</string>\n    <string name=\"title_most_popular\">Most Popular</string>\n    <string name=\"title_most_recent\">Most Recent</string>\n\n    <!-- Movie Detail Screen -->\n    <string name=\"error_movie_not_found\">Couldn\\'t find movie in the database</string>\n    <string name=\"description_not_available\">Description not available</string>\n    <string name=\"button_mark_watched\">Mark as watched</string>\n    <string name=\"button_unmark_watched\">Watched</string>\n    <string name=\"button_favorite\">Add to favorites</string>\n    <string name=\"button_remove_favorite\">Favorite</string>\n    <string name=\"title_main_actors\">Main Actors</string>\n    <string name=\"title_supporting_actors\">Supporting Actors</string>\n    <string name=\"title_user_reviews\">User Reviews</string>\n    <string name=\"hint_write_review\">Write your review</string>\n    <string name=\"button_submit_review\">Submit Review</string>\n\n    <!-- Actor Detail Screen -->\n    <string name=\"error_actor_not_found\">Couldn\\'t find actor in the database</string>\n    <string name=\"title_main_roles\">Main Roles</string>\n    <string name=\"title_supporting_roles\">Supporting Roles</string>\n\n    <!-- Profile Screen -->\n    <string name=\"title_favorite_movies\">Favorite Movies</string>\n    <string name=\"title_reviews\">Reviews</string>\n\n</resources>\n"
  },
  {
    "path": "dataconnect/app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Theme.FirebaseDataConnect\" parent=\"android:Theme.Material.Light.NoActionBar\" />\n</resources>"
  },
  {
    "path": "dataconnect/app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See https://developer.android.com/guide/topics/data/autobackup\n   for details.\n   Note: This file is ignored for devices older that API 31\n   See https://developer.android.com/about/versions/12/backup-restore\n-->\n<full-backup-content>\n    <!--\n   <include domain=\"sharedpref\" path=\".\"/>\n   <exclude domain=\"sharedpref\" path=\"device.xml\"/>\n-->\n</full-backup-content>"
  },
  {
    "path": "dataconnect/app/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample data extraction rules file; uncomment and customize as necessary.\n   See https://developer.android.com/about/versions/12/backup-restore#xml-changes\n   for details.\n-->\n<data-extraction-rules>\n    <cloud-backup>\n        <!-- TODO: Use <include> and <exclude> to control what is backed up.\n        <include .../>\n        <exclude .../>\n        -->\n    </cloud-backup>\n    <!--\n    <device-transfer>\n        <include .../>\n        <exclude .../>\n    </device-transfer>\n    -->\n</data-extraction-rules>"
  },
  {
    "path": "dataconnect/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.google.services) apply false\n    alias(libs.plugins.compose.compiler) apply false\n}\n\ntasks {\n    register<Exec>(\"dataconnectCompile\") {\n        workingDir = project.file(\"./dataconnect\")\n        if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {\n            commandLine(\"npx.cmd\", \"-y\", \"firebase-tools@latest\", \"dataconnect:compile\")\n        } else {\n            commandLine(\"npx\", \"-y\", \"firebase-tools@latest\", \"dataconnect:compile\")\n        }\n        isIgnoreExitValue = true\n    }\n\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n        finalizedBy(\"dataconnectCompile\")\n    }\n}"
  },
  {
    "path": "dataconnect/dataconnect/dataconnect.yaml",
    "content": "specVersion: \"v1\"\nserviceId: \"dataconnect\"\nlocation: \"us-central1\"\nschema:\n  source: \"./schema\"\n  datasource:\n    postgresql:\n      database: \"fdcdb\"\n      cloudSql:\n        instanceId: \"fdc-sql\"\nconnectorDirs: [\"./movie-connector\"]\n\n"
  },
  {
    "path": "dataconnect/dataconnect/movie-connector/connector.yaml",
    "content": "connectorId: movies\n# Required. Accepted values are either \"PUBLIC\" or \"ADMIN\" (only \"PUBLIC\" for gated private\n# preview). If \"ADMIN\", the connector in this directory is an AdminConnector and its operations\n# are gated by IAM.\nauthMode: PUBLIC\ngenerate:\n  # (Web SDK generation omitted, but can be found in https://github.com/firebase/quickstart-js)\n  kotlinSdk:\n    # Create a custom package name for your generated SDK\n    package: com.google.firebase.dataconnect.movies\n    # Specify where to store the generated SDK\n    # We're using the build/ directory so that generated code doesn't get checked into git\n    outputDir: ../../app/build/generated/sources\n"
  },
  {
    "path": "dataconnect/dataconnect/movie-connector/mutations.gql",
    "content": "mutation UpsertUser($username: String!) @auth(level: USER) {\n  user_upsert(\n    data: {\n      id_expr: \"auth.uid\"\n      username: $username\n    }\n  )\n}\n\n# Add a movie to the user's favorites list\nmutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {\n  favorite_movie_upsert(data: { userId_expr: \"auth.uid\", movieId: $movieId })\n}\n\n# Remove a movie from the user's favorites list\nmutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {\n  favorite_movie_delete(key: { userId_expr: \"auth.uid\", movieId: $movieId })\n}\n\n# Add a review for a movie\nmutation AddReview($movieId: UUID!, $rating: Int!, $reviewText: String!)\n@auth(level: USER) {\n  review_insert(\n    data: {\n      userId_expr: \"auth.uid\"\n      movieId: $movieId\n      rating: $rating\n      reviewText: $reviewText\n      reviewDate_date: { today: true }\n    }\n  )\n}\n\n# Update a user's review for a movie\nmutation UpdateReview($movieId: UUID!, $rating: Int!, $reviewText: String!)\n@auth(level: USER) {\n  review_update(\n    key: { userId_expr: \"auth.uid\", movieId: $movieId }\n    data: {\n      userId_expr: \"auth.uid\"\n      movieId: $movieId\n      rating: $rating\n      reviewText: $reviewText\n      reviewDate_date: { today: true }\n    }\n  )\n}\n\n# Delete a user's review for a movie\nmutation DeleteReview($movieId: UUID!) @auth(level: USER) {\n  review_delete(key: { userId_expr: \"auth.uid\", movieId: $movieId })\n}\n\n# The mutations below are unused by the application, but are useful examples for more complex cases\n\n# Create a movie based on user input\n# mutation CreateMovie(\n#   $title: String!\n#   $releaseYear: Int!\n#   $genre: String!\n#   $rating: Float\n#   $description: String\n#   $imageUrl: String!\n#   $tags: [String!] = []\n# ) @auth(expr: \"auth.token.isAdmin == true\") {\n\n# }\n# Update movie information based on the provided ID\n# mutation UpdateMovie(\n#   $id: UUID!\n#   $title: String\n#   $releaseYear: Int\n#   $genre: String\n#   $rating: Float\n#   $description: String\n#   $imageUrl: String\n#   $tags: [String!] = []\n# ) @auth(level: USER_EMAIL_VERIFIED) {\n#   movie_update(\n#     id: $id\n#     data: {\n#       title: $title\n#       releaseYear: $releaseYear\n#       genre: $genre\n#       rating: $rating\n#       description: $description\n#       imageUrl: $imageUrl\n#       tags: $tags\n#     }\n#   )\n# }\n\n# Delete a movie by its ID\n# mutation DeleteMovie($id: UUID!) @auth(level: USER_EMAIL_VERIFIED) {\n#   movie_delete(id: $id)\n# }\n\n# Delete movies with a rating lower than the specified minimum rating\n# mutation DeleteUnpopularMovies($minRating: Float!) @auth(level: USER_EMAIL_VERIFIED) {\n#   movie_deleteMany(where: { rating: { le: $minRating } })\n# }\n# End of example mutations\n"
  },
  {
    "path": "dataconnect/dataconnect/movie-connector/queries.gql",
    "content": "# List subset of fields for movies\nquery ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirection, $limit: Int) @auth(level: PUBLIC, insecureReason: \"Test Mode\") {\n  movies(\n    orderBy: [\n      { rating: $orderByRating },\n      { releaseYear: $orderByReleaseYear }\n    ]\n    limit: $limit\n  ) {\n    id\n    title\n    imageUrl\n    releaseYear\n    genre\n    rating\n    tags\n    description\n  }\n}\n\n# Get movie by id\nquery GetMovieById($id: UUID!) @auth(level: PUBLIC, insecureReason: \"Test Mode\") {\n movie(id: $id) {\n    id\n    title\n    imageUrl\n    releaseYear\n    genre\n    rating\n    description\n    tags\n    metadata: movieMetadatas_on_movie {\n      director\n    }\n    mainActors: actors_via_MovieActor(where: { role: { eq: \"main\" } }) {\n      id\n      name\n      imageUrl\n    }\n    supportingActors: actors_via_MovieActor(\n      where: { role: { eq: \"supporting\" } }\n    ) {\n      id\n      name\n      imageUrl\n    }\n    reviews: reviews_on_movie {\n      id\n      reviewText\n      reviewDate\n      rating\n      user {\n        id\n        username\n      }\n    }\n  }\n }\n\n# Get actor by id\nquery GetActorById($id: UUID!) @auth(level: PUBLIC, insecureReason: \"Test Mode\") {\n  actor(id: $id) {\n    id\n    name\n    imageUrl\n    mainActors: movies_via_MovieActor(where: { role: { eq: \"main\" } }) {\n      id\n      title\n      genre\n      tags\n      imageUrl\n    }\n    supportingActors: movies_via_MovieActor(\n      where: { role: { eq: \"supporting\" } }\n    ) {\n      id\n      title\n      genre\n      tags\n      imageUrl\n    }\n  }\n}\n\n\n# Get user by ID\nquery GetCurrentUser @auth(level: USER) {\n  user(key: { id_expr: \"auth.uid\" }) {\n    id\n    username\n    reviews: reviews_on_user {\n      id\n      rating\n      reviewDate\n      reviewText\n      movie {\n        id\n        title\n      }\n    }\n    favoriteMovies: favorite_movies_on_user {\n      movie {\n        id\n        title\n        genre\n        imageUrl\n        releaseYear\n        rating\n        description\n        tags\n        metadata: movieMetadatas_on_movie {\n          director\n        }\n      }\n    }\n  }\n}\n\nquery GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {\n  favorite_movie(key: { userId_expr: \"auth.uid\", movieId: $movieId }) {\n    movieId\n  }\n}\n\n# Search for movies, actors, and reviews\nquery SearchAll(\n  $input: String\n  $minYear: Int!\n  $maxYear: Int!\n  $minRating: Float!\n  $maxRating: Float!\n  $genre: String!\n) @auth(level: PUBLIC, insecureReason: \"Test Mode\") {\n  moviesMatchingTitle: movies(\n    where: {\n      _and: [\n        { releaseYear: { ge: $minYear } }\n        { releaseYear: { le: $maxYear } }\n        { rating: { ge: $minRating } }\n        { rating: { le: $maxRating } }\n        { genre: { contains: $genre } }\n        { title: { contains: $input } }\n      ]\n    }\n  ) {\n    id\n    title\n    genre\n    rating\n    imageUrl\n  }\n  moviesMatchingDescription: movies(\n    where: {\n      _and: [\n        { releaseYear: { ge: $minYear } }\n        { releaseYear: { le: $maxYear } }\n        { rating: { ge: $minRating } }\n        { rating: { le: $maxRating } }\n        { genre: { contains: $genre } }\n        { description: { contains: $input } }\n      ]\n    }\n  ) {\n    id\n    title\n    genre\n    rating\n    imageUrl\n  }\n  actorsMatchingName: actors(where: { name: { contains: $input } }) {\n    id\n    name\n    imageUrl\n  }\n  reviewsMatchingText: reviews(where: { reviewText: { contains: $input } }) {\n    id\n    rating\n    reviewText\n    reviewDate\n    movie {\n      id\n      title\n    }\n    user {\n      id\n      username\n    }\n  }\n}\n\n# Search movie descriptions using L2 similarity with Vertex AI\n# query SearchMovieDescriptionUsingL2Similarity($query: String!)\n# @auth(level: PUBLIC) {\n#   movies_descriptionEmbedding_similarity(\n#     compare_embed: { model: \"textembedding-gecko@003\", text: $query }\n#     method: L2\n#     within: 2\n#     limit: 5\n#   ) {\n#     id\n#     title\n#     description\n#     tags\n#     rating\n#     imageUrl\n#   }\n# }\n\n\n\n# # The queries below are unused by the application, but are useful examples for more complex cases\n\n# # List movies by partial title match\n# query ListMoviesByPartialTitle($input: String!) @auth(level: PUBLIC) {\n#   movies(where: { title: { contains: $input } }) {\n#     id\n#     title\n#     genre\n#     rating\n#     imageUrl\n#   }\n# }\n\n# # List movies by tag\n# query ListMoviesByTag($tag: String!) @auth(level: PUBLIC) {\n#   movies(where: { tags: { includes: $tag } }) {\n#     id\n#     title\n#     imageUrl\n#     genre\n#     rating\n#   }\n# }\n\n# # List movies by release year range\n# query MoviesByReleaseYear($min: Int, $max: Int) @auth(level: PUBLIC) {\n#   movies(\n#     where: { releaseYear: { le: $max, ge: $min } }\n#     orderBy: { releaseYear: ASC }\n#   ) {\n#     id\n#     rating\n#     title\n#     imageUrl\n#   }\n# }\n\n# # List movies by rating and genre with OR filters\n# query SearchMovieOr(\n#   $minRating: Float\n#   $maxRating: Float\n#   $genre: String\n#   $tag: String\n#   $input: String\n# ) @auth(level: PUBLIC) {\n#   movies(\n#     where: {\n#       _or: [\n#         { rating: { ge: $minRating } }\n#         { rating: { le: $maxRating } }\n#         { genre: { eq: $genre } }\n#         { tags: { includes: $tag } }\n#         { title: { contains: $input } }\n#       ]\n#     }\n#   ) {\n#     id\n#     rating\n#     title\n#     imageUrl\n#   }\n# }\n\n# # List movies by rating and genre with AND filters\n# query SearchMovieAnd(\n#   $minRating: Float\n#   $maxRating: Float\n#   $genre: String\n#   $tag: String\n#   $input: String\n# ) @auth(level: PUBLIC) {\n#   movies(\n#     where: {\n#       _and: [\n#         { rating: { ge: $minRating } }\n#         { rating: { le: $maxRating } }\n#         { genre: { eq: $genre } }\n#         { tags: { includes: $tag } }\n#         { title: { contains: $input } }\n#       ]\n#     }\n#   ) {\n#     id\n#     rating\n#     title\n#     imageUrl\n#   }\n# }\n\n# # Get favorite actors by user ID\n# query GetFavoriteActors @auth(level: USER) {\n#   user(key: {id_expr: \"auth.uid\"}) {\n#     favorite_actors_on_user {\n#       actor {\n#         id\n#         name\n#         imageUrl\n#       }\n#     }\n#   }\n# }\n\n# # Get favorite movies by user ID\n# query GetUserFavoriteMovies @auth(level: USER) {\n#   user(id_expr: \"auth.uid\") {\n#     favorite_movies_on_user {\n#       movie {\n#         id\n#         title\n#         genre\n#         imageUrl\n#         releaseYear\n#         rating\n#         description\n#       }\n#     }\n#   }\n# }\n# # end of example queries\n"
  },
  {
    "path": "dataconnect/dataconnect/moviedata_insert.gql",
    "content": "mutation {\n  movie_insertMany(\n    data: [\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440000\"\n        title: \"Quantum Paradox\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fquantum_paradox.jpeg?alt=media&token=4142e2a1-bf43-43b5-b7cf-6616be3fd4e3\"\n        releaseYear: 2025\n        genre: \"sci-fi\"\n        rating: 7.9\n        description: \"A group of scientists accidentally open a portal to a parallel universe, causing a rift in time. As the team races to close the portal, they encounter alternate versions of themselves, leading to shocking revelations.\"\n        tags: [\"thriller\", \"adventure\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440001\"\n        title: \"The Lone Outlaw\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Flone_outlaw.jpeg?alt=media&token=15525ffc-208f-4b59-b506-ae8348e06e85\"\n        releaseYear: 2023\n        genre: \"western\"\n        rating: 8.2\n        description: \"In the lawless Wild West, a mysterious gunslinger with a hidden past takes on a corrupt sheriff and his band of outlaws to bring justice to a small town.\"\n        tags: [\"action\", \"drama\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440002\"\n        title: \"Celestial Harmony\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fcelestial_harmony.jpeg?alt=media&token=3edf1cf9-c2f5-4c75-9819-36ff6a734c9a\"\n        releaseYear: 2024\n        genre: \"romance\"\n        rating: 7.5\n        description: \"Two astronauts, stationed on a remote space station, fall in love amidst the isolation of deep space. But when a mysterious signal disrupts their communication, they must find a way to reconnect and survive.\"\n        tags: [\"romance\", \"sci-fi\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440003\"\n        title: \"Noir Mystique\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fnoir_mystique.jpeg?alt=media&token=3299adba-cb98-4302-8b23-aeb679a4f913\"\n        releaseYear: 2022\n        genre: \"mystery\"\n        rating: 8.0\n        description: \"A private detective gets caught up in a web of lies, deception, and betrayal while investigating the disappearance of a famous actress in 1940s Hollywood.\"\n        tags: [\"crime\", \"thriller\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440004\"\n        title: \"The Forgotten Island\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fforgotten_island.jpeg?alt=media&token=bc2b16e1-caed-4649-952c-73b6113f205c\"\n        releaseYear: 2025\n        genre: \"adventure\"\n        rating: 7.6\n        description: \"An explorer leads an expedition to a remote island rumored to be home to mythical creatures. As the team ventures deeper into the island, they uncover secrets that change the course of history.\"\n        tags: [\"adventure\", \"fantasy\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440005\"\n        title: \"Digital Nightmare\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fdigital_nightmare.jpeg?alt=media&token=335ec842-1ca4-4b09-abd1-e96d9f5c0c2f\"\n        releaseYear: 2024\n        genre: \"horror\"\n        rating: 6.9\n        description: \"A tech-savvy teenager discovers a cursed app that brings nightmares to life. As the horrors of the digital world cross into reality, she must find a way to break the curse before it's too late.\"\n        tags: [\"horror\", \"thriller\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440006\"\n        title: \"Eclipse of Destiny\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Feclipse_destiny.jpeg?alt=media&token=346649b3-cb5c-4d7e-b0d4-6f02e3df5959\"\n        releaseYear: 2026\n        genre: \"fantasy\"\n        rating: 8.1\n        description: \"In a kingdom on the brink of war, a prophecy speaks of an eclipse that will grant power to the rightful ruler. As factions vie for control, a young warrior must decide where his true loyalty lies.\"\n        tags: [\"fantasy\", \"adventure\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440007\"\n        title: \"Heart of Steel\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fheart_steel.jpeg?alt=media&token=17883d71-329b-415a-86f8-dd4d9e941d7f\"\n        releaseYear: 2023\n        genre: \"sci-fi\"\n        rating: 7.7\n        description: \"A brilliant scientist creates a robot with a human heart. As the robot struggles to understand emotions, it becomes entangled in a plot that could change the fate of humanity.\"\n        tags: [\"sci-fi\", \"drama\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440008\"\n        title: \"Rise of the Crimson Empire\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Frise_crimson_empire.jpeg?alt=media&token=6faa73ad-7504-4146-8f3a-50b90f607f33\"\n        releaseYear: 2025\n        genre: \"action\"\n        rating: 8.4\n        description: \"A legendary warrior rises to challenge the tyrannical rule of a powerful empire. As rebellion brews, the warrior must unite different factions to lead an uprising.\"\n        tags: [\"action\", \"adventure\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440009\"\n        title: \"Silent Waves\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fsilent_waves.jpeg?alt=media&token=bd626bf1-ec60-4e57-aa07-87ba14e35bb7\"\n        releaseYear: 2024\n        genre: \"drama\"\n        rating: 8.2\n        description: \"A talented pianist, who loses his hearing in a tragic accident, must rediscover his passion for music with the help of a young music teacher who believes in him.\"\n        tags: [\"drama\", \"music\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440010\"\n        title: \"Echoes of the Past\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fecho_of_past.jpeg?alt=media&token=d866aa27-8534-4d72-8988-9da4a1b9e452\"\n        releaseYear: 2023\n        genre: \"historical\"\n        rating: 7.8\n        description: \"A historian stumbles upon an ancient artifact that reveals hidden truths about an empire long forgotten. As she deciphers the clues, a shadowy organization tries to stop her from unearthing the past.\"\n        tags: [\"drama\", \"mystery\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440011\"\n        title: \"Beyond the Horizon\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fbeyond_horizon.jpeg?alt=media&token=31493973-0692-4e6e-8b88-afb1aaea17ee\"\n        releaseYear: 2026\n        genre: \"sci-fi\"\n        rating: 8.5\n        description: \"In the future, Earth's best pilots are sent on a mission to explore a mysterious planet beyond the solar system. What they find changes humanity's understanding of the universe forever.\"\n        tags: [\"sci-fi\", \"adventure\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440012\"\n        title: \"Shadows and Lies\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fshadows_lies.jpeg?alt=media&token=01afb80d-caee-47f8-a00e-aea8b9e459a2\"\n        releaseYear: 2022\n        genre: \"crime\"\n        rating: 7.9\n        description: \"A young detective with a dark past investigates a series of mysterious murders in a city plagued by corruption. As she digs deeper, she realizes nothing is as it seems.\"\n        tags: [\"crime\", \"thriller\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440013\"\n        title: \"The Last Symphony\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Flast_symphony.jpeg?alt=media&token=f9bf80cd-3d8e-4e24-8503-7feb11f4e397\"\n        releaseYear: 2024\n        genre: \"drama\"\n        rating: 8.0\n        description: \"An aging composer struggling with memory loss attempts to complete his final symphony. With the help of a young prodigy, he embarks on an emotional journey through his memories and legacy.\"\n        tags: [\"drama\", \"music\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440014\"\n        title: \"Moonlit Crusade\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fmoonlit_crusade.jpeg?alt=media&token=b13241f5-d7d0-4370-b651-07847ad99dc2\"\n        releaseYear: 2025\n        genre: \"fantasy\"\n        rating: 8.3\n        description: \"A knight is chosen by an ancient order to embark on a quest under the light of the full moon. Facing mythical beasts and treacherous landscapes, he seeks a relic that could save his kingdom.\"\n        tags: [\"fantasy\", \"adventure\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440015\"\n        title: \"Abyss of the Deep\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fabyss_deep.jpeg?alt=media&token=2417321d-2451-4ec0-9ed6-6297042170e6\"\n        releaseYear: 2023\n        genre: \"horror\"\n        rating: 7.2\n        description: \"When a group of marine biologists descends into the unexplored depths of the ocean, they encounter a terrifying and ancient force. Now, they must survive as the abyss comes alive.\"\n        tags: [\"horror\", \"thriller\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440016\"\n        title: \"Phoenix Rising\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fpheonix_rising.jpeg?alt=media&token=7298b1fd-833c-471c-a55d-e8fc798b4ab2\"\n        releaseYear: 2025\n        genre: \"action\"\n        rating: 8.6\n        description: \"A special forces operative, presumed dead, returns to avenge his fallen comrades. With nothing to lose, he faces a powerful enemy in a relentless pursuit for justice.\"\n        tags: [\"action\", \"thriller\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440017\"\n        title: \"The Infinite Knot\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Finfinite_knot.jpeg?alt=media&token=93d54d93-d933-4663-a6fe-26b707ef823e\"\n        releaseYear: 2026\n        genre: \"romance\"\n        rating: 7.4\n        description: \"Two souls destined to meet across multiple lifetimes struggle to find each other in a chaotic world. With each incarnation, they get closer, but time itself becomes their greatest obstacle.\"\n        tags: [\"romance\", \"fantasy\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440018\"\n        title: \"Parallel Justice\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fparalel_justice.jpeg?alt=media&token=4544b5e2-7a1d-46ca-a97f-eb6a490d4288\"\n        releaseYear: 2024\n        genre: \"crime\"\n        rating: 8.1\n        description: \"A lawyer who can see the outcomes of different timelines must choose between justice and personal gain. When a high-stakes case arises, he faces a moral dilemma that could alter his life forever.\"\n        tags: [\"crime\", \"thriller\"]\n      }\n      {\n        id: \"550e8400-e29b-41d4-a716-446655440019\"\n        title: \"Veil of Illusion\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fveil_illusion.jpeg?alt=media&token=7bf09a3c-c531-478a-9d02-5d99fca9393b\"\n        releaseYear: 2022\n        genre: \"mystery\"\n        rating: 7.8\n        description: \"A magician-turned-detective uses his skills in illusion to solve crimes. When a series of murders leaves the city in fear, he must reveal the truth hidden behind a veil of deceit.\"\n        tags: [\"mystery\", \"crime\"]\n      }\n    ]\n  )\n  actor_insertMany(\n    data: [\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174020\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Foliver_blackwood.jpeg?alt=media&token=79cdbc29-c2c6-4dc3-b87f-f6dc4f1a8208\"\n        name: \"Oliver Blackwood\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174021\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Femma_westfield.jpeg?alt=media&token=2991c3c9-cfa8-4067-8b26-c5239b6894c4\"\n        name: \"Emma Westfield\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174022\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fjack_stone.jpeg?alt=media&token=74a564aa-d840-4bdd-a8a6-c6b17bbde608\"\n        name: \"Jack Stone\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174023\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fclara_woods.jpeg?alt=media&token=b4ff2a15-ef6d-4f20-86c9-07d67015fb29\"\n        name: \"Clara Woods\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174024\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fnoah_frost.jpeg?alt=media&token=0d08179a-7778-405e-9501-feac43b77a99\"\n        name: \"Noah Frost\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174025\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fisabelle_hart.jpeg?alt=media&token=d4fdf896-0f5b-4c32-91a4-7a541a95e77d\"\n        name: \"Isabella Hart\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174026\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fliam_hale.jpeg?alt=media&token=08e8eeee-b8ef-4e7b-8f97-a1e0b59321cc\"\n        name: \"Liam Hale\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174027\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fsophia_knight.jpeg?alt=media&token=7a79ef21-93e0-46f9-934c-6bbef7b5d430\"\n        name: \"Sophia Knight\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174028\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Falex_clay.jpeg?alt=media&token=2a798cdb-f44f-48d5-91bc-9d26a758944e\"\n        name: \"Alexander Clay\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174029\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Famelia_stone.jpeg?alt=media&token=34f21ba9-9e28-4708-9e55-f123634ab506\"\n        name: \"Amelia Stone\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174030\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fethan_blake.jpeg?alt=media&token=41352170-a5cd-4088-b8fd-1c4ee0d52cad\"\n        name: \"Ethan Blake\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174031\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fmia_gray.jpeg?alt=media&token=1ba1831a-3ada-485a-b5c9-2d018bf1862b\"\n        name: \"Mia Gray\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174032\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Flucas_reed.jpeg?alt=media&token=c74f44f3-ae98-4208-8e67-18c2db65a5c1\"\n        name: \"Lucas Reed\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174033\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fevelyn_harper.jpeg?alt=media&token=b138b308-9589-4dfe-8c50-a6d70f06dfb1\"\n        name: \"Evelyn Harper\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174034\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Foscar_smith.jpeg?alt=media&token=d493da85-644d-4d45-a09d-ecb5416645e4\"\n        name: \"Oscar Smith\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174035\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fava_winter.jpeg?alt=media&token=757e4b11-0372-401e-8fa0-61797e90312a\"\n        name: \"Ava Winter\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174036\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fleo_hunt.jpeg?alt=media&token=2cb14738-b39b-47b1-87f9-b45f38245179\"\n        name: \"Leo Hunt\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174037\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Flucy_walsh.jpeg?alt=media&token=016a216c-f329-4c10-bbe8-b31425f73c69\"\n        name: \"Lucy Walsh\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174038\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Fmason_ford.jpeg?alt=media&token=55388be9-fdc8-483f-8352-c29755ed3574\"\n        name: \"Mason Ford\"\n      }\n      {\n        id: \"123e4567-e89b-12d3-a456-426614174039\"\n        imageUrl: \"https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/actor%2Flily_moore.jpeg?alt=media&token=19538aa6-1baf-4033-8fd7-d2a62aa79f51\"\n        name: \"Lily Moore\"\n      }\n    ]\n  )\n  movieMetadata_insertMany(\n    data: [\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440000\"\n        director: \"Henry Caldwell\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440001\"\n        director: \"Juliana Mason, Clark Avery\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440002\"\n        director: \"Diana Rivers\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440003\"\n        director: \"Liam Thatcher\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440004\"\n        director: \"Evelyn Hart\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440005\"\n        director: \"Grayson Brooks\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440006\"\n        director: \"Isabella Quinn\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440007\"\n        director: \"Vincent Hale\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440008\"\n        director: \"Amelia Sutton\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440009\"\n        director: \"Lucas Stone\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440010\"\n        director: \"Sophia Langford\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440011\"\n        director: \"Noah Bennett\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440012\"\n        director: \"Chloe Armstrong\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440013\"\n        director: \"Sebastian Crane\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440014\"\n        director: \"Isla Fitzgerald\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440015\"\n        director: \"Oliver Hayes\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440016\"\n        director: \"Mila Donovan\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440017\"\n        director: \"Carter Monroe, Elise Turner\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440018\"\n        director: \"Adrian Blake\"\n      }\n      {\n        movieId: \"550e8400-e29b-41d4-a716-446655440019\"\n        director: \"Hazel Carter\"\n      }\n    ]\n  )\nmovieActor_insertMany(\n  data: [\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440000\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174020\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440000\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174021\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440001\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174021\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440001\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174022\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440002\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174022\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440002\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174023\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440003\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174023\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440003\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174024\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440004\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174024\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440004\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174025\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440005\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174025\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440005\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174026\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440006\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174026\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440006\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174027\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440007\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174027\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440007\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174028\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440008\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174028\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440008\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174029\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440009\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174029\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440009\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174030\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440010\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174030\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440010\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174031\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440011\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174031\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440011\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174032\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440012\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174032\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440012\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174033\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440013\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174033\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440013\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174034\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440014\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174034\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440014\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174035\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440015\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174035\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440015\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174036\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440016\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174036\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440016\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174037\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440017\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174037\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440017\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174038\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440018\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174038\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440018\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174039\"\n      role: \"supporting\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440019\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174039\"\n      role: \"main\"\n    }\n    {\n      movieId: \"550e8400-e29b-41d4-a716-446655440019\"\n      actorId: \"123e4567-e89b-12d3-a456-426614174020\"\n      role: \"supporting\"\n    }\n  ]\n)\n  user_insertMany(\n    data: [\n      { id: \"SnLgOC3lN4hcIl69s53cW0Q8R1T2\", username: \"sherlock_h\" }\n      { id: \"fep4fXpGWsaRpuphq9CIrBIXQ0S2\", username: \"hercule_p\" }\n      { id: \"TBedjwCX0Jf955Uuoxk6k74sY0l1\", username: \"jane_d\" }\n    ]\n  )\n\n  review_insertMany(\n    data: [\n      {\n        id: \"345e4567-e89b-12d3-a456-426614174000\"\n        userId: \"SnLgOC3lN4hcIl69s53cW0Q8R1T2\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440000\"\n        rating: 5\n        reviewText: \"An incredible movie with a mind-blowing plot!\"\n        reviewDate_date: { today: true }\n      }\n      {\n        id: \"345e4567-e89b-12d3-a456-426614174001\"\n        userId: \"fep4fXpGWsaRpuphq9CIrBIXQ0S2\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440001\"\n        rating: 5\n        reviewText: \"A revolutionary film that changed cinema forever.\"\n        reviewDate_date: { today: true }\n      }\n      {\n        id: \"345e4567-e89b-12d3-a456-426614174002\"\n        userId: \"TBedjwCX0Jf955Uuoxk6k74sY0l1\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440002\"\n        rating: 5\n        reviewText: \"A visually stunning and emotionally impactful movie.\"\n        reviewDate_date: { today: true }\n      }\n      {\n        id: \"345e4567-e89b-12d3-a456-426614174003\"\n        userId: \"SnLgOC3lN4hcIl69s53cW0Q8R1T2\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440003\"\n        rating: 4\n        reviewText: \"A fantastic superhero film with great performances.\"\n        reviewDate_date: { today: true }\n      }\n      {\n        id: \"345e4567-e89b-12d3-a456-426614174004\"\n        userId: \"fep4fXpGWsaRpuphq9CIrBIXQ0S2\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440004\"\n        rating: 5\n        reviewText: \"An amazing film that keeps you on the edge of your seat.\"\n        reviewDate_date: { today: true }\n      }\n      {\n        id: \"345e4567-e89b-12d3-a456-426614174005\"\n        userId: \"TBedjwCX0Jf955Uuoxk6k74sY0l1\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440005\"\n        rating: 5\n        reviewText: \"An absolute classic with unforgettable dialogue.\"\n        reviewDate_date: { today: true }\n      }\n    ]\n  )\n\n  favorite_movie_insertMany(\n    data: [\n      {\n        userId: \"SnLgOC3lN4hcIl69s53cW0Q8R1T2\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440000\"\n      }\n      {\n        userId: \"fep4fXpGWsaRpuphq9CIrBIXQ0S2\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440001\"\n      }\n      {\n        userId: \"TBedjwCX0Jf955Uuoxk6k74sY0l1\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440002\"\n      }\n      {\n        userId: \"SnLgOC3lN4hcIl69s53cW0Q8R1T2\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440003\"\n      }\n      {\n        userId: \"fep4fXpGWsaRpuphq9CIrBIXQ0S2\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440004\"\n      }\n      {\n        userId: \"TBedjwCX0Jf955Uuoxk6k74sY0l1\"\n        movieId: \"550e8400-e29b-41d4-a716-446655440005\"\n      }\n    ]\n  )\n}\n"
  },
  {
    "path": "dataconnect/dataconnect/schema/schema.gql",
    "content": "# Movies\n# TODO: Fill out Movie table\ntype Movie\n  # The below parameter values are generated by default with @table, and can be edited manually.\n  @table {\n  # implicitly calls @col to generates a column name. ex: @col(name: \"movie_id\")\n  id: UUID! @default(expr: \"uuidV4()\")\n  title: String!\n  imageUrl: String!\n  releaseYear: Int\n  genre: String\n  rating: Float\n  description: String\n  tags: [String]\n  # descriptionEmbedding: Vector @col(size:768) # Enables vector search\n}\n\n# Movie Metadata\n# Movie - MovieMetadata is a one-to-one relationship\n# TODO: Fill out MovieMetadata table\ntype MovieMetadata\n  @table {\n  # @ref creates a field in the current table (MovieMetadata)\n  # It is a reference that holds the primary key of the referenced type\n  # In this case, @ref(fields: \"movieId\", references: \"id\") is implied\n  movie: Movie! @ref\n  # movieId: UUID <- this is created by the above @ref\n  director: String\n}\n\n# Actors\n# Suppose an actor can participate in multiple movies and movies can have multiple actors\n# Movie - Actors (or vice versa) is a many to many relationship\n# TODO: Fill out Actor table\ntype Actor @table {\n  id: UUID!\n  imageUrl: String!\n  name: String! @col(name: \"name\", dataType: \"varchar(30)\")\n}\n\n# Users\n# Suppose a user can leave reviews for movies\n# user-reviews is a one to many relationship, movie-reviews is a one to many relationship, movie:user is a many to many relationship\n# TODO: Fill out User table\ntype User\n  @table {\n  id: String! @col(name: \"user_auth\")\n  username: String! @col(name: \"username\", dataType: \"varchar(50)\")\n  # The following are generated from the @ref in the Review table\n  # reviews_on_user\n  # movies_via_Review\n}\n\n# Reviews\n# TODO: Fill out Review table\ntype Review @table(name: \"Reviews\", key: [\"movie\", \"user\"]) {\n  id: UUID! @default(expr: \"uuidV4()\")\n  user: User!\n  movie: Movie!\n  rating: Int\n  reviewText: String\n  reviewDate: Date! @default(expr: \"request.time\")\n}\n\n# Join table for many-to-many relationship for movies and actors\n# The 'key' param signifies the primary key(s) of this table\n# In this case, the keys are [movieId, actorId], the generated fields of the reference types [movie, actor]\n# TODO: Fill out MovieActor table\ntype MovieActor @table(key: [\"movie\", \"actor\"]) {\n  # @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type\n  # In this case, @ref(fields: \"id\") is implied\n  movie: Movie!\n  # movieId: UUID! <- this is created by the implied @ref, see: implicit.gql\n\n  actor: Actor!\n  # actorId: UUID! <- this is created by the implied  @ref, see: implicit.gql\n\n  role: String! # \"main\" or \"supporting\"\n}\n\n# Join table for many-to-many relationship for users and favorite movies\n# TODO: Fill out FavoriteMovie table\ntype FavoriteMovie\n  @table(name: \"FavoriteMovies\", singular: \"favorite_movie\", plural: \"favorite_movies\", key: [\"user\", \"movie\"]) {\n  # @ref is implicit\n  user: User!\n  movie: Movie!\n}\n"
  },
  {
    "path": "dataconnect/firebase.json",
    "content": "{\n  \"dataconnect\": {\n    \"source\": \"dataconnect\"\n  }\n}\n"
  },
  {
    "path": "dataconnect/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed May 08 19:29:05 BST 2024\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "dataconnect/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. For more details, visit\n# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true"
  },
  {
    "path": "dataconnect/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "dataconnect/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "dataconnect/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencyResolutionManagement {\n        versionCatalogs {\n            create(\"libs\") {\n                from(files(\"../gradle/libs.versions.toml\"))\n            }\n        }\n    }\n}\n\nrootProject.name = \"Firebase Data Connect\"\ninclude(\":app\")\n "
  },
  {
    "path": "dynamiclinks/README.md",
    "content": "Firebase Dynamic Links Quickstart\n==============================\n\n> [!IMPORTANT]\n> Firebase Dynamic Links is **deprecated** and should not be used in new projects. The service will shut down on August 25, 2025.\n>\n> Please see our [Dynamic Links Deprecation FAQ documentation](https://firebase.google.com/support/dynamic-links-faq) for more guidance.\n\n"
  },
  {
    "path": "firebase-ai/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n"
  },
  {
    "path": "firebase-ai/README.md",
    "content": "# Firebase AI Logic quickstart sample app\n\nThis Android sample app demonstrates how to use state-of-the-art\ngenerative AI models (like Gemini) to build AI-powered features and applications.\n\nFor more information about Firebase AI Logic, visit the [documentation](http://firebase.google.com/docs/ai-logic).\n\n## Setup & Configuration\n\n### Prerequisites\n*   **Google AI (Gemini) API Key**: Most samples work out of the box with the Google AI SDK.\n*   **Vertex AI**: Samples marked with *(Vertex AI)* require you to enable the Vertex AI API in your Google Cloud project and have your files in Cloud Storage.\n*   **Server Prompt Templates**: These samples require you to set up templates in the [Firebase Console](https://console.firebase.google.com/project/_/ai-logic).\n\n## Getting Started\n\nTo try out this sample app, you need to use latest stable version of Android Studio.\n\n* [Set up your Android app for Firebase][setup-android]\n  * Use the package name `com.google.firebase.quickstart.ai`\n* [Set up Firebase AI Logic][setup-ai-logic] \n* Run the app on an Android device or emulator.\n\n## Features\n\nYou can find the implementation for each feature by clicking on the links below:\n\n### Text / Chat\n- [Travel tips](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TravelTipsViewModel.kt): The user wants the model to help a new traveler with travel tips\n- [Chatbot recommendations for courses](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/CourseRecommendationsViewModel.kt): A chatbot suggests courses for a performing arts program.\n- [Weather Chat](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/WeatherChatViewModel.kt): Use function calling to get the weather conditions for a specific US city on a specific date.\n- [Grounding with Google Search](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/GoogleSearchGroundingViewModel.kt): Use Grounding with Google Search to get responses based on up-to-date information from the web.\n- [Thinking](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ThinkingChatViewModel.kt): Gemini 2.5 Flash with dynamic thinking\n- [Server Prompt Templates - Gemini](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt): Generate an invoice using server prompt templates.\n\n### Image analysis / generation\n- [Imagen 4 - image generation](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt): Generate images using Imagen 4\n- [Imagen 3 - Inpainting (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt): Replace part of an image using Imagen 3\n- [Imagen 3 - Outpainting (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt): Expand an image by drawing in more background\n- [Imagen 3 - Subject Reference (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt): Generate an image using a referenced subject (must be an animal)\n- [Imagen 3 - Style Transfer (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt): Change the art style of a cat picture using a reference\n- [Gemini 2.5 Flash Image (aka nanobanana)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt): Generate and/or edit images using Gemini 2.5 Flash Image aka nanobanana\n- [Server Prompt Template - Imagen](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt): Generate an image using a server prompt template.\n- [SVG Generator](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt): Use Gemini 3 Flash preview to create SVG illustrations\n- [Blog post creator (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt): Create a blog post from an image file stored in Cloud Storage.\n\n### Audio analysis\n- [Audio Summarization](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt): Summarize an audio file\n- [Translation from audio (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioTranslationViewModel.kt): Translate an audio file stored in Cloud Storage\n\n### Video analysis\n- [Hashtags for a video (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoHashtagGeneratorViewModel.kt): Generate hashtags for a video ad stored in Cloud Storage\n- [Summarize video](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt): Summarize a video and extract important dialogue.\n\n### Live API (Real-time bidrectional streaming)\n- [ForecastTalk](app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamAudioViewModel.kt): Use bidirectional streaming to get information about weather conditions\n- [Gemini Live (Video input)](app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamVideoViewModel.kt): Use bidirectional streaming to chat with Gemini using your phone's camera\n\n### Document (PDFs) analysis\n- [Document comparison (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt): Compare the contents of 2 documents in Cloud Storage.\n\n\n## All samples\n\nThe full list of available samples can be found in the\n[FirebaseAISamples.kt file](app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt).\n\n[setup-android]: https://firebase.google.com/docs/android/setup\n[setup-ai-logic]: https://firebase.google.com/docs/ai-logic/get-started?api=dev#set-up-firebase"
  },
  {
    "path": "firebase-ai/app/.gitignore",
    "content": "/build"
  },
  {
    "path": "firebase-ai/app/build.gradle.kts",
    "content": "\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.compose.compiler)\n    alias(libs.plugins.kotlin.serialization)\n    alias(libs.plugins.google.services)\n}\n\nandroid {\n    namespace = \"com.google.firebase.quickstart.ai\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.quickstart.ai\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = false\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n    buildFeatures {\n        compose = true\n    }\n}\n\ndependencies {\n\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.lifecycle.runtime.ktx)\n    implementation(libs.androidx.activity.compose)\n\n    implementation(platform(libs.androidx.compose.bom))\n    implementation(libs.androidx.ui)\n    implementation(libs.androidx.ui.graphics)\n    implementation(libs.androidx.ui.tooling.preview)\n    implementation(libs.androidx.material3)\n    implementation(libs.androidx.material.icons.extended)\n    implementation(libs.androidx.material3.adaptive.navigation.suite)\n    implementation(libs.compose.navigation)\n    implementation(libs.androidx.lifecycle.viewmodel.ktx)\n    // ViewModel utilities for Compose\n    implementation(libs.androidx.lifecycle.viewmodel.compose)\n    implementation(libs.androidx.lifecycle.viewmodel.savedstate)\n    implementation(libs.kotlinx.serialization.json)\n    // Webkit\n    implementation(libs.androidx.webkit)\n\n    // CameraX (for video with the Gemini Live API)\n    implementation(libs.androidx.camera.core)\n    implementation(libs.androidx.camera.camera2)\n    implementation(libs.androidx.camera.lifecycle)\n    implementation(libs.androidx.camera.view)\n    implementation(libs.androidx.camera.extensions)\n\n    // Material for XML-based theme\n    implementation(libs.material)\n\n    // Firebase\n    implementation(platform(libs.firebase.bom))\n    implementation(libs.firebase.ai)\n\n    // Image loading\n    implementation(libs.coil.compose)\n    implementation(libs.coil.network.okhttp)\n    implementation(libs.coil.svg)\n\n    testImplementation(libs.junit)\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core)\n    androidTestImplementation(platform(libs.androidx.compose.bom))\n    androidTestImplementation(libs.androidx.ui.test.junit4)\n    debugImplementation(libs.androidx.ui.tooling)\n    debugImplementation(libs.androidx.ui.test.manifest)\n}\n"
  },
  {
    "path": "firebase-ai/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "firebase-ai/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n\n    <uses-feature android:name=\"android.hardware.camera\" />\n    <uses-feature android:name=\"android.hardware.microphone\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n        android:fullBackupContent=\"@xml/backup_rules\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.FirebaseAIServices\"\n        tools:targetApi=\"31\">\n        <activity\n            android:name=\".MainActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/Theme.FirebaseAIServices\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt",
    "content": "package com.google.firebase.quickstart.ai\n\nimport android.annotation.SuppressLint\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport androidx.navigation.NavController\nimport androidx.navigation.NavDestination\nimport androidx.navigation.compose.NavHost\nimport androidx.navigation.compose.composable\nimport androidx.navigation.compose.rememberNavController\nimport com.google.firebase.quickstart.ai.feature.live.BidiViewModel\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel\nimport com.google.firebase.quickstart.ai.feature.text.ChatViewModel\nimport com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateViewModel\nimport com.google.firebase.quickstart.ai.feature.text.SvgViewModel\nimport com.google.firebase.quickstart.ai.ui.ChatScreen\nimport com.google.firebase.quickstart.ai.ui.ImagenScreen\nimport com.google.firebase.quickstart.ai.ui.ServerPromptScreen\nimport com.google.firebase.quickstart.ai.ui.StreamRealtimeScreen\nimport com.google.firebase.quickstart.ai.ui.StreamRealtimeVideoScreen\nimport com.google.firebase.quickstart.ai.ui.SvgScreen\nimport com.google.firebase.quickstart.ai.ui.navigation.FIREBASE_AI_SAMPLES\nimport com.google.firebase.quickstart.ai.ui.navigation.MainMenuScreen\nimport com.google.firebase.quickstart.ai.ui.navigation.ScreenType\nimport com.google.firebase.quickstart.ai.ui.theme.FirebaseAILogicTheme\n\nclass MainActivity : ComponentActivity() {\n    @OptIn(ExperimentalMaterial3Api::class)\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        enableEdgeToEdge()\n        catImage = BitmapFactory.decodeResource(applicationContext.resources, R.drawable.cat)\n        setContent {\n            val navController = rememberNavController()\n\n            var topBarTitle: String by rememberSaveable { mutableStateOf(getString(R.string.app_name)) }\n            FirebaseAILogicTheme {\n                Scaffold(\n                    topBar = {\n                        TopAppBar(\n                            colors = TopAppBarDefaults.topAppBarColors(\n                                containerColor = MaterialTheme.colorScheme.primaryContainer,\n                                titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer\n                            ),\n                            title = {\n                                Text(topBarTitle)\n                            }\n                        )\n                    },\n                    modifier = Modifier.fillMaxSize()\n                ) { innerPadding ->\n                    NavHost(\n                        navController,\n                        startDestination = \"mainMenu\",\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .padding(innerPadding)\n                    ) {\n                        composable(\"mainMenu\") {\n                            MainMenuScreen(\n                                onSampleClicked = {\n                                    topBarTitle = it.title\n                                    navController.navigate(it.route)\n                                }\n                            )\n                        }\n\n                        // Add navigation for all of the samples\n                        FIREBASE_AI_SAMPLES.forEach { sample ->\n                            composable(\n                                route = sample.route::class,\n                                typeMap = emptyMap()\n                            ) {\n                                val viewModelClass = sample.viewModelClass?.java\n                                    ?: return@composable\n                                val vm = viewModel(modelClass = viewModelClass)\n\n                                when (sample.screenType) {\n                                    ScreenType.CHAT -> {\n                                        (vm as? ChatViewModel)?.let { ChatScreen(it) }\n                                    }\n\n                                    ScreenType.IMAGEN -> {\n                                        (vm as? ImagenViewModel)?.let { ImagenScreen(it) }\n                                    }\n\n                                    ScreenType.SVG -> {\n                                        (vm as? SvgViewModel)?.let { SvgScreen(it) }\n                                    }\n\n                                    ScreenType.SERVER_PROMPT -> {\n                                        (vm as? ServerPromptTemplateViewModel)?.let { ServerPromptScreen(it) }\n                                    }\n\n                                    ScreenType.BIDI -> {\n                                        (vm as? BidiViewModel)?.let {\n                                            @SuppressLint(\"MissingPermission\")\n                                            StreamRealtimeScreen(it)\n                                        }\n                                    }\n\n                                    ScreenType.BIDI_VIDEO -> {\n                                        (vm as? BidiViewModel)?.let {\n                                            @SuppressLint(\"MissingPermission\")\n                                            StreamRealtimeVideoScreen(it)\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n            navController.addOnDestinationChangedListener { _, destination, _ ->\n                if (destination.route == \"mainMenu\") {\n                    topBarTitle = getString(R.string.app_name)\n                }\n            }\n        }\n    }\n\n    companion object {\n        lateinit var catImage: Bitmap\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.live\n\nimport android.annotation.SuppressLint\nimport android.graphics.Bitmap\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.firebase.ai.type.FunctionCallPart\nimport com.google.firebase.ai.type.FunctionResponsePart\nimport com.google.firebase.ai.type.InlineData\nimport com.google.firebase.ai.type.LiveSession\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport kotlinx.coroutines.launch\nimport kotlinx.serialization.json.JsonObject\nimport java.io.ByteArrayOutputStream\n\n\n@OptIn(PublicPreviewAPI::class)\nabstract class BidiViewModel : ViewModel() {\n    protected lateinit var liveSession: LiveSession\n\n    open fun handler(functionCall: FunctionCallPart): FunctionResponsePart {\n        return FunctionResponsePart(functionCall.name, JsonObject(emptyMap()), functionCall.id)\n    }\n\n    // The permission check is handled by the view that calls this function.\n    @SuppressLint(\"MissingPermission\")\n    suspend fun startConversation() {\n        liveSession.startAudioConversation(::handler)\n    }\n\n    fun endConversation() {\n        liveSession.stopAudioConversation()\n    }\n\n    fun sendVideoFrame(frame: Bitmap) {\n        viewModelScope.launch {\n            // Directly compress the Bitmap to a ByteArray\n            val byteArrayOutputStream = ByteArrayOutputStream()\n            frame.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream)\n            val jpegBytes = byteArrayOutputStream.toByteArray()\n\n            liveSession.sendVideoRealtime(InlineData(jpegBytes, \"image/jpeg\"))\n        }\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamAudioViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.live\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.FunctionCallPart\nimport com.google.firebase.ai.type.FunctionDeclaration\nimport com.google.firebase.ai.type.FunctionResponsePart\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.ai.type.ResponseModality\nimport com.google.firebase.ai.type.Schema\nimport com.google.firebase.ai.type.SpeechConfig\nimport com.google.firebase.ai.type.Tool\nimport com.google.firebase.ai.type.Voice\nimport com.google.firebase.ai.type.liveGenerationConfig\nimport com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository.Companion.fetchWeather\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonObject\nimport kotlinx.serialization.json.jsonPrimitive\n\n@Serializable\nobject StreamRealtimeAudioRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass StreamAudioViewModel : BidiViewModel() {\n    init {\n        val liveGenerationConfig = liveGenerationConfig {\n            speechConfig = SpeechConfig(voice = Voice(\"CHARON\"))\n            responseModality = ResponseModality.AUDIO\n        }\n\n        val liveModel =\n            Firebase.ai(backend = GenerativeBackend.googleAI())\n                .liveModel(\n                    // Note that each backend supports a different set of models.\n                    // See our documentation for a breakdown of models by backend:\n                    // https://firebase.google.com/docs/ai-logic/live-api#supported-models\n                    modelName = \"gemini-2.5-flash-native-audio-preview-09-2025\",\n                    generationConfig = liveGenerationConfig,\n                    tools = listOf(\n                        Tool.functionDeclarations(\n                            listOf(\n                                FunctionDeclaration(\n                                    \"fetchWeather\",\n                                    \"Get the weather conditions for a specific US city on a specific date.\",\n                                    mapOf(\n                                        \"city\" to Schema.string(\"The US city of the location.\"),\n                                        \"state\" to Schema.string(\"The US state of the location.\"),\n                                        \"date\" to Schema.string(\n                                            \"The date for which to get the weather.\" +\n                                                    \" Date must be in the format: YYYY-MM-DD.\"\n                                        ),\n                                    ),\n                                )\n                            )\n                        )\n                    ),\n                )\n        runBlocking { liveSession = liveModel.connect() }\n    }\n\n    override fun handler(functionCall: FunctionCallPart): FunctionResponsePart {\n        val response: JsonObject\n        if (functionCall.name == \"fetchWeather\") {\n            val city = functionCall.args[\"city\"]?.jsonPrimitive?.content\n            val state = functionCall.args[\"state\"]?.jsonPrimitive?.content\n            val date = functionCall.args[\"date\"]?.jsonPrimitive?.content\n            runBlocking {\n                response =\n                    if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and !date.isNullOrEmpty()) {\n                        fetchWeather(city!!, state!!, date!!)\n                    } else {\n                        JsonObject(emptyMap())\n                    }\n            }\n        } else {\n            response = JsonObject(emptyMap())\n        }\n        return FunctionResponsePart(functionCall.name, response, functionCall.id)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamVideoViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.live\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.ai.type.ResponseModality\nimport com.google.firebase.ai.type.SpeechConfig\nimport com.google.firebase.ai.type.Voice\nimport com.google.firebase.ai.type.liveGenerationConfig\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject StreamRealtimeVideoRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass StreamVideoViewModel : BidiViewModel() {\n    init {\n        val liveGenerationConfig = liveGenerationConfig {\n            speechConfig = SpeechConfig(voice = Voice(\"CHARON\"))\n            responseModality = ResponseModality.AUDIO\n        }\n\n        // Note that each backend supports a different set of models.\n        // See our documentation for a breakdown of models by backend:\n        // https://firebase.google.com/docs/ai-logic/live-api#supported-models\n        val liveModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).liveModel(\n            modelName = \"gemini-2.5-flash-native-audio-preview-09-2025\",\n            generationConfig = liveGenerationConfig,\n        )\n        runBlocking { liveSession = liveModel.connect() }\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.media.imagen\n\nimport android.graphics.Bitmap\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.ImagenModel\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.ImagenGenerationResponse\nimport com.google.firebase.ai.type.ImagenImageFormat\nimport com.google.firebase.ai.type.ImagenInlineImage\nimport com.google.firebase.ai.type.ImagenPersonFilterLevel\nimport com.google.firebase.ai.type.ImagenSafetyFilterLevel\nimport com.google.firebase.ai.type.ImagenSafetySettings\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.ai.type.imagenGenerationConfig\nimport com.google.firebase.quickstart.ai.ui.ImagenUiState\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject ImagenGenerationRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass ImagenGenerationViewModel : ImagenViewModel() {\n    override val initialPrompt: String = \"\"\n    override val includeAttach: Boolean = false\n    override val selectionOptions: List<String> = emptyList()\n    override val allowEmptyPrompt: Boolean = false\n    override val additionalImage: Bitmap? = null\n    override val imageLabels: List<String> = emptyList()\n\n    private val imagenModel: ImagenModel\n\n    init {\n        imagenModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).imagenModel(\n            modelName = \"imagen-4.0-generate-001\",\n            generationConfig = imagenGenerationConfig {\n                numberOfImages = 4\n                imageFormat = ImagenImageFormat.png()\n            },\n            safetySettings = ImagenSafetySettings(\n                safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,\n                personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL\n            )\n        )\n    }\n\n    override suspend fun performGeneration(\n        inputText: String,\n        currentState: ImagenUiState.Success\n    ): ImagenGenerationResponse<ImagenInlineImage> {\n        return imagenModel.generateImages(inputText)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.media.imagen\n\nimport android.graphics.Bitmap\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.ImagenModel\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.ImagenBackgroundMask\nimport com.google.firebase.ai.type.ImagenEditMode\nimport com.google.firebase.ai.type.ImagenEditingConfig\nimport com.google.firebase.ai.type.ImagenForegroundMask\nimport com.google.firebase.ai.type.ImagenGenerationResponse\nimport com.google.firebase.ai.type.ImagenImageFormat\nimport com.google.firebase.ai.type.ImagenInlineImage\nimport com.google.firebase.ai.type.ImagenPersonFilterLevel\nimport com.google.firebase.ai.type.ImagenRawImage\nimport com.google.firebase.ai.type.ImagenSafetyFilterLevel\nimport com.google.firebase.ai.type.ImagenSafetySettings\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.ai.type.imagenGenerationConfig\nimport com.google.firebase.ai.type.toImagenInlineImage\nimport com.google.firebase.quickstart.ai.ui.ImagenUiState\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject ImagenInpaintingRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass ImagenInpaintingViewModel : ImagenViewModel() {\n    override val initialPrompt: String = \"A sunny beach\"\n    override val includeAttach: Boolean = true\n    override val selectionOptions: List<String> = listOf(\"Mask\", \"Background\", \"Foreground\")\n    override val allowEmptyPrompt: Boolean = true\n    override val additionalImage: Bitmap? = null\n    override val imageLabels: List<String> = emptyList()\n\n    private val imagenModel: ImagenModel\n\n    init {\n        val config = imagenGenerationConfig {\n            numberOfImages = 4\n            imageFormat = ImagenImageFormat.png()\n        }\n        val settings = ImagenSafetySettings(\n            safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,\n            personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL\n        )\n        imagenModel = Firebase.ai(\n            backend = GenerativeBackend.vertexAI()\n        ).imagenModel(\n            modelName = \"imagen-3.0-capability-001\",\n            generationConfig = config,\n            safetySettings = settings\n        )\n    }\n\n    override suspend fun performGeneration(\n        inputText: String,\n        currentState: ImagenUiState.Success\n    ): ImagenGenerationResponse<ImagenInlineImage> {\n        val bitmap = currentState.attachedImage!!\n        val mask = when (currentState.selectedOption) {\n            \"Foreground\" -> ImagenForegroundMask()\n            else -> ImagenBackgroundMask()\n        }\n        return imagenModel.editImage(\n            listOfNotNull(ImagenRawImage(bitmap.toImagenInlineImage()), mask),\n            inputText,\n            ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION)\n        )\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.media.imagen\n\nimport android.graphics.Bitmap\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.ImagenModel\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Dimensions\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.ImagenEditMode\nimport com.google.firebase.ai.type.ImagenEditingConfig\nimport com.google.firebase.ai.type.ImagenGenerationResponse\nimport com.google.firebase.ai.type.ImagenImageFormat\nimport com.google.firebase.ai.type.ImagenImagePlacement\nimport com.google.firebase.ai.type.ImagenInlineImage\nimport com.google.firebase.ai.type.ImagenMaskReference\nimport com.google.firebase.ai.type.ImagenPersonFilterLevel\nimport com.google.firebase.ai.type.ImagenRawMask\nimport com.google.firebase.ai.type.ImagenSafetyFilterLevel\nimport com.google.firebase.ai.type.ImagenSafetySettings\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.ai.type.imagenGenerationConfig\nimport com.google.firebase.ai.type.toImagenInlineImage\nimport com.google.firebase.quickstart.ai.ui.ImagenUiState\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject ImagenOutpaintingRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass ImagenOutpaintingViewModel : ImagenViewModel() {\n    override val initialPrompt: String = \"\"\n    override val includeAttach: Boolean = true\n    override val selectionOptions: List<String> = listOf(\"Image Alignment\", \"Center\", \"Top\", \"Bottom\", \"Left\", \"Right\")\n    override val allowEmptyPrompt: Boolean = true\n    override val additionalImage: Bitmap? = null\n    override val imageLabels: List<String> = emptyList()\n\n    private val imagenModel: ImagenModel\n\n    init {\n        val config = imagenGenerationConfig {\n            numberOfImages = 4\n            imageFormat = ImagenImageFormat.png()\n        }\n        val settings = ImagenSafetySettings(\n            safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,\n            personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL\n        )\n        imagenModel = Firebase.ai(\n            backend = GenerativeBackend.vertexAI()\n        ).imagenModel(\n            modelName = \"imagen-3.0-capability-001\",\n            generationConfig = config,\n            safetySettings = settings\n        )\n    }\n\n    override suspend fun performGeneration(\n        inputText: String,\n        currentState: ImagenUiState.Success\n    ): ImagenGenerationResponse<ImagenInlineImage> {\n        val bitmap = currentState.attachedImage!!\n        val position = when (currentState.selectedOption) {\n            \"Top\" -> ImagenImagePlacement.TOP_CENTER\n            \"Bottom\" -> ImagenImagePlacement.BOTTOM_CENTER\n            \"Left\" -> ImagenImagePlacement.LEFT_CENTER\n            \"Right\" -> ImagenImagePlacement.RIGHT_CENTER\n            else -> ImagenImagePlacement.CENTER\n        }\n        val dimensions = Dimensions(bitmap.width * 2, bitmap.height * 2)\n        val (sourceImage, mask) = ImagenMaskReference.generateMaskAndPadForOutpainting(\n            bitmap.toImagenInlineImage(),\n            dimensions,\n            position\n        )\n        return imagenModel.editImage(\n            listOf(sourceImage, ImagenRawMask(mask.image!!, 0.05)),\n            inputText,\n            ImagenEditingConfig(ImagenEditMode.OUTPAINT)\n        )\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.media.imagen\n\nimport android.graphics.Bitmap\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.ImagenModel\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.ImagenGenerationResponse\nimport com.google.firebase.ai.type.ImagenImageFormat\nimport com.google.firebase.ai.type.ImagenInlineImage\nimport com.google.firebase.ai.type.ImagenPersonFilterLevel\nimport com.google.firebase.ai.type.ImagenRawImage\nimport com.google.firebase.ai.type.ImagenSafetyFilterLevel\nimport com.google.firebase.ai.type.ImagenSafetySettings\nimport com.google.firebase.ai.type.ImagenStyleReference\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.ai.type.imagenGenerationConfig\nimport com.google.firebase.ai.type.toImagenInlineImage\nimport com.google.firebase.quickstart.ai.MainActivity\nimport com.google.firebase.quickstart.ai.ui.ImagenUiState\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject ImagenStyleTransferRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass ImagenStyleTransferViewModel : ImagenViewModel() {\n    override val initialPrompt: String = \"A picture of a cat\"\n    override val includeAttach: Boolean = true\n    override val selectionOptions: List<String> = emptyList()\n    override val allowEmptyPrompt: Boolean = true\n    override val additionalImage: Bitmap = MainActivity.catImage\n    override val imageLabels: List<String> = listOf(\"Style Target\", \"Style Source\")\n\n    private val imagenModel: ImagenModel\n\n    init {\n        val config = imagenGenerationConfig {\n            numberOfImages = 4\n            imageFormat = ImagenImageFormat.png()\n        }\n        val settings = ImagenSafetySettings(\n            safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,\n            personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL\n        )\n        imagenModel = Firebase.ai(\n            backend = GenerativeBackend.vertexAI()\n        ).imagenModel(\n            modelName = \"imagen-3.0-capability-001\",\n            generationConfig = config,\n            safetySettings = settings\n        )\n    }\n\n    override suspend fun performGeneration(\n        inputText: String,\n        currentState: ImagenUiState.Success\n    ): ImagenGenerationResponse<ImagenInlineImage> {\n        val attachedImage = currentState.attachedImage!!\n        return imagenModel.editImage(\n            listOf(\n                ImagenRawImage(MainActivity.catImage.toImagenInlineImage()),\n                ImagenStyleReference(attachedImage.toImagenInlineImage(), 1, \"an art style\")\n            ),\n            \"Generate an image in an art style [1] based on the following caption: $inputText\",\n        )\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.media.imagen\n\nimport android.graphics.Bitmap\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.ImagenModel\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.ImagenGenerationResponse\nimport com.google.firebase.ai.type.ImagenImageFormat\nimport com.google.firebase.ai.type.ImagenInlineImage\nimport com.google.firebase.ai.type.ImagenPersonFilterLevel\nimport com.google.firebase.ai.type.ImagenSafetyFilterLevel\nimport com.google.firebase.ai.type.ImagenSafetySettings\nimport com.google.firebase.ai.type.ImagenSubjectReference\nimport com.google.firebase.ai.type.ImagenSubjectReferenceType\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.ai.type.imagenGenerationConfig\nimport com.google.firebase.ai.type.toImagenInlineImage\nimport com.google.firebase.quickstart.ai.ui.ImagenUiState\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject ImagenSubjectReferenceRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass ImagenSubjectReferenceViewModel : ImagenViewModel() {\n    override val initialPrompt: String = \"<subject> flying through space\"\n    override val includeAttach: Boolean = true\n    override val selectionOptions: List<String> = emptyList()\n    override val allowEmptyPrompt: Boolean = false\n    override val additionalImage: Bitmap? = null\n    override val imageLabels: List<String> = emptyList()\n\n    private val imagenModel: ImagenModel\n\n    init {\n        val config = imagenGenerationConfig {\n            numberOfImages = 4\n            imageFormat = ImagenImageFormat.png()\n        }\n        val settings = ImagenSafetySettings(\n            safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,\n            personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL\n        )\n        imagenModel = Firebase.ai(\n            backend = GenerativeBackend.vertexAI()\n        ).imagenModel(\n            modelName = \"imagen-3.0-capability-001\",\n            generationConfig = config,\n            safetySettings = settings\n        )\n    }\n\n    override suspend fun performGeneration(\n        inputText: String,\n        currentState: ImagenUiState.Success\n    ): ImagenGenerationResponse<ImagenInlineImage> {\n        val attachedImage = currentState.attachedImage!!\n        return imagenModel.editImage(\n            listOf(\n                ImagenSubjectReference(\n                    referenceId = 1,\n                    image = attachedImage.toImagenInlineImage(),\n                    subjectType = ImagenSubjectReferenceType.ANIMAL,\n                    description = \"An animal\"\n                )\n            ),\n            \"Create an image about An animal [1] to match the description: \" +\n                    inputText.replace(\"<subject>\", \"An animal [1]\"),\n        )\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.media.imagen\n\nimport android.graphics.Bitmap\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.TemplateImagenModel\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.ImagenGenerationResponse\nimport com.google.firebase.ai.type.ImagenInlineImage\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.quickstart.ai.ui.ImagenUiState\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject ImagenTemplateRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass ImagenTemplateViewModel : ImagenViewModel() {\n    override val initialPrompt: String = \"List of things that should be in the image\"\n    override val includeAttach: Boolean = false\n    override val selectionOptions: List<String> = emptyList()\n    override val allowEmptyPrompt: Boolean = false\n    override val additionalImage: Bitmap? = null\n    override val imageLabels: List<String> = emptyList()\n\n    private var templateImagenModel: TemplateImagenModel\n\n    init {\n        templateImagenModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).templateImagenModel()\n    }\n\n    override suspend fun performGeneration(\n        inputText: String,\n        currentState: ImagenUiState.Success\n    ): ImagenGenerationResponse<ImagenInlineImage> {\n        return try {\n            templateImagenModel.generateImages(\"imagen-basic\", mapOf(\"prompt\" to inputText))\n        } catch (e: Exception) {\n            if (e.localizedMessage?.contains(\"not found\") == true) {\n                throw Exception(\n                    \"\"\"\n                    Template was not found, please verify that your project contains a template named \"imagen-basic\".\n                    \"\"\".trimIndent()\n                )\n            } else {\n                throw e\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.media.imagen\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport androidx.core.graphics.scale\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.firebase.ai.type.ImagenGenerationResponse\nimport com.google.firebase.ai.type.ImagenInlineImage\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.quickstart.ai.ui.ImagenUiState\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\n\n@OptIn(PublicPreviewAPI::class)\nabstract class ImagenViewModel : ViewModel() {\n\n    abstract val initialPrompt: String\n    abstract val includeAttach: Boolean\n    abstract val selectionOptions: List<String>\n    abstract val allowEmptyPrompt: Boolean\n    abstract val additionalImage: Bitmap?\n    abstract val imageLabels: List<String>\n\n    private val _uiState = MutableStateFlow<ImagenUiState>(ImagenUiState.Success())\n    val uiState: StateFlow<ImagenUiState> = _uiState.asStateFlow()\n\n    protected abstract suspend fun performGeneration(\n        inputText: String,\n        currentState: ImagenUiState.Success\n    ): ImagenGenerationResponse<ImagenInlineImage>\n\n    fun generateImages(inputText: String) {\n        val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success()\n\n        viewModelScope.launch {\n            _uiState.value = ImagenUiState.Loading\n            try {\n                val imageResponse = performGeneration(inputText, currentState)\n                _uiState.value = currentState.copy(images = imageResponse.images.map { it.asBitmap() })\n            } catch (e: Exception) {\n                _uiState.value = ImagenUiState.Error(e.localizedMessage ?: \"Unknown error\")\n            }\n        }\n    }\n\n    suspend fun attachImage(\n        fileInBytes: ByteArray,\n    ) {\n        val originalBitmap = BitmapFactory.decodeByteArray(fileInBytes, 0, fileInBytes.size)\n        val resizedBitmap = originalBitmap.scale(\n            512,\n            (originalBitmap.height * (512.0 / originalBitmap.width)).toInt()\n        )\n        val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success()\n        _uiState.value = currentState.copy(attachedImage = resizedBitmap)\n    }\n\n    fun selectOption(selection: String) {\n        val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success()\n        _uiState.value = currentState.copy(selectedOption = selection)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.content\nimport com.google.firebase.quickstart.ai.ui.ChatUiState\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject AudioSummarizationRoute\n\nclass AudioSummarizationViewModel : ChatViewModel() {\n\n    override val initialPrompt: String =\n        \"\"\"\n        I have attached the audio file. Please analyze it and summarize \n        the contents of the audio as bullet points.            \n        \"\"\".trimIndent()\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-3.1-flash-lite-preview\"\n        )\n        chat = generativeModel.startChat(\n            listOf(\n                content { text(\"Can you help me summarize an audio file?\") },\n                content(\"model\") {\n                    text(\n                        \"Of course! Click on the attach button\" +\n                                \" below and choose an audio file for me to summarize.\"\n                    )\n                }\n            ))\n        _messages.value = chat.history.map { UiChatMessage(it) }\n        _uiState.value = ChatUiState.Success\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioTranslationViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject AudioTranslationRoute\n\nclass AudioTranslationViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"Please translate the audio to Mandarin.\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).generativeModel(\n            modelName = \"gemini-2.5-flash\"\n        )\n        chat = generativeModel.startChat()\n        \n        // Handling the initial fileData in the prompt builder for the first message\n        contentBuilder.fileData(\n            \"https://storage.googleapis.com/cloud-samples-data/generative-ai/audio/\" +\n                    \"How_to_create_a_My_Map_in_Google_Maps.mp3\",\n            \"audio/mpeg\"\n        )\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport android.graphics.BitmapFactory\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerateContentResponse\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.quickstart.ai.ui.Attachment\nimport com.google.firebase.quickstart.ai.ui.ChatUiState\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\n\n@OptIn(PublicPreviewAPI::class)\nabstract class ChatViewModel : ViewModel() {\n\n    protected val _uiState = MutableStateFlow<ChatUiState>(ChatUiState.Success)\n    val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()\n\n    protected val _messages = MutableStateFlow<List<UiChatMessage>>(emptyList())\n    val messages: StateFlow<List<UiChatMessage>> = _messages.asStateFlow()\n\n    protected val _attachments = MutableStateFlow<List<Attachment>>(emptyList())\n    val attachments: StateFlow<List<Attachment>> = _attachments.asStateFlow()\n\n    abstract val initialPrompt: String\n\n    // Builder for the next message\n    protected var contentBuilder = Content.Builder()\n\n    /**\n     * Entry point for sending a message.\n     * Handles adding the message to the UI and setting the loading state.\n     */\n    fun sendMessage(userMessage: String) {\n        val prompt = contentBuilder\n            .text(userMessage)\n            .build()\n\n        _messages.value = _messages.value + UiChatMessage(prompt)\n\n        viewModelScope.launch {\n            _uiState.value = ChatUiState.Loading\n            try {\n                performSendMessage(prompt, _messages.value)\n            } catch (e: Exception) {\n                _uiState.value = ChatUiState.Error(e.localizedMessage ?: \"Unknown error\")\n            } finally {\n                contentBuilder = Content.Builder() // reset the builder\n            }\n        }\n    }\n\n    /**\n     * Subclasses implement this to handle the actual AI logic.\n     */\n    protected abstract suspend fun performSendMessage(\n        prompt: Content,\n        currentMessages: List<UiChatMessage>\n    )\n\n    /**\n     * Centralized method to validate the AI response (grounding check) and update the UI state.\n     */\n    protected fun validateAndDisplayResponse(\n        response: GenerateContentResponse,\n        currentMessages: List<UiChatMessage>\n    ) {\n        val candidate = response.candidates.firstOrNull() ?: return\n\n        // Compliance check for grounding\n        if (candidate.groundingMetadata != null\n            && candidate.groundingMetadata?.groundingChunks?.isNotEmpty() == true\n            && candidate.groundingMetadata?.searchEntryPoint == null\n        ) {\n            _uiState.value = ChatUiState.Error(\n                \"Could not display the response because it was missing required attribution components.\"\n            )\n        } else {\n            _messages.value = currentMessages + UiChatMessage(candidate.content, candidate.groundingMetadata)\n            _attachments.value = emptyList()\n            _uiState.value = ChatUiState.Success\n        }\n    }\n\n    fun attachFile(\n        fileInBytes: ByteArray,\n        mimeType: String?,\n        fileName: String? = \"Unnamed file\"\n    ) {\n        if (mimeType?.contains(\"image\") == true) {\n            // images should be attached as ImageParts\n            contentBuilder.image(decodeBitmapFromImage(fileInBytes))\n        } else {\n            contentBuilder.inlineData(fileInBytes, mimeType ?: \"text/plain\")\n        }\n\n        _attachments.value = _attachments.value + Attachment(fileName ?: \"Unnamed file\")\n    }\n\n    protected fun decodeBitmapFromImage(input: ByteArray) =\n        BitmapFactory.decodeByteArray(input, 0, input.size)\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/CourseRecommendationsViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.content\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject CourseRecommendationsRoute\n\nclass CourseRecommendationsViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"I am interested in Performing Arts. I have taken Theater 1A.\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-2.5-flash\",\n            systemInstruction = content {\n                text(\n                    \"You are a chatbot for the county's performing and fine arts\" +\n                            \" program. You help students decide what course they will\" +\n                            \" take during the summer.\"\n                )\n            }\n        )\n        chat = generativeModel.startChat()\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject DocumentComparisonRoute\n\nclass DocumentComparisonViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"The first document is from 2013, and the second document is\" +\n            \" from 2023. How did the standard deduction evolve?\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).generativeModel(\n            modelName = \"gemini-2.5-flash\"\n        )\n        chat = generativeModel.startChat()\n\n        // Pre-attach the documents\n        contentBuilder.fileData(\n            \"https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/form_1040_2013.pdf\",\n            \"application/pdf\"\n        )\n        contentBuilder.fileData(\n            \"https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/form_1040_2023.pdf\",\n            \"application/pdf\"\n        )\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/GoogleSearchGroundingViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.Tool\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject GoogleSearchGroundingRoute\n\nclass GoogleSearchGroundingViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"What's the weather in Chicago this weekend?\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-2.5-flash\",\n            tools = listOf(Tool.googleSearch())\n        )\n        chat = generativeModel.startChat()\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject ImageBlogCreatorRoute\n\nclass ImageBlogCreatorViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"Write a short, engaging blog post based on this picture.\" +\n            \" It should include a description of the meal in the\" +\n            \" photo and talk about my journey meal prepping.\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).generativeModel(\n            modelName = \"gemini-2.5-flash\"\n        )\n        chat = generativeModel.startChat()\n\n        // Pre-attach the image from cloud storage\n        contentBuilder.fileData(\n            \"https://storage.googleapis.com/cloud-samples-data/generative-ai/image/meal-prep.jpeg\",\n            \"image/jpeg\"\n        )\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.ResponseModality\nimport com.google.firebase.ai.type.generationConfig\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject ImageGenerationRoute\n\nclass ImageGenerationViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"\"\"\n        Hi, can you create a 3d rendered image of a pig\n        with wings and a top hat flying over a happy\n        futuristic scifi city with lots of greenery?\n        \"\"\".trimIndent()\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-2.5-flash-image\",\n            generationConfig = generationConfig {\n                responseModalities = listOf(ResponseModality.TEXT, ResponseModality.IMAGE)\n            }\n        )\n        chat = generativeModel.startChat()\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.TemplateGenerativeModel\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.PublicPreviewAPI\nimport com.google.firebase.quickstart.ai.ui.ServerPromptUiState\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject ServerPromptTemplateRoute\n\n@OptIn(PublicPreviewAPI::class)\nclass ServerPromptTemplateViewModel : ViewModel() {\n    val initialPrompt = \"Jane Doe\"\n    val allowEmptyPrompt = false\n    \n    private val _uiState = MutableStateFlow<ServerPromptUiState>(ServerPromptUiState.Success())\n    val uiState: StateFlow<ServerPromptUiState> = _uiState.asStateFlow()\n\n    private var templateGenerativeModel: TemplateGenerativeModel\n\n    init {\n        templateGenerativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).templateGenerativeModel()\n    }\n\n    fun generate(inputText: String) {\n        viewModelScope.launch {\n            _uiState.value = ServerPromptUiState.Loading\n            try {\n                val response = templateGenerativeModel\n                    .generateContent(\"input-system-instructions\", mapOf(\"customerName\" to inputText))\n                _uiState.value = ServerPromptUiState.Success(response.text)\n            } catch (e: Exception) {\n                _uiState.value = ServerPromptUiState.Error(\n                    if (e.localizedMessage?.contains(\"not found\") == true) {\n                        \"\"\"\n                        Template was not found, please verify that your project contains a template \n                        named \"input-system-instructions\".   \n                        \"\"\".trimIndent()\n                    } else {\n                        e.localizedMessage ?: \"Unknown error\"\n                    }\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.GenerativeModel\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.content\nimport com.google.firebase.ai.type.generationConfig\nimport com.google.firebase.ai.type.thinkingConfig\nimport kotlinx.coroutines.Dispatchers\nimport com.google.firebase.quickstart.ai.ui.SvgUiState\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\n\n@Serializable\nobject SvgRoute\n\nclass SvgViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow<SvgUiState>(SvgUiState.Success())\n    val uiState: StateFlow<SvgUiState> = _uiState.asStateFlow()\n\n    private val generativeModel: GenerativeModel\n\n    init {\n        generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-3-flash-preview\",\n            systemInstruction = content {\n                text(\n                    \"\"\"\n            You are an expert at turning image prompts into SVG code. When given a prompt,\n            use your creativity to code a 800x600 SVG rendering of it.\n            Always add viewBox=\"0 0 800 600\" to the root svg tag. Do\n            not import external assets, they won't work. Return ONLY the SVG code, nothing else,\n            no commentary.\n            \"\"\".trimIndent()\n                )\n            },\n            generationConfig = generationConfig {\n                thinkingConfig {\n                    thinkingBudget = -1\n                }\n            }\n        )\n    }\n\n    fun generateSVG(prompt: String) {\n        val currentSvgs = (_uiState.value as? SvgUiState.Success)?.svgs ?: emptyList()\n        _uiState.value = SvgUiState.Loading\n        viewModelScope.launch(Dispatchers.IO) {\n            try {\n                val response = generativeModel.generateContent(prompt)\n                val newSvg = response.text\n                if (newSvg != null) {\n                    _uiState.value = SvgUiState.Success(listOf(newSvg) + currentSvgs)\n                } else {\n                    _uiState.value = SvgUiState.Success(currentSvgs)\n                }\n            } catch (e: Exception) {\n                _uiState.value = SvgUiState.Error(e.localizedMessage ?: \"Unknown error\")\n            }\n        }\n    }\n}"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ThinkingChatViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.generationConfig\nimport com.google.firebase.ai.type.thinkingConfig\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject ThinkingChatRoute\n\nclass ThinkingChatViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"Analogize photosynthesis and growing up.\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-2.5-flash\",\n            generationConfig = generationConfig {\n                thinkingConfig = thinkingConfig {\n                    includeThoughts = true\n                    thinkingBudget = -1 // Dynamic Thinking\n                }\n            }\n        )\n        chat = generativeModel.startChat()\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TranslationViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.content\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\nimport kotlinx.serialization.Serializable\n\n@Serializable\nobject TranslationRoute\n\nclass TranslationViewModel : ChatViewModel() {\n    override val initialPrompt: String\n        get() = \"\"\"\n            Translate the following text to Spanish:\n            Hey, are you down to grab some pizza later? I'm starving!\n        \"\"\".trimIndent()\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-3.1-flash-lite-preview\",\n            systemInstruction = content {\n                text(\"Only output the translated text\")\n            }\n        )\n\n        chat = generativeModel.startChat()\n    }\n\n    override suspend fun performSendMessage(\n        prompt: Content,\n        currentMessages: List<UiChatMessage>\n    ) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TravelTipsViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.content\nimport com.google.firebase.quickstart.ai.ui.ChatUiState\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject TravelTipsRoute\n\nclass TravelTipsViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"What else is important when traveling?\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-2.5-flash\",\n            systemInstruction = content {\n                text(\n                    \"You are a Travel assistant. You will answer\" +\n                            \" questions the user asks based on the information listed\" +\n                            \" in Relevant Information. Do not hallucinate. Do not use\" +\n                            \" the internet.\"\n                )\n            }\n        )\n\n        chat = generativeModel.startChat(\n            history = listOf(\n                content(\"role\") {\n                    text(\"I have never traveled before. When should I book a flight?\")\n                },\n                content(\"model\") {\n                    text(\n                        \"You should book flights a couple of months ahead of time.\" +\n                                \" It will be cheaper and more flexible for you.\"\n                    )\n                },\n                content(\"user\") {\n                    text(\"Do I need a passport?\")\n                },\n                content(\"model\") {\n                    text(\n                        \"If you are traveling outside your own country, make sure\" +\n                                \" your passport is up-to-date and valid for more\" +\n                                \" than 6 months during your travel.\"\n                    )\n                }\n            )\n        )\n\n        _messages.value = chat.history.map { UiChatMessage(it) }\n        _uiState.value = ChatUiState.Success\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoHashtagGeneratorViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject VideoHashtagGeneratorRoute\n\nclass VideoHashtagGeneratorViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"Generate 5-10 hashtags that relate to the video content.\" +\n            \" Try to use more popular and engaging terms,\" +\n            \" e.g. #Viral. Do not add content not related to\" +\n            \" the video.\\n Start the output with 'Tags:'\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).generativeModel(\n            modelName = \"gemini-2.5-flash\"\n        )\n        chat = generativeModel.startChat()\n\n        // Pre-attach the video\n        contentBuilder.fileData(\n            \"https://storage.googleapis.com/cloud-samples-data/generative-ai/video/google_home_celebrity_ad.mp4\",\n            \"video/mpeg\"\n        )\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.content\nimport com.google.firebase.quickstart.ai.ui.ChatUiState\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\n\n@Serializable\nobject VideoSummarizationRoute\n\nclass VideoSummarizationViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"I have attached the video file. Provide a description of\" +\n            \" the video. The description should also contain\" +\n            \" anything important which people say in the video.\"\n\n    private val chat: Chat\n\n    init {\n        val chatHistory = listOf(\n            content { text(\"Can you help me with the description of a video file?\") },\n            content(\"model\") {\n                text(\n                    \"Sure! Click on the attach button below and choose a\" +\n                            \" video file for me to describe.\"\n                )\n            }\n        )\n\n        _messages.value = chatHistory.map { UiChatMessage(it) }\n        _uiState.value = ChatUiState.Success\n\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-2.5-flash\"\n        )\n        chat = generativeModel.startChat(chatHistory)\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        validateAndDisplayResponse(response, currentMessages)\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/WeatherChatViewModel.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text\n\nimport kotlinx.serialization.Serializable\n\nimport android.util.Log\nimport com.google.firebase.Firebase\nimport com.google.firebase.ai.Chat\nimport com.google.firebase.ai.ai\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.FunctionDeclaration\nimport com.google.firebase.ai.type.FunctionResponsePart\nimport com.google.firebase.ai.type.GenerateContentResponse\nimport com.google.firebase.ai.type.GenerativeBackend\nimport com.google.firebase.ai.type.Schema\nimport com.google.firebase.ai.type.Tool\nimport com.google.firebase.ai.type.content\nimport com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository\nimport com.google.firebase.quickstart.ai.ui.UiChatMessage\nimport kotlinx.serialization.json.JsonObject\nimport kotlinx.serialization.json.JsonPrimitive\nimport kotlinx.serialization.json.jsonPrimitive\n\n@Serializable\nobject WeatherChatRoute\n\nclass WeatherChatViewModel : ChatViewModel() {\n\n    override val initialPrompt: String = \"What was the weather in Boston, MA on October 17, 2024?\"\n\n    private val chat: Chat\n\n    init {\n        val generativeModel = Firebase.ai(\n            backend = GenerativeBackend.googleAI()\n        ).generativeModel(\n            modelName = \"gemini-2.5-flash\",\n            tools = listOf(\n                Tool.functionDeclarations(\n                    listOf(\n                        FunctionDeclaration(\n                            \"fetchWeather\",\n                            \"Get the weather conditions for a specific US city on a specific date.\",\n                            mapOf(\n                                \"city\" to Schema.string(\"The US city of the location.\"),\n                                \"state\" to Schema.string(\"The US state of the location.\"),\n                                \"date\" to Schema.string(\n                                    \"The date for which to get the weather.\" +\n                                            \" Date must be in the format: YYYY-MM-DD.\"\n                                ),\n                            ),\n                        )\n                    )\n                )\n            )\n        )\n        chat = generativeModel.startChat()\n    }\n\n    override suspend fun performSendMessage(prompt: Content, currentMessages: List<UiChatMessage>) {\n        val response = chat.sendMessage(prompt)\n        if (response.functionCalls.isEmpty()) {\n            validateAndDisplayResponse(response, currentMessages)\n        } else {\n            handleFunctionCalls(response, currentMessages)\n        }\n    }\n\n    private suspend fun handleFunctionCalls(\n        response: GenerateContentResponse,\n        currentMessages: List<UiChatMessage>\n    ) {\n        response.functionCalls.forEach { functionCall ->\n            Log.d(\n                \"WeatherChatViewModel\", \"Model responded with function call:\" +\n                        functionCall.name\n            )\n            when (functionCall.name) {\n                \"fetchWeather\" -> {\n                    val city = functionCall.args[\"city\"]?.jsonPrimitive?.content\n                    val state = functionCall.args[\"state\"]?.jsonPrimitive?.content // Fixed state retrieval\n                    val date = functionCall.args[\"date\"]?.jsonPrimitive?.content\n\n                    val finalResponse = if (city == null || state == null || date == null) {\n                        chat.sendMessage(content(\"function\") {\n                            part(FunctionResponsePart(\"fetchWeather\",\n                                JsonObject(\n                                    mapOf(\n                                        \"error\" to JsonPrimitive(\"Unable to fetch weather - one of the parameters was null\"),\n                                    )\n                                )))\n                        })\n                    } else {\n                        val functionResponse = WeatherRepository\n                            .fetchWeather(city, state, date)\n\n                        chat.sendMessage(content(\"function\") {\n                            part(FunctionResponsePart(\"fetchWeather\", functionResponse))\n                        })\n                    }\n\n                    validateAndDisplayResponse(finalResponse, currentMessages)\n                }\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/functioncalling/WeatherRepository.kt",
    "content": "package com.google.firebase.quickstart.ai.feature.text.functioncalling\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport kotlinx.serialization.json.JsonObject\nimport kotlinx.serialization.json.JsonPrimitive\n\n/**\n * Hypothetical repository that calls an external weather API.\n */\nclass WeatherRepository {\n\n    companion object {\n        suspend fun fetchWeather(\n            city: String, state: String, date: String\n        ): JsonObject = withContext(Dispatchers.IO) {\n            // For demo purposes, this hypothetical response is\n            // hardcoded here in the expected format.\n            return@withContext JsonObject(\n                mapOf(\n                    \"temperature\" to JsonPrimitive(38),\n                    \"chancePrecipitation\" to JsonPrimitive(\"56%\"),\n                    \"cloudConditions\" to JsonPrimitive(\"partlyCloudy\")\n                )\n            )\n        }\n    }\n}"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/CameraView.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport android.annotation.SuppressLint\nimport android.graphics.Bitmap\nimport androidx.camera.core.CameraSelector\nimport androidx.camera.core.ImageAnalysis\nimport androidx.camera.core.ImageProxy\nimport androidx.camera.core.Preview\nimport androidx.camera.lifecycle.ProcessCameraProvider\nimport androidx.camera.view.PreviewView\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport kotlin.time.Duration.Companion.seconds\n\n@Composable\nfun CameraView(\n    modifier: Modifier = Modifier,\n    cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,\n    onFrameCaptured: (Bitmap) -> Unit,\n) {\n    val context = LocalContext.current\n    val lifecycleOwner = LocalLifecycleOwner.current\n    val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }\n\n    AndroidView(\n        factory = { ctx ->\n            val previewView = PreviewView(ctx)\n            val executor = ContextCompat.getMainExecutor(ctx)\n            cameraProviderFuture.addListener(\n                {\n                    val cameraProvider = cameraProviderFuture.get()\n                    bindPreview(\n                        lifecycleOwner,\n                        previewView,\n                        cameraProvider,\n                        cameraSelector,\n                        onFrameCaptured,\n                    )\n                },\n                executor,\n            )\n            previewView\n        },\n        modifier = modifier,\n    )\n}\n\nprivate fun bindPreview(\n    lifecycleOwner: LifecycleOwner,\n    previewView: PreviewView,\n    cameraProvider: ProcessCameraProvider,\n    cameraSelector: CameraSelector,\n    onFrameCaptured: (Bitmap) -> Unit,\n) {\n    val preview =\n        Preview.Builder().build().also { it.surfaceProvider = previewView.surfaceProvider }\n\n    val imageAnalysis =\n        ImageAnalysis.Builder()\n            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)\n            .build()\n            .also {\n                it.setAnalyzer(\n                    ContextCompat.getMainExecutor(previewView.context),\n                    SnapshotFrameAnalyzer(onFrameCaptured),\n                )\n            }\n\n    cameraProvider.unbindAll()\n    cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis)\n}\n\n// Calls the [onFrameCaptured] callback with the captured frame every second.\nprivate class SnapshotFrameAnalyzer(private val onFrameCaptured: (Bitmap) -> Unit) :\n    ImageAnalysis.Analyzer {\n    private var lastFrameTimestamp = 0L\n    private val interval = 1.seconds // 1 second\n\n    @SuppressLint(\"UnsafeOptInUsageError\")\n    override fun analyze(image: ImageProxy) {\n        val currentTimestamp = System.currentTimeMillis()\n        if (lastFrameTimestamp == 0L) {\n            lastFrameTimestamp = currentTimestamp\n        }\n\n        if (currentTimestamp - lastFrameTimestamp >= interval.inWholeMilliseconds) {\n            onFrameCaptured(image.toBitmap())\n            lastFrameTimestamp = currentTimestamp\n        }\n        image.close()\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatScreen.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.graphics.Color\nimport android.net.Uri\nimport android.provider.OpenableColumns\nimport android.text.format.Formatter\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.BoxWithConstraints\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.widthIn\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.LazyListState\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.ClickableText\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.Send\nimport androidx.compose.material.icons.filled.AttachFile\nimport androidx.compose.material.icons.filled.Attachment\nimport androidx.compose.material.icons.filled.ExpandLess\nimport androidx.compose.material.icons.filled.ExpandMore\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.IconButtonDefaults\nimport androidx.compose.material3.LinearProgressIndicator\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.text.AnnotatedString\nimport androidx.compose.ui.text.SpanStyle\nimport androidx.compose.ui.text.font.FontStyle\nimport androidx.compose.ui.text.input.KeyboardCapitalization\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextDecoration\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.core.net.toUri\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.google.firebase.ai.type.FileDataPart\nimport com.google.firebase.ai.type.ImagePart\nimport com.google.firebase.ai.type.InlineDataPart\nimport com.google.firebase.ai.type.TextPart\nimport com.google.firebase.ai.type.WebGroundingChunk\nimport com.google.firebase.quickstart.ai.feature.text.ChatViewModel\nimport kotlinx.coroutines.launch\n\n\n@Composable\nfun ChatScreen(\n    chatViewModel: ChatViewModel\n) {\n    val uiState by chatViewModel.uiState.collectAsStateWithLifecycle()\n    val messages by chatViewModel.messages.collectAsStateWithLifecycle()\n    val attachments by chatViewModel.attachments.collectAsStateWithLifecycle()\n\n    val initialPrompt: String = chatViewModel.initialPrompt\n\n    val listState = rememberLazyListState()\n    val coroutineScope = rememberCoroutineScope()\n\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n    ) {\n        ChatList(\n            messages,\n            listState,\n            modifier = Modifier\n                .fillMaxSize()\n                .weight(0.5f)\n        )\n        \n        Box(\n            contentAlignment = Alignment.BottomCenter\n        ) {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(color = MaterialTheme.colorScheme.surfaceContainer)\n            ) {\n                if (uiState is ChatUiState.Loading) {\n                    LinearProgressIndicator(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(horizontal = 16.dp, vertical = 8.dp)\n                    )\n                }\n                (uiState as? ChatUiState.Error)?.let {\n                    Card(\n                        colors = CardDefaults.cardColors(\n                            containerColor = MaterialTheme.colorScheme.errorContainer\n                        ),\n                        modifier = Modifier.fillMaxWidth()\n                    ) {\n                        Text(\n                            text = it.message,\n                            modifier = Modifier.padding(16.dp),\n                            color = MaterialTheme.colorScheme.onErrorContainer\n                        )\n                    }\n                }\n                AttachmentsList(attachments)\n                val context = LocalContext.current\n                val contentResolver = context.contentResolver\n                MessageInput(\n                    initialPrompt = initialPrompt,\n                    onSendMessage = { inputText ->\n                        chatViewModel.sendMessage(inputText)\n                    },\n                    resetScroll = {\n                        coroutineScope.launch {\n                            listState.scrollToItem(0)\n                        }\n                    },\n                    onFileAttached = { uri ->\n                        val mimeType = contentResolver.getType(uri).orEmpty()\n                        var fileName: String? = null\n                        // Fetch file name and size\n                        contentResolver.query(uri, null, null, null, null)?.use { cursor ->\n                            val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)\n                            val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)\n                            cursor.moveToFirst()\n                            val humanReadableSize = Formatter.formatShortFileSize(\n                                context,\n                                cursor.getLong(sizeIndex)\n                            )\n                            fileName = \"${cursor.getString(nameIndex)} ($humanReadableSize)\"\n                        }\n\n                        contentResolver.openInputStream(uri)?.use { stream ->\n                            val bytes = stream.readBytes()\n                            chatViewModel.attachFile(bytes, mimeType, fileName)\n                        }\n                    },\n                    isLoading = uiState is ChatUiState.Loading\n                )\n            }\n        }\n    }\n}\n\n@SuppressLint(\"UnusedBoxWithConstraintsScope\")\n@Composable\nfun ChatBubbleItem(\n    message: UiChatMessage\n) {\n    val isModelMessage = message.content.role == \"model\"\n\n    val isDarkTheme = isSystemInDarkTheme()\n\n    val backgroundColor = when (message.content.role) {\n        \"user\" -> MaterialTheme.colorScheme.tertiaryContainer\n        else -> MaterialTheme.colorScheme.secondaryContainer\n    }\n\n    val textColor = if (isModelMessage) {\n        MaterialTheme.colorScheme.onBackground\n    } else {\n        MaterialTheme.colorScheme.onTertiaryContainer\n    }\n\n    val bubbleShape = if (isModelMessage) {\n        RoundedCornerShape(4.dp, 20.dp, 20.dp, 20.dp)\n    } else {\n        RoundedCornerShape(20.dp, 4.dp, 20.dp, 20.dp)\n    }\n\n    val horizontalAlignment = if (isModelMessage) {\n        Alignment.Start\n    } else {\n        Alignment.End\n    }\n\n    Column(\n        horizontalAlignment = horizontalAlignment,\n        modifier = Modifier\n            .padding(horizontal = 8.dp, vertical = 4.dp)\n            .fillMaxWidth()\n    ) {\n        Text(\n            text = message.content.role?.uppercase() ?: \"USER\",\n            style = MaterialTheme.typography.bodySmall,\n            modifier = Modifier.padding(bottom = 4.dp)\n        )\n        Row {\n            BoxWithConstraints {\n                Card(\n                    colors = CardDefaults.cardColors(containerColor = backgroundColor),\n                    shape = bubbleShape,\n                    modifier = Modifier.widthIn(0.dp, maxWidth * 0.9f)\n                ) {\n                    Column(\n                        modifier = Modifier\n                            .padding(16.dp)\n                            .fillMaxWidth()\n                    ) {\n                        message.content.parts.forEach { part ->\n                            when (part) {\n                                is TextPart -> {\n                                    if (part.isThought) {\n                                        ThoughtBubble(part.text)\n                                    } else {\n                                        Text(\n                                            text = part.text.trimIndent(),\n                                            modifier = Modifier.fillMaxWidth(),\n                                            color = textColor\n                                        )\n                                    }\n                                }\n\n                                is ImagePart -> {\n                                    Image(\n                                        bitmap = part.image.asImageBitmap(),\n                                        contentDescription = \"Attached image\",\n                                        modifier = Modifier\n                                            .fillMaxWidth()\n                                            .padding(bottom = 4.dp)\n                                    )\n                                }\n\n                                is InlineDataPart -> {\n                                    // TODO: show a human readable version of audio, PDFs and videos\n                                    val attachmentType = if (part.mimeType.contains(\"audio\")) {\n                                        \"audio attached\"\n                                    } else if (part.mimeType.contains(\"application/pdf\")) {\n                                        \"PDF attached\"\n                                    } else if (part.mimeType.contains(\"video\")) {\n                                        \"video\"\n                                    } else {\n                                        \"file attached\"\n                                    }\n                                    Text(\n                                        text = \"($attachmentType)\",\n                                        modifier = Modifier\n                                            .padding(4.dp)\n                                            .fillMaxWidth(),\n                                        style = MaterialTheme.typography.bodySmall,\n                                        textAlign = TextAlign.End\n                                    )\n                                }\n\n                                is FileDataPart -> {\n                                    Text(\n                                        text = part.uri,\n                                        style = MaterialTheme.typography.bodySmall,\n                                        textAlign = TextAlign.End,\n                                        modifier = Modifier\n                                            .background(\n                                                backgroundColor.copy(\n                                                    red = backgroundColor.red * 0.7f,\n                                                    green = backgroundColor.green * 0.7f,\n                                                    blue = backgroundColor.blue * 0.7f\n                                                )\n                                            )\n                                            .padding(4.dp)\n                                            .fillMaxWidth()\n                                    )\n                                }\n                            }\n                        }\n                        message.groundingMetadata?.let { metadata ->\n                            HorizontalDivider(modifier = Modifier.padding(vertical = 18.dp))\n\n                            // Search Entry Point (WebView)\n                            metadata.searchEntryPoint?.let { searchEntryPoint ->\n                                val context = LocalContext.current\n                                AndroidView(\n                                    factory = {\n                                        WebView(it).apply {\n                                            webViewClient = object : WebViewClient() {\n                                                override fun shouldOverrideUrlLoading(\n                                                    view: WebView?,\n                                                    request: WebResourceRequest?\n                                                ): Boolean {\n                                                    request?.url?.let { uri ->\n                                                        val intent = Intent(Intent.ACTION_VIEW, uri)\n                                                        context.startActivity(intent)\n                                                    }\n                                                    // Return true to indicate we handled the URL loading\n                                                    return true\n                                                }\n                                            }\n\n                                            setBackgroundColor(Color.TRANSPARENT)\n                                            loadDataWithBaseURL(\n                                                null,\n                                                searchEntryPoint.renderedContent,\n                                                \"text/html\",\n                                                \"UTF-8\",\n                                                null\n                                            )\n                                        }\n                                    },\n                                    modifier = Modifier\n                                        .clip(RoundedCornerShape(22.dp))\n                                        .fillMaxHeight()\n                                        .fillMaxWidth()\n                                )\n                            }\n\n                            if (metadata.groundingChunks.isNotEmpty()) {\n                                Text(\n                                    text = \"Sources\",\n                                    style = MaterialTheme.typography.titleSmall,\n                                    modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)\n                                )\n                                metadata.groundingChunks.forEach { chunk ->\n                                    chunk.web?.let { SourceLinkView(it) }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun SourceLinkView(\n    webChunk: WebGroundingChunk\n) {\n    val context = LocalContext.current\n    val annotatedString = AnnotatedString.Builder(webChunk.title ?: \"Untitled Source\").apply {\n        addStyle(\n            style = SpanStyle(\n                color = MaterialTheme.colorScheme.primary,\n                textDecoration = TextDecoration.Underline\n            ),\n            start = 0,\n            end = webChunk.title?.length ?: \"Untitled Source\".length\n        )\n        webChunk.uri?.let { addStringAnnotation(\"URL\", it, 0, it.length) }\n    }.toAnnotatedString()\n\n    Row(modifier = Modifier.padding(bottom = 8.dp)) {\n        Icon(\n            Icons.Default.Attachment,\n            contentDescription = \"Source link\",\n            modifier = Modifier.padding(end = 8.dp)\n        )\n        ClickableText(text = annotatedString, onClick = { offset ->\n            annotatedString.getStringAnnotations(tag = \"URL\", start = offset, end = offset)\n                .firstOrNull()?.let { annotation ->\n                    context.startActivity(Intent(Intent.ACTION_VIEW, annotation.item.toUri()))\n                }\n        })\n    }\n}\n\n@Composable\nfun ChatList(\n    chatMessages: List<UiChatMessage>,\n    listState: LazyListState,\n    modifier: Modifier = Modifier\n) {\n    LazyColumn(\n        reverseLayout = true,\n        state = listState,\n        modifier = modifier\n    ) {\n        items(chatMessages.reversed()) { message ->\n            ChatBubbleItem(message)\n        }\n    }\n}\n\n@Composable\nfun MessageInput(\n    initialPrompt: String,\n    onSendMessage: (String) -> Unit,\n    resetScroll: () -> Unit = {},\n    onFileAttached: (Uri) -> Unit,\n    isLoading: Boolean = false\n) {\n    var userMessage by rememberSaveable { mutableStateOf(initialPrompt) }\n\n    Row(\n        modifier = Modifier\n            .padding(8.dp)\n            .fillMaxWidth()\n    ) {\n        OutlinedTextField(\n            value = userMessage,\n            label = { Text(\"Message\") },\n            onValueChange = { userMessage = it },\n            keyboardOptions = KeyboardOptions(\n                capitalization = KeyboardCapitalization.Sentences\n            ),\n            modifier = Modifier\n                .align(Alignment.CenterVertically)\n                .padding(end = 4.dp)\n                .fillMaxWidth()\n                .weight(1f)\n        )\n        AttachmentsMenu(\n            modifier = Modifier.align(Alignment.CenterVertically),\n            onFileAttached = onFileAttached\n        )\n        IconButton(\n            onClick = {\n                if (userMessage.isNotBlank()) {\n                    onSendMessage(userMessage)\n                    userMessage = \"\"\n                    resetScroll()\n                }\n            },\n            enabled = !isLoading,\n            modifier = Modifier\n                .align(Alignment.CenterVertically)\n                .clip(CircleShape)\n                .background(\n                    color = if (isLoading) {\n                        IconButtonDefaults.iconButtonColors().disabledContainerColor\n                    } else {\n                        MaterialTheme.colorScheme.primary\n                    }\n                )\n        ) {\n            Icon(\n                Icons.AutoMirrored.Default.Send,\n                contentDescription = \"Send\",\n                tint = MaterialTheme.colorScheme.onPrimary,\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(8.dp)\n            )\n        }\n    }\n}\n\n@Composable\nfun AttachmentsMenu(\n    modifier: Modifier = Modifier,\n    onFileAttached: (Uri) -> Unit\n) {\n    var expanded by remember { mutableStateOf(false) }\n\n    val openDocument = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->\n        uri?.let {\n            onFileAttached(it)\n        }\n    }\n    Box(\n        modifier = modifier\n            .padding(end = 4.dp)\n    ) {\n        IconButton(\n            onClick = {\n                expanded = !expanded\n            }\n        ) {\n            Icon(\n                Icons.Default.AttachFile,\n                contentDescription = \"Attach\",\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(4.dp)\n            )\n        }\n        DropdownMenu(\n            expanded = expanded,\n            onDismissRequest = { expanded = false }\n        ) {\n            Text(\n                text = \"Attach\",\n                modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.primary\n            )\n            DropdownMenuItem(\n                text = { Text(\"Image / Video\") },\n                onClick = {\n                    openDocument.launch(arrayOf(\"image/*\", \"video/*\"))\n                    expanded = !expanded\n                }\n            )\n            DropdownMenuItem(\n                text = { Text(\"Audio\") },\n                onClick = {\n                    openDocument.launch(arrayOf(\"audio/*\"))\n                    expanded = !expanded\n                }\n            )\n            DropdownMenuItem(\n                text = { Text(\"Document (PDF)\") },\n                onClick = {\n                    openDocument.launch(arrayOf(\"application/pdf\"))\n                    expanded = !expanded\n                }\n            )\n        }\n    }\n}\n\n\n@Composable\nfun AttachmentsList(\n    attachments: List<Attachment>\n) {\n    Column(\n        modifier = Modifier.fillMaxWidth()\n    ) {\n        attachments.forEach { attachment ->\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(horizontal = 8.dp, vertical = 4.dp)\n            ) {\n                Icon(\n                    Icons.Default.Attachment,\n                    contentDescription = \"Attachment icon\",\n                    modifier = Modifier\n                        .padding(4.dp)\n                        .align(Alignment.CenterVertically)\n                )\n                Column(modifier = Modifier.align (Alignment.CenterVertically)) {\n                    attachment.image?.let {\n                        Image(\n                            bitmap = it.asImageBitmap(),\n                            contentDescription = attachment.fileName,\n                            modifier = Modifier\n                        )\n                    }\n                    Text(\n                        text = attachment.fileName,\n                        style = MaterialTheme.typography.bodySmall,\n                        modifier = Modifier\n                            .padding(horizontal = 4.dp)\n                    )\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun ThoughtBubble(\n    text: String\n) {\n    var expanded by remember { mutableStateOf(false) }\n\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(vertical = 4.dp)\n            .clip(RoundedCornerShape(8.dp))\n            .background(MaterialTheme.colorScheme.tertiaryContainer)\n            .clickable { expanded = !expanded }\n            .padding(8.dp)\n    ) {\n        Row(\n            verticalAlignment = Alignment.CenterVertically,\n            modifier = Modifier.fillMaxWidth()\n        ) {\n            Icon(\n                imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,\n                contentDescription = if (expanded) \"Collapse thoughts\" else \"Expand thoughts\",\n                modifier = Modifier.padding(end = 8.dp),\n                tint = MaterialTheme.colorScheme.onTertiaryContainer\n            )\n            Text(\n                text = \"Thoughts\",\n                style = MaterialTheme.typography.labelMedium,\n                color = MaterialTheme.colorScheme.onTertiaryContainer\n            )\n        }\n\n        AnimatedVisibility(visible = expanded) {\n            Column {\n                HorizontalDivider(\n                    modifier = Modifier.padding(vertical = 8.dp),\n                    color = MaterialTheme.colorScheme.onTertiaryContainer\n                )\n                Text(\n                    text = text.trimIndent(),\n                    style = MaterialTheme.typography.bodySmall.copy(\n                        fontStyle = FontStyle.Italic\n                    ),\n                    modifier = Modifier.fillMaxWidth(),\n                    color = MaterialTheme.colorScheme.onTertiaryContainer\n                )\n            }\n        }\n    }\n}"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatUiState.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport android.graphics.Bitmap\nimport com.google.firebase.ai.type.Content\nimport com.google.firebase.ai.type.GroundingMetadata\n\n/**\n * Meant to present attachments in the UI\n */\ndata class Attachment(\n    val fileName: String,\n    val image: Bitmap? = null // only for image attachments\n)\n\n/**\n * A wrapper for a model [Content] object that includes additional UI-specific metadata.\n */\ndata class UiChatMessage(\n    val content: Content,\n    val groundingMetadata: GroundingMetadata? = null,\n)\n\nsealed interface ChatUiState {\n    data object Loading : ChatUiState\n    data object Success : ChatUiState\n    data class Error(val message: String) : ChatUiState\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenScreen.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport android.net.Uri\nimport android.provider.OpenableColumns\nimport android.text.format.Formatter\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.grid.GridCells\nimport androidx.compose.foundation.lazy.grid.LazyHorizontalGrid\nimport androidx.compose.foundation.lazy.grid.items\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.google.firebase.quickstart.ai.R\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel\nimport kotlinx.coroutines.launch\n\n\n@Composable\nfun ImagenScreen(\n    imagenViewModel: ImagenViewModel\n) {\n    val uiState by imagenViewModel.uiState.collectAsStateWithLifecycle()\n    val successState = uiState as? ImagenUiState.Success\n    val attachedImage = successState?.attachedImage\n    val generatedImages = successState?.images ?: emptyList()\n\n    var imagenPrompt by rememberSaveable { mutableStateOf(imagenViewModel.initialPrompt) }\n\n    val context = LocalContext.current\n    val contentResolver = context.contentResolver\n    val scope = rememberCoroutineScope()\n    val openDocument = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { optionalUri: Uri? ->\n        optionalUri?.let { uri ->\n            var fileName: String? = null\n            // Fetch file name and size\n            contentResolver.query(uri, null, null, null, null)?.use { cursor ->\n                val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)\n                val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)\n                cursor.moveToFirst()\n                val humanReadableSize = Formatter.formatShortFileSize(\n                    context, cursor.getLong(sizeIndex)\n                )\n                fileName = \"${cursor.getString(nameIndex)} ($humanReadableSize)\"\n            }\n\n            contentResolver.openInputStream(uri)?.use { stream ->\n                val bytes = stream.readBytes()\n                scope.launch {\n                    imagenViewModel.attachImage(bytes)\n                }\n            }\n        }\n    }\n\n    Column(\n        modifier = Modifier.verticalScroll(rememberScrollState())\n    ) {\n        ElevatedCard(\n            modifier = Modifier\n                .padding(all = 16.dp)\n                .fillMaxWidth(),\n            shape = MaterialTheme.shapes.large\n        ) {\n            OutlinedTextField(\n                value = imagenPrompt,\n                label = { Text(\"Prompt\") },\n                placeholder = { Text(\"Enter text to generate image\") },\n                onValueChange = { imagenPrompt = it },\n                modifier = Modifier\n                    .padding(16.dp)\n                    .fillMaxWidth()\n            )\n            if (imagenViewModel.selectionOptions.isNotEmpty()) {\n                DropDownMenu(imagenViewModel.selectionOptions) { imagenViewModel.selectOption(it) }\n            }\n            val attachmentsList = buildList {\n                if (imagenViewModel.additionalImage != null) {\n                    add(\n                        Attachment(\n                            imagenViewModel.imageLabels.getOrElse(0) { \"\" },\n                            imagenViewModel.additionalImage\n                        )\n                    )\n                }\n                if (attachedImage != null) {\n                    add(Attachment(imagenViewModel.imageLabels.getOrElse(1) { \"\" }, attachedImage))\n                }\n            }\n\n            if (imagenViewModel.includeAttach && attachmentsList.isNotEmpty()) {\n                AttachmentsList(attachmentsList)\n            }\n            Row() {\n                if (imagenViewModel.includeAttach) {\n                    TextButton(\n                        onClick = {\n                            openDocument.launch(arrayOf(\"image/*\"))\n                        },\n                        modifier = Modifier\n                            .padding(end = 16.dp, bottom = 16.dp)\n                    ) { Text(\"Attach\") }\n                }\n                TextButton(\n                    onClick = {\n                        if (imagenViewModel.allowEmptyPrompt || imagenPrompt.isNotBlank()) {\n                            imagenViewModel.generateImages(imagenPrompt)\n                        }\n                    },\n                    modifier = Modifier\n                        .padding(end = 16.dp, bottom = 16.dp)\n                ) {\n                    Text(\"Generate\")\n                }\n            }\n\n        }\n\n        if (uiState is ImagenUiState.Loading) {\n            Box(\n                contentAlignment = Alignment.Center,\n                modifier = Modifier\n                    .padding(all = 8.dp)\n                    .align(Alignment.CenterHorizontally)\n            ) {\n                CircularProgressIndicator()\n            }\n        }\n        (uiState as? ImagenUiState.Error)?.let {\n            Card(\n                modifier = Modifier\n                    .padding(horizontal = 16.dp)\n                    .fillMaxWidth(),\n                shape = MaterialTheme.shapes.large,\n                colors = CardDefaults.cardColors(\n                    containerColor = MaterialTheme.colorScheme.errorContainer\n                )\n            ) {\n                Text(\n                    text = it.message,\n                    color = MaterialTheme.colorScheme.error,\n                    modifier = Modifier.padding(all = 16.dp)\n                )\n            }\n        }\n        LazyHorizontalGrid(\n            rows = GridCells.Fixed(2),\n            modifier = Modifier\n                .padding(16.dp)\n                .height(500.dp)\n        ) {\n            items(generatedImages) { image ->\n                Card(\n                    modifier = Modifier\n                        .padding(8.dp)\n                        .fillMaxWidth(),\n                    shape = MaterialTheme.shapes.large,\n                    colors = CardDefaults.cardColors(\n                        containerColor = MaterialTheme.colorScheme.onSecondaryContainer\n                    )\n                ) {\n                    Image(bitmap = image.asImageBitmap(), \"Generated image\")\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun DropDownMenu(items: List<String>, onClick: (String) -> Unit) {\n\n    val isDropDownExpanded = remember {\n        mutableStateOf(false)\n    }\n\n    val itemPosition = remember {\n        mutableIntStateOf(0)\n    }\n\n\n    Column(\n        horizontalAlignment = Alignment.Start,\n        verticalArrangement = Arrangement.Top,\n        modifier = Modifier.padding(horizontal = 10.dp)\n    ) {\n\n        Box {\n            Row(\n                horizontalArrangement = Arrangement.Start,\n                verticalAlignment = Alignment.Top,\n                modifier = Modifier.clickable {\n                    isDropDownExpanded.value = true\n                }\n            ) {\n                Text(text = items[itemPosition.intValue])\n                Image(\n                    painter = painterResource(id = R.drawable.round_arrow_drop_down_24),\n                    contentDescription = \"Dropdown Icon\"\n                )\n            }\n            DropdownMenu(\n                expanded = isDropDownExpanded.value,\n                onDismissRequest = {\n                    isDropDownExpanded.value = false\n                }) {\n                items.forEachIndexed { index, item ->\n                    DropdownMenuItem(\n                        text = {\n                            Text(text = item)\n                        },\n                        onClick = {\n                            isDropDownExpanded.value = false\n                            itemPosition.intValue = index\n                            onClick(item)\n                        })\n                }\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenUiState.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport android.graphics.Bitmap\n\nsealed interface ImagenUiState {\n    data object Idle : ImagenUiState\n    data object Loading : ImagenUiState\n    data class Success(\n        val images: List<Bitmap> = emptyList(),\n        val attachedImage: Bitmap? = null,\n        val selectedOption: String? = null\n    ) : ImagenUiState\n    data class Error(val message: String) : ImagenUiState\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptScreen.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateViewModel\n\n\n@Composable\nfun ServerPromptScreen(\n    viewModel: ServerPromptTemplateViewModel\n) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n    \n    val isLoading = uiState is ServerPromptUiState.Loading\n    val errorMessage = (uiState as? ServerPromptUiState.Error)?.message\n    val generatedText = (uiState as? ServerPromptUiState.Success)?.generatedText\n\n    ServerPromptContent(\n        initialPrompt = viewModel.initialPrompt,\n        isLoading = isLoading,\n        errorMessage = errorMessage,\n        generatedText = generatedText,\n        allowEmptyPrompt = viewModel.allowEmptyPrompt,\n        onGenerate = { viewModel.generate(it) }\n    )\n}\n\n@Composable\nprivate fun ServerPromptContent(\n    initialPrompt: String,\n    isLoading: Boolean,\n    errorMessage: String?,\n    generatedText: String?,\n    allowEmptyPrompt: Boolean,\n    onGenerate: (String) -> Unit\n) {\n    var textPrompt by rememberSaveable { mutableStateOf(initialPrompt) }\n\n    Column(\n        modifier = Modifier.verticalScroll(rememberScrollState())\n    ) {\n        ElevatedCard(\n            modifier = Modifier\n                .padding(all = 16.dp)\n                .fillMaxWidth(),\n            shape = MaterialTheme.shapes.large\n        ) {\n            OutlinedTextField(\n                value = textPrompt,\n                label = { Text(\"Prompt\") },\n                placeholder = { Text(\"Enter text to generate\") },\n                onValueChange = { textPrompt = it },\n                modifier = Modifier\n                    .padding(16.dp)\n                    .fillMaxWidth()\n            )\n            Row() {\n                TextButton(\n                    onClick = {\n                        if (allowEmptyPrompt || textPrompt.isNotBlank()) {\n                            onGenerate(textPrompt)\n                        }\n                    },\n                    modifier = Modifier.padding(end = 16.dp, bottom = 16.dp)\n                ) {\n                    Text(\"Generate\")\n                }\n            }\n\n        }\n\n        if (isLoading) {\n            Box(\n                contentAlignment = Alignment.Center,\n                modifier = Modifier\n                    .padding(all = 8.dp)\n                    .align(Alignment.CenterHorizontally)\n            ) {\n                CircularProgressIndicator()\n            }\n        }\n        errorMessage?.let {\n            Card(\n                modifier = Modifier\n                    .padding(horizontal = 16.dp)\n                    .fillMaxWidth(),\n                shape = MaterialTheme.shapes.large,\n                colors = CardDefaults.cardColors(\n                    containerColor = MaterialTheme.colorScheme.errorContainer\n                )\n            ) {\n                Text(\n                    text = it,\n                    color = MaterialTheme.colorScheme.error,\n                    modifier = Modifier.padding(all = 16.dp)\n                )\n            }\n        }\n        generatedText?.let {\n            Card(\n                modifier = Modifier\n                    .padding(horizontal = 16.dp)\n                    .fillMaxWidth(),\n                shape = MaterialTheme.shapes.large,\n                colors = CardDefaults.cardColors(\n                    containerColor = MaterialTheme.colorScheme.primaryContainer\n                )\n            ) {\n                Text(\n                    text = it,\n                    color = MaterialTheme.colorScheme.primary,\n                    modifier = Modifier.padding(all = 16.dp)\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptUiState.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nsealed interface ServerPromptUiState {\n    data object Idle : ServerPromptUiState\n    data object Loading : ServerPromptUiState\n    data class Success(val generatedText: String? = null) : ServerPromptUiState\n    data class Error(val message: String) : ServerPromptUiState\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeScreen.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport android.Manifest\nimport androidx.annotation.RequiresPermission\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.CallEnd\nimport androidx.compose.material.icons.filled.Mic\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.IconButtonDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.mutableStateOf\n\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.google.firebase.quickstart.ai.feature.live.BidiViewModel\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\n\n@RequiresPermission(Manifest.permission.RECORD_AUDIO)\n@Composable\nfun StreamRealtimeScreen(bidiView: BidiViewModel) {\n    val isConversationActive = remember { mutableStateOf(false) }\n    val backgroundColor =\n        MaterialTheme.colorScheme.background\n    Surface(\n        modifier = Modifier.fillMaxSize(),\n        color = backgroundColor\n    ) {\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(16.dp),\n            horizontalAlignment = Alignment.CenterHorizontally,\n            verticalArrangement = Arrangement.Center\n        ) {\n            // The content will animate its size when it changes\n            Column(\n                horizontalAlignment = Alignment.CenterHorizontally,\n                modifier = Modifier.animateContentSize()\n            ) {\n                if (isConversationActive.value) {\n                    // Active state UI\n                    Text(\n                        text = \"Conversation Active\",\n                        fontSize = 22.sp,\n                        fontWeight = FontWeight.Bold,\n                        color = MaterialTheme.colorScheme.onSurface\n                    )\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Text(\n                        text = \"Tap the end button to stop\",\n                        fontSize = 18.sp,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                } else {\n                    // Idle state UI\n                    Text(\n                        text = \"Start Conversation\",\n                        fontSize = 22.sp,\n                        fontWeight = FontWeight.Bold,\n                        color = MaterialTheme.colorScheme.onSurface\n                    )\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Text(\n                        text = \"Tap the microphone to begin\",\n                        fontSize = 18.sp,\n                        color = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            }\n\n            Spacer(modifier = Modifier.height(80.dp))\n\n            // The main button with pulsing animation\n            if (isConversationActive.value) {\n                // Button to end the conversation\n                IconButton(\n                    onClick = {\n                        bidiView.endConversation()\n                        isConversationActive.value = false },\n                    modifier = Modifier\n                        .size(90.dp)\n                        .clip(CircleShape),\n                    colors = IconButtonDefaults.iconButtonColors(\n                        containerColor = Color(0xFFE63946), // A nice red color\n                        contentColor = Color.White\n                    )\n                ) {\n                    Icon(\n                        imageVector = Icons.Default.CallEnd,\n                        contentDescription = \"End Conversation\",\n                        modifier = Modifier.size(48.dp)\n                    )\n                }\n            } else {\n                // Button to start the conversation\n                IconButton(\n                    onClick = {\n                        CoroutineScope(Dispatchers.IO).launch {\n                            bidiView.startConversation()\n                        }\n                        isConversationActive.value = true },\n                    modifier = Modifier\n                        .size(90.dp)\n                        .clip(CircleShape),\n                    colors = IconButtonDefaults.iconButtonColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        contentColor = Color.White\n                    )\n                ) {\n                    Icon(\n                        imageVector = Icons.Default.Mic,\n                        contentDescription = \"Start Conversation\",\n                        modifier = Modifier.size(48.dp)\n                    )\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeVideoScreen.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport android.Manifest\nimport android.content.pm.PackageManager\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.RequiresPermission\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.google.firebase.quickstart.ai.feature.live.BidiViewModel\nimport kotlinx.coroutines.launch\n\n\n@RequiresPermission(allOf = [Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA])\n@Composable\nfun StreamRealtimeVideoScreen(bidiView: BidiViewModel) {\n    val backgroundColor = MaterialTheme.colorScheme.background\n\n    val scope = rememberCoroutineScope()\n\n    val context = LocalContext.current\n    var hasPermissions by remember {\n        mutableStateOf(\n            ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) ==\n                PackageManager.PERMISSION_GRANTED &&\n                ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) ==\n                    PackageManager.PERMISSION_GRANTED\n        )\n    }\n\n    val launcher =\n        rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {\n            permissions ->\n            hasPermissions = permissions.values.all { it }\n        }\n\n    LaunchedEffect(Unit) {\n        if (!hasPermissions) {\n            launcher.launch(arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))\n        }\n    }\n\n    DisposableEffect(hasPermissions) {\n        if (hasPermissions) {\n            scope.launch { bidiView.startConversation() }\n        }\n        onDispose { bidiView.endConversation() }\n    }\n\n    Surface(modifier = Modifier.fillMaxSize(), color = backgroundColor) {\n        Column(modifier = Modifier.fillMaxSize()) {\n            if (hasPermissions) {\n                Box(modifier = Modifier.fillMaxSize()) {\n                    CameraView(\n                        modifier = Modifier.fillMaxHeight(0.5f),\n                        onFrameCaptured = { bitmap -> bidiView.sendVideoFrame(bitmap) },\n                    )\n                }\n            } else {\n                Text(\"Camera and audio permissions are required to use this feature.\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgScreen.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport coil3.compose.SubcomposeAsyncImage\nimport coil3.request.ImageRequest\nimport coil3.request.crossfade\nimport coil3.svg.SvgDecoder\nimport com.google.firebase.quickstart.ai.feature.text.SvgViewModel\nimport kotlinx.coroutines.Dispatchers\nimport java.nio.ByteBuffer\n\n@Composable\nfun SvgScreen(\n    svgViewModel: SvgViewModel\n) {\n    var prompt by rememberSaveable { mutableStateOf(\"A kitten\") }\n    val uiState by svgViewModel.uiState.collectAsStateWithLifecycle()\n    \n    val isLoading = uiState is SvgUiState.Loading\n    val errorMessage = (uiState as? SvgUiState.Error)?.message\n    val generatedSvgs = (uiState as? SvgUiState.Success)?.svgs ?: emptyList()\n\n    Column {\n        ElevatedCard(\n            modifier = Modifier\n                .padding(all = 16.dp)\n                .fillMaxWidth(),\n            shape = MaterialTheme.shapes.large\n        ) {\n            OutlinedTextField(\n                value = prompt,\n                label = { Text(\"Generate a SVG of\") },\n                placeholder = { Text(\"Enter text to generate image\") },\n                onValueChange = { prompt = it },\n                modifier = Modifier\n                    .padding(16.dp)\n                    .fillMaxWidth()\n            )\n            TextButton(\n                onClick = {\n                    svgViewModel.generateSVG(prompt)\n                },\n                modifier = Modifier\n                    .padding(horizontal = 16.dp, vertical = 16.dp)\n                    .align(Alignment.End)\n            ) {\n                Text(\"Generate\")\n            }\n        }\n        if (isLoading) {\n            Box(\n                contentAlignment = Alignment.Center,\n                modifier = Modifier\n                    .padding(all = 8.dp)\n                    .align(Alignment.CenterHorizontally)\n            ) {\n                CircularProgressIndicator()\n            }\n        }\n        LazyColumn(\n            modifier = Modifier.fillMaxSize()\n        ) {\n            items(generatedSvgs) { svg ->\n                Card(\n                    modifier = Modifier\n                        .padding(horizontal = 16.dp, vertical = 8.dp)\n                        .fillMaxWidth(),\n                    shape = MaterialTheme.shapes.large,\n                    colors = CardDefaults.cardColors(\n                        containerColor = MaterialTheme.colorScheme.onSecondaryContainer\n                    )\n                ) {\n                    SubcomposeAsyncImage(\n                        model = ImageRequest.Builder(LocalContext.current)\n                            .data(ByteBuffer.wrap(svg.toByteArray()))\n                            .decoderFactory(SvgDecoder.Factory())\n                            .decoderCoroutineContext(Dispatchers.Main)\n                            .crossfade(true)\n                            .build(),\n                        contentDescription = \"Generated SVG\",\n                        modifier = Modifier\n                            .fillMaxWidth()\n                    )\n                }\n            }\n        }\n        errorMessage?.let {\n            Card(\n                modifier = Modifier\n                    .padding(horizontal = 16.dp)\n                    .fillMaxWidth(),\n                shape = MaterialTheme.shapes.large,\n                colors = CardDefaults.cardColors(\n                    containerColor = MaterialTheme.colorScheme.errorContainer\n                )\n            ) {\n                Text(\n                    text = it,\n                    color = MaterialTheme.colorScheme.error,\n                    modifier = Modifier.padding(all = 16.dp)\n                )\n            }\n        }\n    }\n}"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgUiState.kt",
    "content": "package com.google.firebase.quickstart.ai.ui\n\nsealed interface SvgUiState {\n    data object Idle : SvgUiState\n    data object Loading : SvgUiState\n    data class Success(val svgs: List<String> = emptyList()) : SvgUiState\n    data class Error(val message: String) : SvgUiState\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt",
    "content": "package com.google.firebase.quickstart.ai.ui.navigation\n\nimport com.google.firebase.quickstart.ai.feature.live.StreamAudioViewModel\nimport com.google.firebase.quickstart.ai.feature.live.StreamVideoViewModel\nimport com.google.firebase.quickstart.ai.feature.live.StreamRealtimeAudioRoute\nimport com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenGenerationRoute\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenGenerationViewModel\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenInpaintingRoute\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenInpaintingViewModel\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenOutpaintingRoute\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenOutpaintingViewModel\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenStyleTransferRoute\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenStyleTransferViewModel\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenSubjectReferenceRoute\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenSubjectReferenceViewModel\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenTemplateRoute\nimport com.google.firebase.quickstart.ai.feature.media.imagen.ImagenTemplateViewModel\nimport com.google.firebase.quickstart.ai.feature.text.AudioSummarizationRoute\nimport com.google.firebase.quickstart.ai.feature.text.AudioSummarizationViewModel\nimport com.google.firebase.quickstart.ai.feature.text.AudioTranslationRoute\nimport com.google.firebase.quickstart.ai.feature.text.AudioTranslationViewModel\nimport com.google.firebase.quickstart.ai.feature.text.CourseRecommendationsRoute\nimport com.google.firebase.quickstart.ai.feature.text.CourseRecommendationsViewModel\nimport com.google.firebase.quickstart.ai.feature.text.DocumentComparisonRoute\nimport com.google.firebase.quickstart.ai.feature.text.DocumentComparisonViewModel\nimport com.google.firebase.quickstart.ai.feature.text.GoogleSearchGroundingRoute\nimport com.google.firebase.quickstart.ai.feature.text.GoogleSearchGroundingViewModel\nimport com.google.firebase.quickstart.ai.feature.text.ImageBlogCreatorRoute\nimport com.google.firebase.quickstart.ai.feature.text.ImageBlogCreatorViewModel\nimport com.google.firebase.quickstart.ai.feature.text.ImageGenerationRoute\nimport com.google.firebase.quickstart.ai.feature.text.ImageGenerationViewModel\nimport com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateRoute\nimport com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateViewModel\nimport com.google.firebase.quickstart.ai.feature.text.SvgRoute\nimport com.google.firebase.quickstart.ai.feature.text.SvgViewModel\nimport com.google.firebase.quickstart.ai.feature.text.ThinkingChatRoute\nimport com.google.firebase.quickstart.ai.feature.text.ThinkingChatViewModel\nimport com.google.firebase.quickstart.ai.feature.text.TranslationRoute\nimport com.google.firebase.quickstart.ai.feature.text.TranslationViewModel\nimport com.google.firebase.quickstart.ai.feature.text.TravelTipsRoute\nimport com.google.firebase.quickstart.ai.feature.text.TravelTipsViewModel\nimport com.google.firebase.quickstart.ai.feature.text.VideoHashtagGeneratorRoute\nimport com.google.firebase.quickstart.ai.feature.text.VideoHashtagGeneratorViewModel\nimport com.google.firebase.quickstart.ai.feature.text.VideoSummarizationRoute\nimport com.google.firebase.quickstart.ai.feature.text.VideoSummarizationViewModel\nimport com.google.firebase.quickstart.ai.feature.text.WeatherChatRoute\nimport com.google.firebase.quickstart.ai.feature.text.WeatherChatViewModel\n\nval FIREBASE_AI_SAMPLES = listOf(\n    Sample(\n        title = \"Translate text\",\n        description = \"Use Gemini 3.1 Flash-Lite to translate text\",\n        route = TranslationRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = TranslationViewModel::class,\n        categories = listOf(Category.TEXT)\n    ),\n    Sample(\n        title = \"Travel tips\",\n        description = \"The user wants the model to help a new traveler\" +\n                \" with travel tips\",\n        route = TravelTipsRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = TravelTipsViewModel::class,\n        categories = listOf(Category.TEXT),\n    ),\n    Sample(\n        title = \"Chatbot recommendations for courses\",\n        description = \"A chatbot suggests courses for a performing arts program.\",\n        route = CourseRecommendationsRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = CourseRecommendationsViewModel::class,\n        categories = listOf(Category.TEXT),\n    ),\n    Sample(\n        title = \"Audio Summarization\",\n        description = \"Use Gemini 3.1 Flash Lite to summarize an audio file\",\n        route = AudioSummarizationRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = AudioSummarizationViewModel::class,\n        categories = listOf(Category.AUDIO),\n    ),\n    Sample(\n        title = \"Translation from audio (Vertex AI)\",\n        description = \"Translate an audio file stored in Cloud Storage\",\n        route = AudioTranslationRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = AudioTranslationViewModel::class,\n        categories = listOf(Category.AUDIO)\n    ),\n    Sample(\n        title = \"Blog post creator (Vertex AI)\",\n        description = \"Create a blog post from an image file stored in Cloud Storage.\",\n        route = ImageBlogCreatorRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = ImageBlogCreatorViewModel::class,\n        categories = listOf(Category.IMAGE)\n    ),\n    Sample(\n        title = \"Imagen 4 - image generation\",\n        description = \"Generate images using Imagen 4\",\n        route = ImagenGenerationRoute,\n        screenType = ScreenType.IMAGEN,\n        viewModelClass = ImagenGenerationViewModel::class,\n        categories = listOf(Category.IMAGE)\n    ),\n    Sample(\n        title = \"Imagen 3 - Inpainting (Vertex AI)\",\n        description = \"Replace part of an image using Imagen 3\",\n        route = ImagenInpaintingRoute,\n        screenType = ScreenType.IMAGEN,\n        viewModelClass = ImagenInpaintingViewModel::class,\n        categories = listOf(Category.IMAGE)\n    ),\n    Sample(\n        title = \"Imagen 3 - Outpainting (Vertex AI)\",\n        description = \"Expand an image by drawing in more background\",\n        route = ImagenOutpaintingRoute,\n        screenType = ScreenType.IMAGEN,\n        viewModelClass = ImagenOutpaintingViewModel::class,\n        categories = listOf(Category.IMAGE)\n    ),\n    Sample(\n        title = \"Imagen 3 - Subject Reference (Vertex AI)\",\n        description = \"Generate an image using a referenced subject (must be an animal)\",\n        route = ImagenSubjectReferenceRoute,\n        screenType = ScreenType.IMAGEN,\n        viewModelClass = ImagenSubjectReferenceViewModel::class,\n        categories = listOf(Category.IMAGE)\n    ),\n    Sample(\n        title = \"Imagen 3 - Style Transfer (Vertex AI)\",\n        description = \"Change the art style of a cat picture using a reference\",\n        route = ImagenStyleTransferRoute,\n        screenType = ScreenType.IMAGEN,\n        viewModelClass = ImagenStyleTransferViewModel::class,\n        categories = listOf(Category.IMAGE)\n    ),\n    Sample(\n        title = \"Gemini 2.5 Flash Image (aka nanobanana)\",\n        description = \"Generate and/or edit images using Gemini 2.5 Flash Image aka nanobanana\",\n        route = ImageGenerationRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = ImageGenerationViewModel::class,\n        categories = listOf(Category.IMAGE)\n    ),\n    Sample(\n        title = \"Document comparison (Vertex AI)\",\n        description = \"Compare the contents of 2 documents.\" +\n                \" Only supported by the Vertex AI Gemini API because the documents are stored in Cloud Storage\",\n        route = DocumentComparisonRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = DocumentComparisonViewModel::class,\n        categories = listOf(Category.DOCUMENT)\n    ),\n    Sample(\n        title = \"Hashtags for a video (Vertex AI)\",\n        description = \"Generate hashtags for a video ad stored in Cloud Storage\",\n        route = VideoHashtagGeneratorRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = VideoHashtagGeneratorViewModel::class,\n        categories = listOf(Category.VIDEO)\n    ),\n    Sample(\n        title = \"Summarize video\",\n        description = \"Summarize a video and extract important dialogue.\",\n        route = VideoSummarizationRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = VideoSummarizationViewModel::class,\n        categories = listOf(Category.VIDEO)\n    ),\n    Sample(\n        title = \"ForecastTalk\",\n        description = \"Use bidirectional streaming to get information about\" +\n                \" weather conditions for a specific US city on a specific date\",\n        route = StreamRealtimeAudioRoute,\n        screenType = ScreenType.BIDI,\n        viewModelClass = StreamAudioViewModel::class,\n        categories = listOf(Category.LIVE_API, Category.AUDIO, Category.FUNCTION_CALLING)\n    ),\n    Sample(\n        title = \"Gemini Live (Video input)\",\n        description = \"Use bidirectional streaming to chat with Gemini using your\" +\n                \" phone's camera\",\n        route = StreamRealtimeVideoRoute,\n        screenType = ScreenType.BIDI_VIDEO,\n        viewModelClass = StreamVideoViewModel::class,\n        categories = listOf(Category.LIVE_API, Category.VIDEO, Category.FUNCTION_CALLING)\n    ),\n    Sample(\n        title = \"Weather Chat\",\n        description = \"Use function calling to get the weather conditions\" +\n                \" for a specific US city on a specific date.\",\n        route = WeatherChatRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = WeatherChatViewModel::class,\n        categories = listOf(Category.TEXT, Category.FUNCTION_CALLING)\n    ),\n    Sample(\n        title = \"Grounding with Google Search\",\n        description = \"Use Grounding with Google Search to get responses based on up-to-date information from the\" +\n                \" web.\",\n        route = GoogleSearchGroundingRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = GoogleSearchGroundingViewModel::class,\n        categories = listOf(Category.TEXT)\n    ),\n    Sample(\n        title = \"Server Prompt Template - Imagen\",\n        description = \"Generate an image using a server prompt template. Note that you need to setup the template in \" +\n                \"the Firebase console before running this demo.\",\n        route = ImagenTemplateRoute,\n        screenType = ScreenType.IMAGEN,\n        viewModelClass = ImagenTemplateViewModel::class,\n        categories = listOf(Category.IMAGE)\n    ),\n    Sample(\n        title = \"Server Prompt Templates - Gemini\",\n        description = \"Generate an invoice using server prompt templates.  Note that you need to setup the template\" +\n                \" in the Firebase console before running this demo.\",\n        route = ServerPromptTemplateRoute,\n        screenType = ScreenType.SERVER_PROMPT,\n        viewModelClass = ServerPromptTemplateViewModel::class,\n        categories = listOf(Category.TEXT),\n    ),\n    Sample(\n        title = \"Thinking\",\n        description = \"Gemini 2.5 Flash with dynamic thinking\",\n        route = ThinkingChatRoute,\n        screenType = ScreenType.CHAT,\n        viewModelClass = ThinkingChatViewModel::class,\n        categories = listOf(Category.TEXT)\n    ),\n    Sample(\n        title = \"SVG Generator\",\n        description = \"Use Gemini 3 Flash preview to create SVG illustrations\",\n        route = SvgRoute,\n        screenType = ScreenType.SVG,\n        viewModelClass = SvgViewModel::class,\n        categories = listOf(Category.IMAGE, Category.TEXT)\n    )\n)\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/MainMenuScreen.kt",
    "content": "package com.google.firebase.quickstart.ai.ui.navigation\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.foundation.lazy.grid.GridCells\nimport androidx.compose.foundation.lazy.grid.LazyVerticalGrid\nimport androidx.compose.foundation.lazy.grid.items\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Done\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.FilterChip\nimport androidx.compose.material3.FilterChipDefaults\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\n\nval MIN_CARD_SIZE = 180.dp\n\n@Composable\nfun MainMenuScreen(\n    onSampleClicked: (Sample) -> Unit\n) {\n    MenuScreen(\n        filterTitle = \"Filter by use case:\",\n        filters = Category.entries.toList(),\n        samples = FIREBASE_AI_SAMPLES,\n        onSampleClicked = {\n            onSampleClicked(it)\n        }\n    )\n}\n\n@Composable\nfun MenuScreen(\n    filterTitle: String,\n    filters: List<Category>,\n    samples: List<Sample>,\n    onSampleClicked: (sample: Sample) -> Unit = {}\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n            .padding(16.dp)\n    ) {\n        var selectedCategory by rememberSaveable { mutableStateOf(filters.first()) }\n        Text(text = filterTitle, style = MaterialTheme.typography.titleLarge)\n        LazyRow {\n            items(filters) { capability ->\n                FilterChip(\n                    onClick = { selectedCategory = capability },\n                    label = {\n                        Text(capability.label)\n                    },\n                    selected = selectedCategory == capability,\n                    leadingIcon = if (selectedCategory == capability) {\n                        {\n                            Icon(\n                                imageVector = Icons.Filled.Done,\n                                contentDescription = \"Done icon\",\n                                modifier = Modifier.size(FilterChipDefaults.IconSize)\n                            )\n                        }\n                    } else {\n                        null\n                    },\n                    modifier = Modifier.padding(end = 8.dp)\n                )\n            }\n        }\n        Text(\n            text = \"Samples\",\n            style = MaterialTheme.typography.titleLarge,\n            modifier = Modifier.padding(top = 16.dp)\n        )\n        val filteredSamples = samples.filter {\n            it.categories.contains(selectedCategory)\n        }\n        LazyVerticalGrid(\n            columns = GridCells.Adaptive(MIN_CARD_SIZE),\n            modifier = Modifier\n        ) {\n            items(filteredSamples) { sample ->\n                SampleItem(sample.title, sample.description, onItemClicked = {\n                    onSampleClicked(sample)\n                })\n            }\n        }\n    }\n}\n\n@Composable\nfun SampleItem(\n    titleResId: String,\n    descriptionResId: String,\n    onItemClicked: () -> Unit = {}\n) {\n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .heightIn(min = MIN_CARD_SIZE)\n            .padding(4.dp)\n            .clickable {\n                onItemClicked()\n            }\n    ) {\n        Column(\n            modifier = Modifier\n                .padding(all = 16.dp)\n                .fillMaxSize()\n        ) {\n            Text(\n                text = titleResId,\n                style = MaterialTheme.typography.labelLarge\n            )\n            Text(\n                text = descriptionResId,\n                style = MaterialTheme.typography.bodyMedium,\n                modifier = Modifier.padding(top = 8.dp)\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt",
    "content": "package com.google.firebase.quickstart.ai.ui.navigation\n\nimport androidx.lifecycle.ViewModel\nimport kotlin.reflect.KClass\n\nenum class Category(\n    val label: String\n) {\n    TEXT(\"Text\"),\n    IMAGE(\"Image\"),\n    VIDEO(\"Video\"),\n    AUDIO(\"Audio\"),\n    DOCUMENT(\"Document\"),\n    FUNCTION_CALLING(\"Function calling\"),\n    LIVE_API(\"Live API Streaming\")\n}\n\nenum class ScreenType {\n    CHAT,\n    IMAGEN,\n    SVG,\n    SERVER_PROMPT,\n    BIDI,\n    BIDI_VIDEO\n}\n\ndata class Sample(\n    val title: String,\n    val description: String,\n    val route: Any,\n    val screenType: ScreenType,\n    val viewModelClass: KClass<out ViewModel>? = null,\n    val categories: List<Category>,\n)\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/theme/Color.kt",
    "content": "package com.google.firebase.quickstart.ai.ui.theme\n\nimport androidx.compose.ui.graphics.Color\n\nval Purple80 = Color(0xFFD0BCFF)\nval PurpleGrey80 = Color(0xFFCCC2DC)\nval Pink80 = Color(0xFFEFB8C8)\n\nval Purple40 = Color(0xFF6650a4)\nval PurpleGrey40 = Color(0xFF625b71)\nval Pink40 = Color(0xFF7D5260)\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/theme/Theme.kt",
    "content": "package com.google.firebase.quickstart.ai.ui.theme\n\nimport android.os.Build\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.dynamicDarkColorScheme\nimport androidx.compose.material3.dynamicLightColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.platform.LocalContext\n\nprivate val DarkColorScheme = darkColorScheme(\n    primary = Purple80,\n    secondary = PurpleGrey80,\n    tertiary = Pink80\n)\n\nprivate val LightColorScheme = lightColorScheme(\n    primary = Purple40,\n    secondary = PurpleGrey40,\n    tertiary = Pink40\n\n    /* Other default colors to override\n    background = Color(0xFFFFFBFE),\n    surface = Color(0xFFFFFBFE),\n    onPrimary = Color.White,\n    onSecondary = Color.White,\n    onTertiary = Color.White,\n    onBackground = Color(0xFF1C1B1F),\n    onSurface = Color(0xFF1C1B1F),\n     */\n)\n\n@Composable\nfun FirebaseAILogicTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    // Dynamic color is available on Android 12+\n    dynamicColor: Boolean = true,\n    content: @Composable () -> Unit\n) {\n    val colorScheme = when {\n        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {\n            val context = LocalContext.current\n            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)\n        }\n\n        darkTheme -> DarkColorScheme\n        else -> LightColorScheme\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}\n"
  },
  {
    "path": "firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/theme/Type.kt",
    "content": "package com.google.firebase.quickstart.ai.ui.theme\n\nimport androidx.compose.material3.Typography\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.sp\n\n// Set of Material typography styles to start with\nval Typography = Typography(\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp,\n        letterSpacing = 0.5.sp\n    )\n    /* Other default text styles to override\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 22.sp,\n        lineHeight = 28.sp,\n        letterSpacing = 0.sp\n    ),\n    labelSmall = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Medium,\n        fontSize = 11.sp,\n        lineHeight = 16.sp,\n        letterSpacing = 0.5.sp\n    )\n     */\n)\n"
  },
  {
    "path": "firebase-ai/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#FF9100\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "firebase-ai/app/src/main/res/drawable/round_arrow_drop_down_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:viewportHeight=\"24\" android:viewportWidth=\"24\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M8.71,11.71l2.59,2.59c0.39,0.39 1.02,0.39 1.41,0l2.59,-2.59c0.63,-0.63 0.18,-1.71 -0.71,-1.71H9.41c-0.89,0 -1.33,1.08 -0.7,1.71z\"/>\n    \n</vector>\n"
  },
  {
    "path": "firebase-ai/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"192\"\n    android:viewportHeight=\"192\">\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M124,96l-16.36133,16.33333l-6.68267,-6.67333l9.67867,-9.66l-9.67867,-9.66l6.68267,-6.67333zM91.044,105.66l-6.68267,6.67333l-16.36133,-16.33333l16.36133,-16.33333l6.68267,6.67333l-9.67867,9.66zM121.88133,70.12333c4.522,4.52667 6.78533,10.038 6.78533,16.54333c0,-6.50533 2.26333,-12.02133 6.78533,-16.54333c4.52667,-4.52667 10.04733,-6.79 16.548,-6.79c-6.50533,0 -12.02133,-2.26333 -16.548,-6.79c-4.522,-4.52667 -6.78533,-10.038 -6.78533,-16.54333c0,6.50533 -2.26333,12.02133 -6.78533,16.54333c-4.52667,4.52667 -10.04733,6.79 -16.548,6.79c6.50533,0 12.02133,2.26333 16.548,6.79zM96.336,44.676v10.03333l-0.336,-0.19133l-37.33333,21.13533v41.60333l37.33333,21.26133l37.33333,-21.26133v-23.954l9.33333,-5.38533v29.21333c0.04042,3.70523 -1.94884,7.13585 -5.18467,8.94133l-36.29733,20.874c-1.5693,0.93137 -3.3598,1.42452 -5.18467,1.428c-1.82502,-0.00234 -3.61582,-0.49557 -5.18467,-1.428l-36.302,-20.874c-3.23625,-1.80675 -5.2241,-5.23982 -5.18,-8.946v-41.22067c-0.04316,-3.70716 1.94661,-7.14047 5.18467,-8.946l36.29733,-20.86467c1.5693,-0.93137 3.3598,-1.42452 5.18467,-1.428z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>\n"
  },
  {
    "path": "firebase-ai/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "firebase-ai/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "firebase-ai/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "firebase-ai/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase AI Logic</string>\n</resources>"
  },
  {
    "path": "firebase-ai/app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Theme.FirebaseAIServices\" parent=\"Theme.Material3.DayNight.NoActionBar\" />\n\n</resources>"
  },
  {
    "path": "firebase-ai/app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See https://developer.android.com/guide/topics/data/autobackup\n   for details.\n   Note: This file is ignored for devices older than API 31\n   See https://developer.android.com/about/versions/12/backup-restore\n-->\n<full-backup-content>\n    <!--\n   <include domain=\"sharedpref\" path=\".\"/>\n   <exclude domain=\"sharedpref\" path=\"device.xml\"/>\n-->\n</full-backup-content>"
  },
  {
    "path": "firebase-ai/app/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample data extraction rules file; uncomment and customize as necessary.\n   See https://developer.android.com/about/versions/12/backup-restore#xml-changes\n   for details.\n-->\n<data-extraction-rules>\n    <cloud-backup>\n        <!-- TODO: Use <include> and <exclude> to control what is backed up.\n        <include .../>\n        <exclude .../>\n        -->\n    </cloud-backup>\n    <!--\n    <device-transfer>\n        <include .../>\n        <exclude .../>\n    </device-transfer>\n    -->\n</data-extraction-rules>"
  },
  {
    "path": "firebase-ai/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.compose.compiler) apply false\n    alias(libs.plugins.kotlin.serialization) apply false\n    alias(libs.plugins.google.services) apply false\n}\n"
  },
  {
    "path": "firebase-ai/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Aug 19 11:04:48 PDT 2025\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "firebase-ai/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. For more details, visit\n# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true"
  },
  {
    "path": "firebase-ai/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "firebase-ai/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "firebase-ai/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n    dependencyResolutionManagement {\n        versionCatalogs {\n            create(\"libs\") {\n                from(files(\"../gradle/libs.versions.toml\"))\n            }\n        }\n    }\n}\n\nrootProject.name = \"Firebase AI Logic\"\ninclude(\":app\")\n "
  },
  {
    "path": "firestore/.gitignore",
    "content": ".gradle\nlocal.properties\n.idea\nbuild/\n.DS_Store\n*.iml\n*.apk\n*.aar\n*.zip\ngoogle-services.json\n"
  },
  {
    "path": "firestore/CONTRIBUTING.md",
    "content": "# Contributing\n\nWe'd love for you to contribute to our source code and to make the project even better than it is today! Here are the guidelines we'd like you to follow:\n\n - [Code of Conduct](#coc)\n - [Question or Problem?](#question)\n - [Issues and Bugs](#issue)\n - [Submission Guidelines](#submit)\n - [Signing the CLA](#cla)\n\n## <a name=\"coc\"></a> Code of Conduct\n\nAs contributors and maintainers of the project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.\n\nCommunication through any of Firebase's channels (GitHub, StackOverflow, Google+, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.\n\nWe promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the project to do the same.\n\nIf any member of the community violates this code of conduct, the maintainers of the project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate.\n\nIf you are subject to or witness unacceptable behavior, or have any other concerns, please drop us a line at samstern@google.com\n\n## <a name=\"question\"></a> Got a Question or Problem?\n\nIf you have questions about how to use the Firebase Android Quickstarts, please direct these to [StackOverflow][stackoverflow] and use the `firebase` tag. We are also available on GitHub issues.\n\nIf you feel that we're missing an important bit of documentation, feel free to\nfile an issue so we can help. Here's an example to get you started:\n\n```\nWhat are you trying to do or find out more about?\n\nWhere have you looked?\n\nWhere did you expect to find this information?\n```\n\n## <a name=\"issue\"></a> Found an Issue?\nIf you find a bug in the source code or a mistake in the documentation, you can help us by\nsubmitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request\nwith a fix.\n\nSee [below](#submit) for some guidelines.\n\n## <a name=\"submit\"></a> Submission Guidelines\n\n### Submitting an Issue\nBefore you submit your issue search the archive, maybe your question was already answered.\n\nIf your issue appears to be a bug, and hasn't been reported, open a new issue.\nHelp us to maximize the effort we can spend fixing issues and adding new\nfeatures, by not reporting duplicate issues.  Providing the following information will increase the\nchances of your issue being dealt with quickly:\n\n* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps\n* **Motivation for or Use Case** - explain why this is a bug for you\n* **Browsers and Operating System** - is this a problem with all browsers or only IE9?\n* **Reproduce the Error** - provide a live example (using JSBin) or a unambiguous set of steps.\n* **Related Issues** - has a similar issue been reported before?\n* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be\n  causing the problem (line of code or commit)\n\n**If you get help, help others. Good karma rulez!**\n\nHere's a template to get you started:\n\n```\nBrowser:\nBrowser version:\nOperating system:\nOperating system version:\n\nWhat steps will reproduce the problem:\n1.\n2.\n3.\n\nWhat is the expected result?\n\nWhat happens instead of that?\n\nPlease provide any other information below, and attach a screenshot if possible.\n```\n\n## <a name=\"cla\"></a> Signing the CLA\n\nPlease sign our [Contributor License Agreement][google-cla] (CLA) before sending pull requests. For any code\nchanges to be accepted, the CLA must be signed. It's a quick process, we promise!\n\n*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).*\n\n[google-cla]: https://cla.developers.google.com\n[stackoverflow]: http://stackoverflow.com/questions/tagged/firebase\n[global-gitignore]: https://help.github.com/articles/ignoring-files/#create-a-global-gitignore\n"
  },
  {
    "path": "firestore/LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2015 Google Inc\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n   All code in any directories or sub-directories that end with *.html or\n   *.css is licensed under the Creative Commons Attribution International\n   4.0 License, which full text can be found here:\n   https://creativecommons.org/licenses/by/4.0/legalcode.\n\n   As an exception to this license, all html or css that is generated by\n   the software at the direction of the user is copyright the user. The\n   user has full ownership and control over such content, including\n   whether and how they wish to license it.\n"
  },
  {
    "path": "firestore/README.md",
    "content": "# Firestore Quickstart\n\n## Introduction\n\nFriendly Eats is a restaurant recommendation app built on Firestore.\nFor more information about Firestore visit [the docs][firestore-docs].\n\n## Getting Started\n\n  * [Set up your Android app for Firestore][setup-android]\n    * Use the package name `com.google.firebase.example.fireeats`\n  * In the Authentication tab of the Firebase console go to the \n    [Sign-in Method][auth-providers] page and enable 'Email/Password'.\n    * This app uses [FirebaseUI][firebaseui] for authentication.\n  * Run the app on an Android device or emulator.\n    \n### Security Rules\n\nAdd the following security rules to your project in the:\n[rules tab](https://console.firebase.google.com/project/_/database/firestore/rules):\n\n```\nservice cloud.firestore {  \n  match /databases/{database}/documents {\n    // Anyone can read a restaurant, only authorized\n    // users can create or update. Deletes are not allowed.\n  \t match /restaurants/{restaurantId} {\n    \t allow read: if true;\n    \t allow create, update: if request.auth.uid != null;\n    }\n    \n    // Anyone can read a rating. Only the user who made the rating\n    // can delete it. Ratings can never be updated.\n    match /restaurants/{restaurantId}/ratings/{ratingId} {\n    \t allow read: if true;\n      allow create: if request.auth.uid != null;\n    \t allow delete: if request.resource.data.userId == request.auth.uid;\n    \t allow update: if false;\n    }\n  }\n}\n```\n\n### Run the App\n\n  * When you open the app you will be prompted to sign in, choose\n    any email and password.\n  * When you first open the app it will be empty, choose\n    **Add Random Items** from the overflow menu to add some\n    new entries.\n    \n### Result\n\n<img src=\"./docs/home.png\" height=\"534\" width=\"300\"/>\n\n### Indexes\n\nAs you use the app's filter functionality you may see warnings\nin logcat that look like this:\n\n```\ncom.google.firebase.example.fireeats W/Firestore Adapter: onEvent:error\ncom.google.firebase.firestore.FirebaseFirestoreException: FAILED_PRECONDITION: The query requires an index. You can create it here: https://console.firebase.google.com/project/...\n```\n\nThis is because indexes are required for most compound queries in\nFirestore. Clicking on the link from the error message will\nautomatically open the index creation UI in the Firebase console\nwith the correct paramters filled in:\n\n<img src=\"./docs/index.png\" />\n\nThis app also provides an index specification file in `indexes.json`\nwhich specifies all indexes required to run the application. You can\nadd all of these indexes programatically using the [Firebase CLI][firebase-cli].\n\n[firestore-docs]: https://firebase.google.com/docs/firestore/\n[setup-android]: https://firebase.google.com/docs/firestore/client/setup-android\n[auth-providers]: https://console.firebase.google.com/project/_/authentication/providers\n[firebaseui]: https://github.com/firebase/FirebaseUI-Android\n[firebase-cli]: https://firebase.google.com/docs/firestore/query-data/indexing#use_the_firebase_cli\n"
  },
  {
    "path": "firestore/accounts.json",
    "content": "{\n\t\"users\": [{\n\t\t\"localId\": \"1\",\n\t\t\"email\": \"test@mailinator.com\",\n\t\t\"passwordHash\": \"XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=\",\n\t}]\n}\n"
  },
  {
    "path": "firestore/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "firestore/app/build.gradle.kts",
    "content": "\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.services)\n    alias(libs.plugins.navigation.safeargs)\n}\n\nandroid {\n    namespace = \"com.google.firebase.example.fireeats\"\n    testBuildType = \"release\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.example.fireeats\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n        vectorDrawables.useSupportLibrary = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            testProguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"test-proguard-rules.pro\")\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n            signingConfig = signingConfigs.getByName(\"debug\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n\n    lint {\n        disable += \"InvalidPackage\"\n        // TODO(thatfiredev): Remove this once\n        //  https://github.com/bumptech/glide/issues/4940 is fixed\n        disable += \"NotificationPermission\"\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Firestore\n    implementation(\"com.google.firebase:firebase-firestore\")\n\n    // Firebase Authentication\n    implementation(\"com.google.firebase:firebase-auth\")\n\n    // Google Play services\n    implementation(\"com.google.android.gms:play-services-auth:20.7.0\")\n\n    // FirebaseUI (for authentication)\n    implementation(\"com.firebaseui:firebase-ui-auth:9.1.1\")\n\n    // Support Libs\n    implementation(\"androidx.activity:activity-ktx:1.12.1\")\n    implementation(\"androidx.appcompat:appcompat:1.7.1\")\n    implementation(\"androidx.core:core-ktx:1.17.0\")\n    implementation(\"androidx.vectordrawable:vectordrawable-animated:1.2.0\")\n    implementation(\"androidx.cardview:cardview:1.0.0\")\n    implementation(\"androidx.browser:browser:1.5.0\")\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.media:media:1.7.1\")\n    implementation(\"androidx.recyclerview:recyclerview:1.4.0\")\n    implementation(\"androidx.navigation:navigation-fragment-ktx:2.9.6\")\n    implementation(\"androidx.navigation:navigation-ui-ktx:2.9.6\")\n\n    // Android architecture components\n    implementation(\"androidx.lifecycle:lifecycle-runtime-ktx:2.10.0\")\n    implementation(\"androidx.lifecycle:lifecycle-extensions:2.2.0\")\n    annotationProcessor(\"androidx.lifecycle:lifecycle-compiler:2.10.0\")\n\n    // Third-party libraries\n    implementation(\"me.zhanghai.android.materialratingbar:library:1.4.0\")\n    implementation(\"com.github.bumptech.glide:glide:4.12.0\")\n\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-contrib:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test.uiautomator:uiautomator:2.3.0\")\n    androidTestImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"org.hamcrest:hamcrest-library:3.0\")\n    androidTestImplementation(\"com.google.firebase:firebase-auth\")\n}\n"
  },
  {
    "path": "firestore/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /usr/local/google/home/samstern/android-sdk-linux/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n# Keep custom model classes\n-keep class com.google.firebase.example.fireeats.java.model.** { *; }\n-keep class com.google.firebase.example.fireeats.kotlin.model.** { *; }\n\n# https://github.com/firebase/FirebaseUI-Android/issues/1175\n-dontwarn okio.**\n-dontwarn retrofit2.Call\n-dontnote retrofit2.Platform$IOS$MainThreadExecutor\n-keep class android.support.v7.widget.RecyclerView { *; }\n"
  },
  {
    "path": "firestore/app/src/androidTest/AndroidManifest.xml",
    "content": "<manifest xmlns:tools=\"http://schemas.android.com/tools\">\n\n  <uses-sdk tools:overrideLibrary=\"android.support.test.uiautomator.v18\"/>\n\n</manifest>\n"
  },
  {
    "path": "firestore/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity android:name=\".EntryChoiceActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme.EntryChoice\"\n            android:exported=\"true\">\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".java.MainActivity\"\n            android:theme=\"@style/AppTheme.Activity\" />\n\n        <activity\n            android:name=\".kotlin.MainActivity\"\n            android:theme=\"@style/AppTheme.Activity\" />\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.example.fireeats\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firestore quickstart written in Java.\",\n                Intent(this, com.google.firebase.example.fireeats.java.MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firestore quickstart written in Kotlin.\",\n                Intent(this, com.google.firebase.example.fireeats.kotlin.MainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/FilterDialogFragment.java",
    "content": "package com.google.firebase.example.fireeats.java;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Spinner;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.DialogFragment;\n\nimport com.google.firebase.example.fireeats.R;\nimport com.google.firebase.example.fireeats.databinding.DialogFiltersBinding;\nimport com.google.firebase.example.fireeats.java.model.Restaurant;\nimport com.google.firebase.firestore.Query;\n\n/**\n * Dialog Fragment containing filter form.\n */\npublic class FilterDialogFragment extends DialogFragment implements View.OnClickListener {\n\n    public static final String TAG = \"FilterDialog\";\n\n    interface FilterListener {\n\n        void onFilter(Filters filters);\n\n    }\n\n    private DialogFiltersBinding mBinding;\n    private FilterListener mFilterListener;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater,\n                             @Nullable ViewGroup container,\n                             @Nullable Bundle savedInstanceState) {\n        mBinding = DialogFiltersBinding.inflate(inflater, container, false);\n        \n        mBinding.buttonSearch.setOnClickListener(this);\n        mBinding.buttonCancel.setOnClickListener(this);\n\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n\n        if (getParentFragment() instanceof FilterListener) {\n            mFilterListener = (FilterListener) getParentFragment();\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        getDialog().getWindow().setLayout(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.WRAP_CONTENT);\n    }\n\n    public void onSearchClicked() {\n        if (mFilterListener != null) {\n            mFilterListener.onFilter(getFilters());\n        }\n\n        dismiss();\n    }\n\n    public void onCancelClicked() {\n        dismiss();\n    }\n\n    @Nullable\n    private String getSelectedCategory() {\n        String selected = (String) mBinding.spinnerCategory.getSelectedItem();\n        if (getString(R.string.value_any_category).equals(selected)) {\n            return null;\n        } else {\n            return selected;\n        }\n    }\n\n    @Nullable\n    private String getSelectedCity() {\n        String selected = (String) mBinding.spinnerCity.getSelectedItem();\n        if (getString(R.string.value_any_city).equals(selected)) {\n            return null;\n        } else {\n            return selected;\n        }\n    }\n\n    private int getSelectedPrice() {\n        String selected = (String) mBinding.spinnerPrice.getSelectedItem();\n        if (selected.equals(getString(R.string.price_1))) {\n            return 1;\n        } else if (selected.equals(getString(R.string.price_2))) {\n            return 2;\n        } else if (selected.equals(getString(R.string.price_3))) {\n            return 3;\n        } else {\n            return -1;\n        }\n    }\n\n    @Nullable\n    private String getSelectedSortBy() {\n        String selected = (String) mBinding.spinnerSort.getSelectedItem();\n        if (getString(R.string.sort_by_rating).equals(selected)) {\n            return Restaurant.FIELD_AVG_RATING;\n        } if (getString(R.string.sort_by_price).equals(selected)) {\n            return Restaurant.FIELD_PRICE;\n        } if (getString(R.string.sort_by_popularity).equals(selected)) {\n            return Restaurant.FIELD_POPULARITY;\n        }\n\n        return null;\n    }\n\n    @Nullable\n    private Query.Direction getSortDirection() {\n        String selected = (String) mBinding.spinnerSort.getSelectedItem();\n        if (getString(R.string.sort_by_rating).equals(selected)) {\n            return Query.Direction.DESCENDING;\n        } if (getString(R.string.sort_by_price).equals(selected)) {\n            return Query.Direction.ASCENDING;\n        } if (getString(R.string.sort_by_popularity).equals(selected)) {\n            return Query.Direction.DESCENDING;\n        }\n\n        return null;\n    }\n\n    public void resetFilters() {\n        if (mBinding != null) {\n            mBinding.spinnerCategory.setSelection(0);\n            mBinding.spinnerCity.setSelection(0);\n            mBinding.spinnerPrice.setSelection(0);\n            mBinding.spinnerSort.setSelection(0);\n        }\n    }\n\n    public Filters getFilters() {\n        Filters filters = new Filters();\n\n        if (mBinding != null) {\n            filters.setCategory(getSelectedCategory());\n            filters.setCity(getSelectedCity());\n            filters.setPrice(getSelectedPrice());\n            filters.setSortBy(getSelectedSortBy());\n            filters.setSortDirection(getSortDirection());\n        }\n\n        return filters;\n    }\n\n    @Override\n    public void onClick(View v) {\n        //Due to bump in Java version, we can not use view ids in switch\n        //(see: http://tools.android.com/tips/non-constant-fields), so we\n        //need to use if/else:\n\n        int viewId = v.getId();\n        if (viewId == R.id.buttonSearch) {\n            onSearchClicked();\n        } else if (viewId == R.id.buttonCancel) {\n            onCancelClicked();\n        }\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/Filters.java",
    "content": "package com.google.firebase.example.fireeats.java;\n\nimport android.content.Context;\nimport android.text.TextUtils;\n\nimport com.google.firebase.example.fireeats.R;\nimport com.google.firebase.example.fireeats.java.model.Restaurant;\nimport com.google.firebase.example.fireeats.java.util.RestaurantUtil;\nimport com.google.firebase.firestore.Query;\n\n/**\n * Object for passing filters around.\n */\npublic class Filters {\n\n    private String category = null;\n    private String city = null;\n    private int price = -1;\n    private String sortBy = null;\n    private Query.Direction sortDirection = null;\n\n    public Filters() {}\n\n    public static Filters getDefault() {\n        Filters filters = new Filters();\n        filters.setSortBy(Restaurant.FIELD_AVG_RATING);\n        filters.setSortDirection(Query.Direction.DESCENDING);\n\n        return filters;\n    }\n\n    public boolean hasCategory() {\n        return !(TextUtils.isEmpty(category));\n    }\n\n    public boolean hasCity() {\n        return !(TextUtils.isEmpty(city));\n    }\n\n    public boolean hasPrice() {\n        return (price > 0);\n    }\n\n    public boolean hasSortBy() {\n        return !(TextUtils.isEmpty(sortBy));\n    }\n\n    public String getCategory() {\n        return category;\n    }\n\n    public void setCategory(String category) {\n        this.category = category;\n    }\n\n    public String getCity() {\n        return city;\n    }\n\n    public void setCity(String city) {\n        this.city = city;\n    }\n\n    public int getPrice() {\n        return price;\n    }\n\n    public void setPrice(int price) {\n        this.price = price;\n    }\n\n    public String getSortBy() {\n        return sortBy;\n    }\n\n    public void setSortBy(String sortBy) {\n        this.sortBy = sortBy;\n    }\n\n    public Query.Direction getSortDirection() {\n        return sortDirection;\n    }\n\n    public void setSortDirection(Query.Direction sortDirection) {\n        this.sortDirection = sortDirection;\n    }\n\n    public String getSearchDescription(Context context) {\n        StringBuilder desc = new StringBuilder();\n\n        if (category == null && city == null) {\n            desc.append(\"<b>\");\n            desc.append(context.getString(R.string.all_restaurants));\n            desc.append(\"</b>\");\n        }\n\n        if (category != null) {\n            desc.append(\"<b>\");\n            desc.append(category);\n            desc.append(\"</b>\");\n        }\n\n        if (category != null && city != null) {\n            desc.append(\" in \");\n        }\n\n        if (city != null) {\n            desc.append(\"<b>\");\n            desc.append(city);\n            desc.append(\"</b>\");\n        }\n\n        if (price > 0) {\n            desc.append(\" for \");\n            desc.append(\"<b>\");\n            desc.append(RestaurantUtil.getPriceString(price));\n            desc.append(\"</b>\");\n        }\n\n        return desc.toString();\n    }\n\n    public String getOrderDescription(Context context) {\n        if (Restaurant.FIELD_PRICE.equals(sortBy)) {\n            return context.getString(R.string.sorted_by_price);\n        } else if (Restaurant.FIELD_POPULARITY.equals(sortBy)) {\n            return context.getString(R.string.sorted_by_popularity);\n        } else {\n            return context.getString(R.string.sorted_by_rating);\n        }\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java",
    "content": "package com.google.firebase.example.fireeats.java;\n\nimport android.os.Bundle;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.widget.Toolbar;\nimport androidx.navigation.Navigation;\n\nimport com.google.firebase.example.fireeats.R;\n\npublic class  MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        setSupportActionBar(this.<Toolbar>findViewById(R.id.toolbar));\n\n        Navigation.findNavController(this, R.id.nav_host_fragment)\n                .setGraph(R.navigation.nav_graph_java);\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainFragment.java",
    "content": "package com.google.firebase.example.fireeats.java;\n\nimport android.app.Activity;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.ContextMenu;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.core.text.HtmlCompat;\nimport androidx.core.view.MenuHost;\nimport androidx.core.view.MenuProvider;\nimport androidx.fragment.app.Fragment;\nimport androidx.lifecycle.ViewModelProvider;\nimport androidx.navigation.fragment.NavHostFragment;\nimport androidx.recyclerview.widget.LinearLayoutManager;\n\nimport com.firebase.ui.auth.AuthUI;\nimport com.firebase.ui.auth.ErrorCodes;\nimport com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;\nimport com.firebase.ui.auth.IdpResponse;\nimport com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult;\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.material.snackbar.Snackbar;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.example.fireeats.R;\nimport com.google.firebase.example.fireeats.databinding.FragmentMainBinding;\nimport com.google.firebase.example.fireeats.java.adapter.RestaurantAdapter;\nimport com.google.firebase.example.fireeats.java.model.Rating;\nimport com.google.firebase.example.fireeats.java.model.Restaurant;\nimport com.google.firebase.example.fireeats.java.util.RatingUtil;\nimport com.google.firebase.example.fireeats.java.util.RestaurantUtil;\nimport com.google.firebase.example.fireeats.java.viewmodel.MainActivityViewModel;\nimport com.google.firebase.firestore.DocumentReference;\nimport com.google.firebase.firestore.DocumentSnapshot;\nimport com.google.firebase.firestore.FirebaseFirestore;\nimport com.google.firebase.firestore.FirebaseFirestoreException;\nimport com.google.firebase.firestore.Query;\nimport com.google.firebase.firestore.WriteBatch;\n\nimport java.util.Collections;\nimport java.util.List;\n\npublic class MainFragment extends Fragment implements\n        FilterDialogFragment.FilterListener,\n        RestaurantAdapter.OnRestaurantSelectedListener, View.OnClickListener,\n        MenuProvider {\n\n    private static final String TAG = \"MainActivity\";\n\n    private static final int LIMIT = 50;\n\n    private FragmentMainBinding mBinding;\n\n    private FirebaseFirestore mFirestore;\n    private Query mQuery;\n\n    private FilterDialogFragment mFilterDialog;\n    private RestaurantAdapter mAdapter;\n\n    private MainActivityViewModel mViewModel;\n\n    private MenuHost menuHost;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentMainBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mBinding.filterBar.setOnClickListener(this);\n        mBinding.buttonClearFilter.setOnClickListener(this);\n\n        // MenuProvider\n        menuHost = requireActivity();\n        menuHost.addMenuProvider(this);\n\n        // View model\n        mViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);\n\n        // Enable Firestore logging\n        FirebaseFirestore.setLoggingEnabled(true);\n\n        // Firestore\n        mFirestore = FirebaseFirestore.getInstance();\n\n        // Get ${LIMIT} restaurants\n        mQuery = mFirestore.collection(\"restaurants\")\n                .orderBy(\"avgRating\", Query.Direction.DESCENDING)\n                .limit(LIMIT);\n\n        // RecyclerView\n        mAdapter = new RestaurantAdapter(mQuery, this) {\n            @Override\n            protected void onDataChanged() {\n                // Show/hide content if the query returns empty.\n                if (getItemCount() == 0) {\n                    mBinding.recyclerRestaurants.setVisibility(View.GONE);\n                    mBinding.viewEmpty.setVisibility(View.VISIBLE);\n                } else {\n                    mBinding.recyclerRestaurants.setVisibility(View.VISIBLE);\n                    mBinding.viewEmpty.setVisibility(View.GONE);\n                }\n            }\n\n            @Override\n            protected void onError(FirebaseFirestoreException e) {\n                // Show a snackbar on errors\n                Snackbar.make(mBinding.getRoot(),\n                        \"Error: check logs for info.\", Snackbar.LENGTH_LONG).show();\n            }\n        };\n\n        mBinding.recyclerRestaurants.setLayoutManager(new LinearLayoutManager(requireContext()));\n        mBinding.recyclerRestaurants.setAdapter(mAdapter);\n\n        // Filter Dialog\n        mFilterDialog = new FilterDialogFragment();\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n\n        // Start sign in if necessary\n        if (shouldStartSignIn()) {\n            startSignIn();\n            return;\n        }\n\n        // Apply filters\n        onFilter(mViewModel.getFilters());\n\n        // Start listening for Firestore updates\n        if (mAdapter != null) {\n            mAdapter.startListening();\n        }\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        if (mAdapter != null) {\n            mAdapter.stopListening();\n        }\n    }\n\n    @Override\n    public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {\n        menuInflater.inflate(R.menu.menu_main, menu);\n    }\n\n    @Override\n    public boolean onMenuItemSelected(@NonNull MenuItem item) {\n        //Due to bump in Java version, we can not use view ids in switch\n        //(see: http://tools.android.com/tips/non-constant-fields), so we\n        //need to use if/else:\n\n        int itemId = item.getItemId();\n        if (itemId == R.id.menu_add_items) {\n            onAddItemsClicked();\n            return true;\n        } else if (itemId == R.id.menu_sign_out) {\n            AuthUI.getInstance().signOut(requireContext());\n            startSignIn();\n            return true;\n        }\n        return false;\n    }\n\n    private void onSignInResult(FirebaseAuthUIAuthenticationResult result) {\n        IdpResponse response = result.getIdpResponse();\n        mViewModel.setIsSigningIn(false);\n\n        if (result.getResultCode() != Activity.RESULT_OK) {\n            if (response == null) {\n                // User pressed the back button.\n                requireActivity().finish();\n            } else if (response.getError() != null\n                    && response.getError().getErrorCode() == ErrorCodes.NO_NETWORK) {\n                showSignInErrorDialog(R.string.message_no_network);\n            } else {\n                showSignInErrorDialog(R.string.message_unknown);\n            }\n        }\n    }\n\n    public void onFilterClicked() {\n        // Show the dialog containing filter options\n        mFilterDialog.show(getChildFragmentManager(), FilterDialogFragment.TAG);\n    }\n\n    public void onClearFilterClicked() {\n        mFilterDialog.resetFilters();\n\n        onFilter(Filters.getDefault());\n    }\n\n    @Override\n    public void onRestaurantSelected(DocumentSnapshot restaurant) {\n        // Go to the details page for the selected restaurant\n        MainFragmentDirections.ActionMainFragmentToRestaurantDetailFragment action = MainFragmentDirections\n                .actionMainFragmentToRestaurantDetailFragment(restaurant.getId());\n\n        NavHostFragment.findNavController(this)\n                .navigate(action);\n    }\n\n    @Override\n    public void onFilter(Filters filters) {\n        // Construct query basic query\n        Query query = mFirestore.collection(\"restaurants\");\n\n        // Category (equality filter)\n        if (filters.hasCategory()) {\n            query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.getCategory());\n        }\n\n        // City (equality filter)\n        if (filters.hasCity()) {\n            query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.getCity());\n        }\n\n        // Price (equality filter)\n        if (filters.hasPrice()) {\n            query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.getPrice());\n        }\n\n        // Sort by (orderBy with direction)\n        if (filters.hasSortBy()) {\n            query = query.orderBy(filters.getSortBy(), filters.getSortDirection());\n        }\n\n        // Limit items\n        query = query.limit(LIMIT);\n\n        // Update the query\n        mAdapter.setQuery(query);\n\n        // Set header\n        mBinding.textCurrentSearch.setText(HtmlCompat.fromHtml(filters.getSearchDescription(requireContext()),\n                HtmlCompat.FROM_HTML_MODE_LEGACY));\n        mBinding.textCurrentSortBy.setText(filters.getOrderDescription(requireContext()));\n\n        // Save filters\n        mViewModel.setFilters(filters);\n    }\n\n    private boolean shouldStartSignIn() {\n        return (!mViewModel.getIsSigningIn() && FirebaseAuth.getInstance().getCurrentUser() == null);\n    }\n\n    private void startSignIn() {\n        // Sign in with FirebaseUI\n        ActivityResultLauncher<Intent> signinLauncher = requireActivity()\n                .registerForActivityResult(new FirebaseAuthUIActivityResultContract(),\n                        this::onSignInResult\n                );\n\n        Intent intent = AuthUI.getInstance().createSignInIntentBuilder()\n                .setAvailableProviders(Collections.singletonList(\n                        new AuthUI.IdpConfig.EmailBuilder().build()))\n                .setCredentialManagerEnabled(false)\n                .build();\n\n        signinLauncher.launch(intent);\n        mViewModel.setIsSigningIn(true);\n    }\n\n    private void onAddItemsClicked() {\n        // Add a bunch of random restaurants\n        WriteBatch batch = mFirestore.batch();\n        for (int i = 0; i < 10; i++) {\n            DocumentReference restRef = mFirestore.collection(\"restaurants\").document();\n\n            // Create random restaurant / ratings\n            Restaurant randomRestaurant = RestaurantUtil.getRandom(requireContext());\n            List<Rating> randomRatings = RatingUtil.getRandomList(randomRestaurant.getNumRatings());\n            randomRestaurant.setAvgRating(RatingUtil.getAverageRating(randomRatings));\n\n            // Add restaurant\n            batch.set(restRef, randomRestaurant);\n\n            // Add ratings to subcollection\n            for (Rating rating : randomRatings) {\n                batch.set(restRef.collection(\"ratings\").document(), rating);\n            }\n        }\n\n        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {\n            @Override\n            public void onComplete(@NonNull Task<Void> task) {\n                if (task.isSuccessful()) {\n                    Log.d(TAG, \"Write batch succeeded.\");\n                } else {\n                    Log.w(TAG, \"write batch failed.\", task.getException());\n                }\n            }\n        });\n    }\n\n    private void showSignInErrorDialog(@StringRes int message) {\n        AlertDialog dialog = new AlertDialog.Builder(requireContext())\n                .setTitle(R.string.title_sign_in_error)\n                .setMessage(message)\n                .setCancelable(false)\n                .setPositiveButton(R.string.option_retry, new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialogInterface, int i) {\n                      startSignIn();\n                    }\n                })\n                .setNegativeButton(R.string.option_exit, new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialogInterface, int i) {\n                        requireActivity().finish();\n                    }\n                }).create();\n\n        dialog.show();\n    }\n\n    @Override\n    public void onClick(View v) {\n        //Due to bump in Java version, we can not use view ids in switch\n        //(see: http://tools.android.com/tips/non-constant-fields), so we\n        //need to use if/else:\n\n        int viewId = v.getId();\n        if (viewId == R.id.filterBar) {\n            onFilterClicked();\n        } else if (viewId == R.id.buttonClearFilter) {\n            onClearFilterClicked();\n        }\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RatingDialogFragment.java",
    "content": "package com.google.firebase.example.fireeats.java;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.EditText;\n\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.DialogFragment;\n\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.example.fireeats.R;\nimport com.google.firebase.example.fireeats.databinding.DialogRatingBinding;\nimport com.google.firebase.example.fireeats.java.model.Rating;\n\nimport me.zhanghai.android.materialratingbar.MaterialRatingBar;\n\n/**\n * Dialog Fragment containing rating form.\n */\npublic class RatingDialogFragment extends DialogFragment implements View.OnClickListener {\n\n    public static final String TAG = \"RatingDialog\";\n\n    private DialogRatingBinding mBinding;\n    \n    interface RatingListener {\n\n        void onRating(Rating rating);\n\n    }\n\n    private RatingListener mRatingListener;\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater,\n                             @Nullable ViewGroup container,\n                             @Nullable Bundle savedInstanceState) {\n        mBinding = DialogRatingBinding.inflate(inflater, container, false);\n\n        mBinding.restaurantFormButton.setOnClickListener(this);\n        mBinding.restaurantFormCancel.setOnClickListener(this);\n\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mBinding = null;\n    }\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n\n        if (getParentFragment() instanceof RatingListener) {\n            mRatingListener = (RatingListener) getParentFragment();\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        getDialog().getWindow().setLayout(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.WRAP_CONTENT);\n\n    }\n\n    private void onSubmitClicked(View view) {\n        Rating rating = new Rating(\n                FirebaseAuth.getInstance().getCurrentUser(),\n                mBinding.restaurantFormRating.getRating(),\n                mBinding.restaurantFormText.getText().toString());\n\n        if (mRatingListener != null) {\n            mRatingListener.onRating(rating);\n        }\n\n        dismiss();\n    }\n\n    private void onCancelClicked(View view) {\n        dismiss();\n    }\n\n    @Override\n    public void onClick(View v) {\n        //Due to bump in Java version, we can not use view ids in switch\n        //(see: http://tools.android.com/tips/non-constant-fields), so we\n        //need to use if/else:\n\n        int viewId = v.getId();\n        if (viewId == R.id.restaurantFormButton) {\n            onSubmitClicked(v);\n        } else if (viewId == R.id.restaurantFormCancel) {\n            onCancelClicked(v);\n        }\n    }\n\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RestaurantDetailFragment.java",
    "content": "package com.google.firebase.example.fireeats.java;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.inputmethod.InputMethodManager;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.fragment.app.Fragment;\nimport androidx.navigation.fragment.NavHostFragment;\nimport androidx.recyclerview.widget.LinearLayoutManager;\n\nimport com.bumptech.glide.Glide;\nimport com.google.android.gms.tasks.OnFailureListener;\nimport com.google.android.gms.tasks.OnSuccessListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.material.snackbar.Snackbar;\nimport com.google.firebase.example.fireeats.R;\nimport com.google.firebase.example.fireeats.databinding.FragmentRestaurantDetailBinding;\nimport com.google.firebase.example.fireeats.java.adapter.RatingAdapter;\nimport com.google.firebase.example.fireeats.java.model.Rating;\nimport com.google.firebase.example.fireeats.java.model.Restaurant;\nimport com.google.firebase.example.fireeats.java.util.RestaurantUtil;\nimport com.google.firebase.firestore.DocumentReference;\nimport com.google.firebase.firestore.DocumentSnapshot;\nimport com.google.firebase.firestore.EventListener;\nimport com.google.firebase.firestore.FirebaseFirestore;\nimport com.google.firebase.firestore.FirebaseFirestoreException;\nimport com.google.firebase.firestore.ListenerRegistration;\nimport com.google.firebase.firestore.Query;\nimport com.google.firebase.firestore.Transaction;\n\npublic class RestaurantDetailFragment extends Fragment\n        implements EventListener<DocumentSnapshot>, RatingDialogFragment.RatingListener, View.OnClickListener {\n\n    private static final String TAG = \"RestaurantDetail\";\n\n    private FragmentRestaurantDetailBinding mBinding;\n    \n    private RatingDialogFragment mRatingDialog;\n\n    private FirebaseFirestore mFirestore;\n    private DocumentReference mRestaurantRef;\n    private ListenerRegistration mRestaurantRegistration;\n\n    private RatingAdapter mRatingAdapter;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mBinding = FragmentRestaurantDetailBinding.inflate(inflater, container, false);\n        return mBinding.getRoot();\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        mBinding.restaurantButtonBack.setOnClickListener(this);\n        mBinding.fabShowRatingDialog.setOnClickListener(this);\n\n        String restaurantId = RestaurantDetailFragmentArgs.fromBundle(getArguments()).getKeyRestaurantId();\n\n        // Initialize Firestore\n        mFirestore = FirebaseFirestore.getInstance();\n\n        // Get reference to the restaurant\n        mRestaurantRef = mFirestore.collection(\"restaurants\").document(restaurantId);\n\n        // Get ratings\n        Query ratingsQuery = mRestaurantRef\n                .collection(\"ratings\")\n                .orderBy(\"timestamp\", Query.Direction.DESCENDING)\n                .limit(50);\n\n        // RecyclerView\n        mRatingAdapter = new RatingAdapter(ratingsQuery) {\n            @Override\n            protected void onDataChanged() {\n                if (getItemCount() == 0) {\n                    mBinding.recyclerRatings.setVisibility(View.GONE);\n                    mBinding.viewEmptyRatings.setVisibility(View.VISIBLE);\n                } else {\n                    mBinding.recyclerRatings.setVisibility(View.VISIBLE);\n                    mBinding.viewEmptyRatings.setVisibility(View.GONE);\n                }\n            }\n        };\n        mBinding.recyclerRatings.setLayoutManager(new LinearLayoutManager(requireContext()));\n        mBinding.recyclerRatings.setAdapter(mRatingAdapter);\n\n        mRatingDialog = new RatingDialogFragment();\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n\n        mRatingAdapter.startListening();\n        mRestaurantRegistration = mRestaurantRef.addSnapshotListener(this);\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n\n        mRatingAdapter.stopListening();\n\n        if (mRestaurantRegistration != null) {\n            mRestaurantRegistration.remove();\n            mRestaurantRegistration = null;\n        }\n    }\n\n    /**\n     * Listener for the Restaurant document ({@link #mRestaurantRef}).\n     */\n    @Override\n    public void onEvent(DocumentSnapshot snapshot, FirebaseFirestoreException e) {\n        if (e != null) {\n            Log.w(TAG, \"restaurant:onEvent\", e);\n            return;\n        }\n\n        onRestaurantLoaded(snapshot.toObject(Restaurant.class));\n    }\n\n    private void onRestaurantLoaded(Restaurant restaurant) {\n        mBinding.restaurantName.setText(restaurant.getName());\n        mBinding.restaurantRating.setRating((float) restaurant.getAvgRating());\n        mBinding.restaurantNumRatings.setText(getString(R.string.fmt_num_ratings, restaurant.getNumRatings()));\n        mBinding.restaurantCity.setText(restaurant.getCity());\n        mBinding.restaurantCategory.setText(restaurant.getCategory());\n        mBinding.restaurantPrice.setText(RestaurantUtil.getPriceString(restaurant));\n\n        // Background image\n        Glide.with(mBinding.restaurantImage.getContext())\n                .load(restaurant.getPhoto())\n                .into(mBinding.restaurantImage);\n    }\n\n    public void onBackArrowClicked(View view) {\n        NavHostFragment.findNavController(this).popBackStack();\n    }\n\n    public void onAddRatingClicked(View view) {\n        mRatingDialog.show(getChildFragmentManager(), RatingDialogFragment.TAG);\n    }\n\n    @Override\n    public void onRating(Rating rating) {\n        // In a transaction, add the new rating and update the aggregate totals\n        addRating(mRestaurantRef, rating)\n                .addOnSuccessListener(requireActivity(), new OnSuccessListener<Void>() {\n                    @Override\n                    public void onSuccess(Void aVoid) {\n                        Log.d(TAG, \"Rating added\");\n\n                        // Hide keyboard and scroll to top\n                        hideKeyboard();\n                        mBinding.recyclerRatings.smoothScrollToPosition(0);\n                    }\n                })\n                .addOnFailureListener(requireActivity(), new OnFailureListener() {\n                    @Override\n                    public void onFailure(@NonNull Exception e) {\n                        Log.w(TAG, \"Add rating failed\", e);\n\n                        // Show failure message and hide keyboard\n                        hideKeyboard();\n                        Snackbar.make(mBinding.getRoot(), \"Failed to add rating\",\n                                Snackbar.LENGTH_SHORT).show();\n                    }\n                });\n    }\n\n    private Task<Void> addRating(final DocumentReference restaurantRef, final Rating rating) {\n        // Create reference for new rating, for use inside the transaction\n        final DocumentReference ratingRef = restaurantRef.collection(\"ratings\").document();\n\n        // In a transaction, add the new rating and update the aggregate totals\n        return mFirestore.runTransaction(new Transaction.Function<Void>() {\n            @Override\n            public Void apply(Transaction transaction) throws FirebaseFirestoreException {\n                Restaurant restaurant = transaction.get(restaurantRef).toObject(Restaurant.class);\n\n                // Compute new number of ratings\n                int newNumRatings = restaurant.getNumRatings() + 1;\n\n                // Compute new average rating\n                double oldRatingTotal = restaurant.getAvgRating() * restaurant.getNumRatings();\n                double newAvgRating = (oldRatingTotal + rating.getRating()) / newNumRatings;\n\n                // Set new restaurant info\n                restaurant.setNumRatings(newNumRatings);\n                restaurant.setAvgRating(newAvgRating);\n\n                // Commit to Firestore\n                transaction.set(restaurantRef, restaurant);\n                transaction.set(ratingRef, rating);\n\n                return null;\n            }\n        });\n    }\n\n    private void hideKeyboard() {\n        View view = requireActivity().getCurrentFocus();\n        if (view != null) {\n            ((InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE))\n                    .hideSoftInputFromWindow(view.getWindowToken(), 0);\n        }\n    }\n\n    @Override\n    public void onClick(View v) {\n        //Due to bump in Java version, we can not use view ids in switch\n        //(see: http://tools.android.com/tips/non-constant-fields), so we\n        //need to use if/else:\n\n        int viewId = v.getId();\n        if (viewId == R.id.restaurantButtonBack) {\n            onBackArrowClicked(v);\n        } else if (viewId == R.id.fabShowRatingDialog) {\n            onAddRatingClicked(v);\n        }\n    }\n\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/adapter/FirestoreAdapter.java",
    "content": "package com.google.firebase.example.fireeats.java.adapter;\n\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.Log;\n\nimport com.google.firebase.firestore.DocumentChange;\nimport com.google.firebase.firestore.DocumentSnapshot;\nimport com.google.firebase.firestore.EventListener;\nimport com.google.firebase.firestore.FirebaseFirestoreException;\nimport com.google.firebase.firestore.ListenerRegistration;\nimport com.google.firebase.firestore.Query;\nimport com.google.firebase.firestore.QuerySnapshot;\n\nimport java.util.ArrayList;\n\n/**\n * RecyclerView adapter for displaying the results of a Firestore {@link Query}.\n *\n * Note that this class forgoes some efficiency to gain simplicity. For example, the result of\n * {@link DocumentSnapshot#toObject(Class)} is not cached so the same object may be deserialized\n * many times as the user scrolls.\n */\npublic abstract class FirestoreAdapter<VH extends RecyclerView.ViewHolder>\n        extends RecyclerView.Adapter<VH>\n        implements EventListener<QuerySnapshot> {\n\n    private static final String TAG = \"FirestoreAdapter\";\n\n    private Query mQuery;\n    private ListenerRegistration mRegistration;\n\n    private ArrayList<DocumentSnapshot> mSnapshots = new ArrayList<>();\n\n    public FirestoreAdapter(Query query) {\n        mQuery = query;\n    }\n\n    @Override\n    public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {\n        if (e != null) {\n            Log.w(TAG, \"onEvent:error\", e);\n            onError(e);\n            return;\n        }\n\n        // Dispatch the event\n        Log.d(TAG, \"onEvent:numChanges:\" + documentSnapshots.getDocumentChanges().size());\n        for (DocumentChange change : documentSnapshots.getDocumentChanges()) {\n            switch (change.getType()) {\n                case ADDED:\n                    onDocumentAdded(change);\n                    break;\n                case MODIFIED:\n                    onDocumentModified(change);\n                    break;\n                case REMOVED:\n                    onDocumentRemoved(change);\n                    break;\n            }\n        }\n\n        onDataChanged();\n    }\n\n    public void startListening() {\n        if (mQuery != null && mRegistration == null) {\n            mRegistration = mQuery.addSnapshotListener(this);\n        }\n    }\n\n    public void stopListening() {\n        if (mRegistration != null) {\n            mRegistration.remove();\n            mRegistration = null;\n        }\n\n        mSnapshots.clear();\n        notifyDataSetChanged();\n    }\n\n    public void setQuery(Query query) {\n        // Stop listening\n        stopListening();\n\n        // Clear existing data\n        mSnapshots.clear();\n        notifyDataSetChanged();\n\n        // Listen to new query\n        mQuery = query;\n        startListening();\n    }\n\n    @Override\n    public int getItemCount() {\n        return mSnapshots.size();\n    }\n\n    protected DocumentSnapshot getSnapshot(int index) {\n        return mSnapshots.get(index);\n    }\n\n    protected void onDocumentAdded(DocumentChange change) {\n        mSnapshots.add(change.getNewIndex(), change.getDocument());\n        notifyItemInserted(change.getNewIndex());\n    }\n\n    protected void onDocumentModified(DocumentChange change) {\n        if (change.getOldIndex() == change.getNewIndex()) {\n            // Item changed but remained in same position\n            mSnapshots.set(change.getOldIndex(), change.getDocument());\n            notifyItemChanged(change.getOldIndex());\n        } else {\n            // Item changed and changed position\n            mSnapshots.remove(change.getOldIndex());\n            mSnapshots.add(change.getNewIndex(), change.getDocument());\n            notifyItemMoved(change.getOldIndex(), change.getNewIndex());\n        }\n    }\n\n    protected void onDocumentRemoved(DocumentChange change) {\n        mSnapshots.remove(change.getOldIndex());\n        notifyItemRemoved(change.getOldIndex());\n    }\n\n    protected void onError(FirebaseFirestoreException e) {\n        Log.w(TAG, \"onError\", e);\n    };\n\n    protected void onDataChanged() {}\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/adapter/RatingAdapter.java",
    "content": "package com.google.firebase.example.fireeats.java.adapter;\n\nimport android.view.LayoutInflater;\nimport android.view.ViewGroup;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.google.firebase.example.fireeats.databinding.ItemRatingBinding;\nimport com.google.firebase.example.fireeats.java.model.Rating;\nimport com.google.firebase.firestore.Query;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Locale;\n\n/**\n * RecyclerView adapter for a list of {@link Rating}.\n */\npublic class RatingAdapter extends FirestoreAdapter<RatingAdapter.ViewHolder> {\n\n    public RatingAdapter(Query query) {\n        super(query);\n    }\n\n    @Override\n    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        return new ViewHolder(ItemRatingBinding.inflate(\n                LayoutInflater.from(parent.getContext()), parent, false));\n    }\n\n    @Override\n    public void onBindViewHolder(ViewHolder holder, int position) {\n        holder.bind(getSnapshot(position).toObject(Rating.class));\n    }\n\n    static class ViewHolder extends RecyclerView.ViewHolder {\n\n        private static final SimpleDateFormat FORMAT  = new SimpleDateFormat(\n                \"MM/dd/yyyy\", Locale.US);\n\n        private ItemRatingBinding binding;\n\n        public ViewHolder(ItemRatingBinding binding) {\n            super(binding.getRoot());\n            this.binding = binding;\n        }\n\n        public void bind(Rating rating) {\n            binding.ratingItemName.setText(rating.getUserName());\n            binding.ratingItemRating.setRating((float) rating.getRating());\n            binding.ratingItemText.setText(rating.getText());\n\n            if (rating.getTimestamp() != null) {\n                binding.ratingItemDate.setText(FORMAT.format(rating.getTimestamp()));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/adapter/RestaurantAdapter.java",
    "content": "package com.google.firebase.example.fireeats.java.adapter;\n\nimport android.content.res.Resources;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.bumptech.glide.Glide;\nimport com.google.firebase.example.fireeats.R;\nimport com.google.firebase.example.fireeats.databinding.ItemRestaurantBinding;\nimport com.google.firebase.example.fireeats.java.model.Restaurant;\nimport com.google.firebase.example.fireeats.java.util.RestaurantUtil;\nimport com.google.firebase.firestore.DocumentSnapshot;\nimport com.google.firebase.firestore.Query;\n\n/**\n * RecyclerView adapter for a list of Restaurants.\n */\npublic class RestaurantAdapter extends FirestoreAdapter<RestaurantAdapter.ViewHolder> {\n\n    public interface OnRestaurantSelectedListener {\n\n        void onRestaurantSelected(DocumentSnapshot restaurant);\n\n    }\n\n    private OnRestaurantSelectedListener mListener;\n\n    public RestaurantAdapter(Query query, OnRestaurantSelectedListener listener) {\n        super(query);\n        mListener = listener;\n    }\n\n    @Override\n    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        return new ViewHolder(ItemRestaurantBinding.inflate(\n                LayoutInflater.from(parent.getContext()), parent, false));\n    }\n\n    @Override\n    public void onBindViewHolder(ViewHolder holder, int position) {\n        holder.bind(getSnapshot(position), mListener);\n    }\n\n    static class ViewHolder extends RecyclerView.ViewHolder {\n\n        private ItemRestaurantBinding binding;\n\n        public ViewHolder(ItemRestaurantBinding binding) {\n            super(binding.getRoot());\n            this.binding = binding;\n        }\n\n        public ViewHolder(View itemView) {\n            super(itemView);\n        }\n\n        public void bind(final DocumentSnapshot snapshot,\n                         final OnRestaurantSelectedListener listener) {\n\n            Restaurant restaurant = snapshot.toObject(Restaurant.class);\n            Resources resources = itemView.getResources();\n\n            // Load image\n            Glide.with(binding.restaurantItemImage.getContext())\n                    .load(restaurant.getPhoto())\n                    .into(binding.restaurantItemImage);\n\n            binding.restaurantItemName.setText(restaurant.getName());\n            binding.restaurantItemRating.setRating((float) restaurant.getAvgRating());\n            binding.restaurantItemCity.setText(restaurant.getCity());\n            binding.restaurantItemCategory.setText(restaurant.getCategory());\n            binding.restaurantItemNumRatings.setText(resources.getString(R.string.fmt_num_ratings,\n                    restaurant.getNumRatings()));\n            binding.restaurantItemPrice.setText(RestaurantUtil.getPriceString(restaurant));\n\n            // Click listener\n            itemView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    if (listener != null) {\n                        listener.onRestaurantSelected(snapshot);\n                    }\n                }\n            });\n        }\n\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/model/Rating.java",
    "content": "package com.google.firebase.example.fireeats.java.model;\n\nimport android.text.TextUtils;\n\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.firestore.ServerTimestamp;\n\nimport java.util.Date;\n\n/**\n * Model POJO for a rating.\n */\npublic class Rating {\n\n    private String userId;\n    private String userName;\n    private double rating;\n    private String text;\n    private @ServerTimestamp Date timestamp;\n\n    public Rating() {}\n\n    public Rating(FirebaseUser user, double rating, String text) {\n        this.userId = user.getUid();\n        this.userName = user.getDisplayName();\n        if (TextUtils.isEmpty(this.userName)) {\n            this.userName = user.getEmail();\n        }\n\n        this.rating = rating;\n        this.text = text;\n    }\n\n    public String getUserId() {\n        return userId;\n    }\n\n    public void setUserId(String userId) {\n        this.userId = userId;\n    }\n\n    public String getUserName() {\n        return userName;\n    }\n\n    public void setUserName(String userName) {\n        this.userName = userName;\n    }\n\n    public double getRating() {\n        return rating;\n    }\n\n    public void setRating(double rating) {\n        this.rating = rating;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public void setText(String text) {\n        this.text = text;\n    }\n\n    public Date getTimestamp() {\n        return timestamp;\n    }\n\n    public void setTimestamp(Date timestamp) {\n        this.timestamp = timestamp;\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/model/Restaurant.java",
    "content": "package com.google.firebase.example.fireeats.java.model;\n\nimport com.google.firebase.firestore.IgnoreExtraProperties;\n\n/**\n * Restaurant POJO.\n */\n@IgnoreExtraProperties\npublic class Restaurant {\n\n    public static final String FIELD_CITY = \"city\";\n    public static final String FIELD_CATEGORY = \"category\";\n    public static final String FIELD_PRICE = \"price\";\n    public static final String FIELD_POPULARITY = \"numRatings\";\n    public static final String FIELD_AVG_RATING = \"avgRating\";\n\n    private String name;\n    private String city;\n    private String category;\n    private String photo;\n    private int price;\n    private int numRatings;\n    private double avgRating;\n\n    public Restaurant() {}\n\n    public Restaurant(String name, String city, String category, String photo,\n                      int price, int numRatings, double avgRating) {\n        this.name = name;\n        this.city = city;\n        this.category = category;\n        this.photo = photo;\n        this.price = price;\n        this.numRatings = numRatings;\n        this.avgRating = avgRating;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getCity() {\n        return city;\n    }\n\n    public void setCity(String city) {\n        this.city = city;\n    }\n\n    public String getCategory() {\n        return category;\n    }\n\n    public void setCategory(String category) {\n        this.category = category;\n    }\n\n    public String getPhoto() {\n        return photo;\n    }\n\n    public void setPhoto(String photo) {\n        this.photo = photo;\n    }\n\n    public int getPrice() {\n        return price;\n    }\n\n    public void setPrice(int price) {\n        this.price = price;\n    }\n\n    public int getNumRatings() {\n        return numRatings;\n    }\n\n    public void setNumRatings(int numRatings) {\n        this.numRatings = numRatings;\n    }\n\n    public double getAvgRating() {\n        return avgRating;\n    }\n\n    public void setAvgRating(double avgRating) {\n        this.avgRating = avgRating;\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/util/RatingUtil.java",
    "content": "package com.google.firebase.example.fireeats.java.util;\n\nimport com.google.firebase.example.fireeats.java.model.Rating;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.UUID;\n\n/**\n * Utilities for Ratings.\n */\npublic class RatingUtil {\n\n    public static final String[] REVIEW_CONTENTS = {\n            // 0 - 1 stars\n            \"This was awful! Totally inedible.\",\n\n            // 1 - 2 stars\n            \"This was pretty bad, would not go back.\",\n\n            // 2 - 3 stars\n            \"I was fed, so that's something.\",\n\n            // 3 - 4 stars\n            \"This was a nice meal, I'd go back.\",\n\n            // 4 - 5 stars\n            \"This was fantastic!  Best ever!\"\n    };\n\n    /**\n     * Get a list of random Rating POJOs.\n     */\n    public static List<Rating> getRandomList(int length) {\n        List<Rating> result = new ArrayList<>();\n\n        for (int i = 0; i < length; i++) {\n            result.add(getRandom());\n        }\n\n        return result;\n    }\n\n    /**\n     * Get the average rating of a List.\n     */\n    public static double getAverageRating(List<Rating> ratings) {\n        double sum = 0.0;\n\n        for (Rating rating : ratings) {\n            sum += rating.getRating();\n        }\n\n        return sum / ratings.size();\n    }\n\n    /**\n     * Create a random Rating POJO.\n     */\n    public static Rating getRandom() {\n        Rating rating = new Rating();\n\n        Random random = new Random();\n\n        double score = random.nextDouble() * 5.0;\n        String text = REVIEW_CONTENTS[(int) Math.floor(score)];\n\n        rating.setUserId(UUID.randomUUID().toString());\n        rating.setUserName(\"Random User\");\n        rating.setRating(score);\n        rating.setText(text);\n\n        return rating;\n    }\n\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/util/RestaurantUtil.java",
    "content": "package com.google.firebase.example.fireeats.java.util;\n\nimport android.content.Context;\n\nimport com.google.firebase.example.fireeats.R;\nimport com.google.firebase.example.fireeats.java.model.Restaurant;\n\nimport java.util.Arrays;\nimport java.util.Locale;\nimport java.util.Random;\n\n/**\n * Utilities for Restaurants.\n */\npublic class RestaurantUtil {\n\n    private static final String TAG = \"RestaurantUtil\";\n\n    private static final String RESTAURANT_URL_FMT = \"https://storage.googleapis.com/firestorequickstarts.appspot.com/food_%d.png\";\n    private static final int MAX_IMAGE_NUM = 22;\n\n    private static final String[] NAME_FIRST_WORDS = {\n            \"Foo\",\n            \"Bar\",\n            \"Baz\",\n            \"Qux\",\n            \"Fire\",\n            \"Sam's\",\n            \"World Famous\",\n            \"Google\",\n            \"The Best\",\n    };\n\n    private static final String[] NAME_SECOND_WORDS = {\n            \"Restaurant\",\n            \"Cafe\",\n            \"Spot\",\n            \"Eatin' Place\",\n            \"Eatery\",\n            \"Drive Thru\",\n            \"Diner\",\n    };\n\n    /**\n     * Create a random Restaurant POJO.\n     */\n    public static Restaurant getRandom(Context context) {\n        Restaurant restaurant = new Restaurant();\n        Random random = new Random();\n\n        // Cities (first elemnt is 'Any')\n        String[] cities = context.getResources().getStringArray(R.array.cities);\n        cities = Arrays.copyOfRange(cities, 1, cities.length);\n\n        // Categories (first element is 'Any')\n        String[] categories = context.getResources().getStringArray(R.array.categories);\n        categories = Arrays.copyOfRange(categories, 1, categories.length);\n\n        int[] prices = new int[]{1, 2, 3};\n\n        restaurant.setName(getRandomName(random));\n        restaurant.setCity(getRandomString(cities, random));\n        restaurant.setCategory(getRandomString(categories, random));\n        restaurant.setPhoto(getRandomImageUrl(random));\n        restaurant.setPrice(getRandomInt(prices, random));\n        restaurant.setNumRatings(random.nextInt(20));\n\n        // Note: average rating intentionally not set\n\n        return restaurant;\n    }\n\n\n    /**\n     * Get a random image.\n     */\n    private static String getRandomImageUrl(Random random) {\n        // Integer between 1 and MAX_IMAGE_NUM (inclusive)\n        int id = random.nextInt(MAX_IMAGE_NUM) + 1;\n\n        return String.format(Locale.getDefault(), RESTAURANT_URL_FMT, id);\n    }\n\n    /**\n     * Get price represented as dollar signs.\n     */\n    public static String getPriceString(Restaurant restaurant) {\n        return getPriceString(restaurant.getPrice());\n    }\n\n    /**\n     * Get price represented as dollar signs.\n     */\n    public static String getPriceString(int priceInt) {\n        switch (priceInt) {\n            case 1:\n                return \"$\";\n            case 2:\n                return \"$$\";\n            case 3:\n            default:\n                return \"$$$\";\n        }\n    }\n\n    private static String getRandomName(Random random) {\n        return getRandomString(NAME_FIRST_WORDS, random) + \" \"\n                + getRandomString(NAME_SECOND_WORDS, random);\n    }\n\n    private static String getRandomString(String[] array, Random random) {\n        int ind = random.nextInt(array.length);\n        return array[ind];\n    }\n\n    private static int getRandomInt(int[] array, Random random) {\n        int ind = random.nextInt(array.length);\n        return array[ind];\n    }\n\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/java/viewmodel/MainActivityViewModel.java",
    "content": "package com.google.firebase.example.fireeats.java.viewmodel;\n\nimport androidx.lifecycle.ViewModel;\n\nimport com.google.firebase.example.fireeats.java.Filters;\n\n/**\n * ViewModel for {@link com.google.firebase.example.fireeats.MainActivity}.\n */\n\npublic class MainActivityViewModel extends ViewModel {\n\n    private boolean mIsSigningIn;\n    private Filters mFilters;\n\n    public MainActivityViewModel() {\n        mIsSigningIn = false;\n        mFilters = Filters.getDefault();\n    }\n\n    public boolean getIsSigningIn() {\n        return mIsSigningIn;\n    }\n\n    public void setIsSigningIn(boolean mIsSigningIn) {\n        this.mIsSigningIn = mIsSigningIn;\n    }\n\n    public Filters getFilters() {\n        return mFilters;\n    }\n\n    public void setFilters(Filters mFilters) {\n        this.mFilters = mFilters;\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/FilterDialogFragment.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.DialogFragment\nimport com.google.firebase.example.fireeats.R\nimport com.google.firebase.example.fireeats.databinding.DialogFiltersBinding\nimport com.google.firebase.example.fireeats.kotlin.model.Restaurant\nimport com.google.firebase.firestore.Query\n\n/**\n * Dialog Fragment containing filter form.\n */\nclass FilterDialogFragment : DialogFragment() {\n\n    private var _binding: DialogFiltersBinding? = null\n    private val binding get() = _binding!!\n    private var filterListener: FilterListener? = null\n\n    private val selectedCategory: String?\n        get() {\n            val selected = binding.spinnerCategory.selectedItem as String\n            return if (getString(R.string.value_any_category) == selected) {\n                null\n            } else {\n                selected\n            }\n        }\n\n    private val selectedCity: String?\n        get() {\n            val selected = binding.spinnerCity.selectedItem as String\n            return if (getString(R.string.value_any_city) == selected) {\n                null\n            } else {\n                selected\n            }\n        }\n\n    private val selectedPrice: Int\n        get() {\n            val selected = binding.spinnerPrice.selectedItem as String\n            return when (selected) {\n                getString(R.string.price_1) -> 1\n                getString(R.string.price_2) -> 2\n                getString(R.string.price_3) -> 3\n                else -> -1\n            }\n        }\n\n    private val selectedSortBy: String?\n        get() {\n            val selected = binding.spinnerSort.selectedItem as String\n            if (getString(R.string.sort_by_rating) == selected) {\n                return Restaurant.FIELD_AVG_RATING\n            }\n            if (getString(R.string.sort_by_price) == selected) {\n                return Restaurant.FIELD_PRICE\n            }\n            return if (getString(R.string.sort_by_popularity) == selected) {\n                Restaurant.FIELD_POPULARITY\n            } else {\n                null\n            }\n        }\n\n    private val sortDirection: Query.Direction\n        get() {\n            val selected = binding.spinnerSort.selectedItem as String\n            if (getString(R.string.sort_by_rating) == selected) {\n                return Query.Direction.DESCENDING\n            }\n            if (getString(R.string.sort_by_price) == selected) {\n                return Query.Direction.ASCENDING\n            }\n            return if (getString(R.string.sort_by_popularity) == selected) {\n                Query.Direction.DESCENDING\n            } else {\n                Query.Direction.DESCENDING\n            }\n        }\n\n    val filters: Filters\n        get() {\n            val filters = Filters()\n\n            filters.category = selectedCategory\n            filters.city = selectedCity\n            filters.price = selectedPrice\n            filters.sortBy = selectedSortBy\n            filters.sortDirection = sortDirection\n\n            return filters\n        }\n\n    interface FilterListener {\n\n        fun onFilter(filters: Filters)\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View? {\n        _binding = DialogFiltersBinding.inflate(inflater, container, false)\n\n        binding.buttonSearch.setOnClickListener { onSearchClicked() }\n        binding.buttonCancel.setOnClickListener { onCancelClicked() }\n\n        return binding.root\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    override fun onAttach(context: Context) {\n        super.onAttach(context)\n\n        if (parentFragment is FilterListener) {\n            filterListener = parentFragment as FilterListener\n        }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        dialog?.window?.setLayout(\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            ViewGroup.LayoutParams.WRAP_CONTENT,\n        )\n    }\n\n    private fun onSearchClicked() {\n        filterListener?.onFilter(filters)\n        dismiss()\n    }\n\n    private fun onCancelClicked() {\n        dismiss()\n    }\n\n    fun resetFilters() {\n        _binding?.let {\n            it.spinnerCategory.setSelection(0)\n            it.spinnerCity.setSelection(0)\n            it.spinnerPrice.setSelection(0)\n            it.spinnerSort.setSelection(0)\n        }\n    }\n\n    companion object {\n\n        const val TAG = \"FilterDialog\"\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/Filters.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin\n\nimport android.content.Context\nimport android.text.TextUtils\nimport com.google.firebase.example.fireeats.R\nimport com.google.firebase.example.fireeats.kotlin.model.Restaurant\nimport com.google.firebase.example.fireeats.kotlin.util.RestaurantUtil\nimport com.google.firebase.firestore.Query\n\n/**\n * Object for passing filters around.\n */\nclass Filters {\n\n    var category: String? = null\n    var city: String? = null\n    var price = -1\n    var sortBy: String? = null\n    var sortDirection: Query.Direction = Query.Direction.DESCENDING\n\n    fun hasCategory(): Boolean {\n        return !TextUtils.isEmpty(category)\n    }\n\n    fun hasCity(): Boolean {\n        return !TextUtils.isEmpty(city)\n    }\n\n    fun hasPrice(): Boolean {\n        return price > 0\n    }\n\n    fun hasSortBy(): Boolean {\n        return !TextUtils.isEmpty(sortBy)\n    }\n\n    fun getSearchDescription(context: Context): String {\n        val desc = StringBuilder()\n\n        if (category == null && city == null) {\n            desc.append(\"<b>\")\n            desc.append(context.getString(R.string.all_restaurants))\n            desc.append(\"</b>\")\n        }\n\n        if (category != null) {\n            desc.append(\"<b>\")\n            desc.append(category)\n            desc.append(\"</b>\")\n        }\n\n        if (category != null && city != null) {\n            desc.append(\" in \")\n        }\n\n        if (city != null) {\n            desc.append(\"<b>\")\n            desc.append(city)\n            desc.append(\"</b>\")\n        }\n\n        if (price > 0) {\n            desc.append(\" for \")\n            desc.append(\"<b>\")\n            desc.append(RestaurantUtil.getPriceString(price))\n            desc.append(\"</b>\")\n        }\n\n        return desc.toString()\n    }\n\n    fun getOrderDescription(context: Context): String {\n        return when (sortBy) {\n            Restaurant.FIELD_PRICE -> context.getString(R.string.sorted_by_price)\n            Restaurant.FIELD_POPULARITY -> context.getString(R.string.sorted_by_popularity)\n            else -> context.getString(R.string.sorted_by_rating)\n        }\n    }\n\n    companion object {\n\n        val default: Filters\n            get() {\n                val filters = Filters()\n                filters.sortBy = Restaurant.FIELD_AVG_RATING\n                filters.sortDirection = Query.Direction.DESCENDING\n\n                return filters\n            }\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainActivity.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.navigation.Navigation\nimport com.google.firebase.example.fireeats.R\n\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n        setSupportActionBar(findViewById(R.id.toolbar))\n        Navigation.findNavController(this, R.id.nav_host_fragment)\n            .setGraph(R.navigation.nav_graph_kotlin)\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.text.HtmlCompat\nimport androidx.core.view.MenuHost\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.get\nimport androidx.navigation.fragment.findNavController\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.firebase.ui.auth.AuthUI\nimport com.firebase.ui.auth.ErrorCodes\nimport com.firebase.ui.auth.FirebaseAuthUIActivityResultContract\nimport com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult\nimport com.google.android.material.snackbar.Snackbar\nimport com.google.firebase.auth.auth\nimport com.google.firebase.example.fireeats.R\nimport com.google.firebase.example.fireeats.databinding.FragmentMainBinding\nimport com.google.firebase.example.fireeats.kotlin.adapter.RestaurantAdapter\nimport com.google.firebase.example.fireeats.kotlin.model.Restaurant\nimport com.google.firebase.example.fireeats.kotlin.util.RatingUtil\nimport com.google.firebase.example.fireeats.kotlin.util.RestaurantUtil\nimport com.google.firebase.example.fireeats.kotlin.viewmodel.MainActivityViewModel\nimport com.google.firebase.firestore.DocumentSnapshot\nimport com.google.firebase.firestore.FirebaseFirestore\nimport com.google.firebase.firestore.FirebaseFirestoreException\nimport com.google.firebase.firestore.Query\nimport com.google.firebase.firestore.firestore\nimport com.google.firebase.Firebase\n\nclass MainFragment :\n    Fragment(),\n    FilterDialogFragment.FilterListener,\n    RestaurantAdapter.OnRestaurantSelectedListener,\n    MenuProvider {\n\n    lateinit var firestore: FirebaseFirestore\n    lateinit var query: Query\n\n    private lateinit var binding: FragmentMainBinding\n    private lateinit var filterDialog: FilterDialogFragment\n    lateinit var adapter: RestaurantAdapter\n\n    private lateinit var viewModel: MainActivityViewModel\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        binding = FragmentMainBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // View model\n        viewModel = ViewModelProvider(this).get<MainActivityViewModel>()\n\n        // Enable Firestore logging\n        FirebaseFirestore.setLoggingEnabled(true)\n\n        // Firestore\n        firestore = Firebase.firestore\n\n        // Get ${LIMIT} restaurants\n        query = firestore.collection(\"restaurants\")\n            .orderBy(\"avgRating\", Query.Direction.DESCENDING)\n            .limit(LIMIT.toLong())\n\n        // RecyclerView\n        adapter = object : RestaurantAdapter(query, this@MainFragment) {\n            override fun onDataChanged() {\n                // Show/hide content if the query returns empty.\n                if (itemCount == 0) {\n                    binding.recyclerRestaurants.visibility = View.GONE\n                    binding.viewEmpty.visibility = View.VISIBLE\n                } else {\n                    binding.recyclerRestaurants.visibility = View.VISIBLE\n                    binding.viewEmpty.visibility = View.GONE\n                }\n            }\n\n            override fun onError(e: FirebaseFirestoreException) {\n                // Show a snackbar on errors\n                Snackbar.make(\n                    binding.root,\n                    \"Error: check logs for info.\",\n                    Snackbar.LENGTH_LONG,\n                ).show()\n            }\n        }\n\n        // MenuProvider\n        val menuHost: MenuHost = requireActivity() as MenuHost\n        menuHost.addMenuProvider(this)\n\n        binding.recyclerRestaurants.layoutManager = LinearLayoutManager(context)\n        binding.recyclerRestaurants.adapter = adapter\n\n        // Filter Dialog\n        filterDialog = FilterDialogFragment()\n\n        binding.filterBar.setOnClickListener { onFilterClicked() }\n        binding.buttonClearFilter.setOnClickListener { onClearFilterClicked() }\n    }\n\n    public override fun onStart() {\n        super.onStart()\n\n        // Start sign in if necessary\n        if (shouldStartSignIn()) {\n            startSignIn()\n            return\n        }\n\n        // Apply filters\n        onFilter(viewModel.filters)\n\n        // Start listening for Firestore updates\n        adapter.startListening()\n    }\n\n    public override fun onStop() {\n        super.onStop()\n        adapter.stopListening()\n    }\n\n    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n        menuInflater.inflate(R.menu.menu_main, menu)\n    }\n\n    override fun onMenuItemSelected(item: MenuItem): Boolean {\n        return when (item.itemId) {\n            R.id.menu_add_items -> {\n                onAddItemsClicked()\n                true\n            }\n            R.id.menu_sign_out -> {\n                AuthUI.getInstance().signOut(requireContext())\n                startSignIn()\n                true\n            }\n            else -> false\n        }\n    }\n\n    private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) {\n        val response = result.idpResponse\n        viewModel.isSigningIn = false\n\n        if (result.resultCode != Activity.RESULT_OK) {\n            if (response == null) {\n                // User pressed the back button.\n                requireActivity().finish()\n            } else if (response.error != null && response.error!!.errorCode == ErrorCodes.NO_NETWORK) {\n                showSignInErrorDialog(R.string.message_no_network)\n            } else {\n                showSignInErrorDialog(R.string.message_unknown)\n            }\n        }\n    }\n\n    private fun onFilterClicked() {\n        // Show the dialog containing filter options\n        filterDialog.show(childFragmentManager, FilterDialogFragment.TAG)\n    }\n\n    private fun onClearFilterClicked() {\n        filterDialog.resetFilters()\n\n        onFilter(Filters.default)\n    }\n\n    override fun onRestaurantSelected(restaurant: DocumentSnapshot) {\n        // Go to the details page for the selected restaurant\n        val action = MainFragmentDirections\n            .actionMainFragmentToRestaurantDetailFragment(restaurant.id)\n\n        findNavController().navigate(action)\n    }\n\n    override fun onFilter(filters: Filters) {\n        // Construct query basic query\n        var query: Query = firestore.collection(\"restaurants\")\n\n        // Category (equality filter)\n        if (filters.hasCategory()) {\n            query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.category)\n        }\n\n        // City (equality filter)\n        if (filters.hasCity()) {\n            query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.city)\n        }\n\n        // Price (equality filter)\n        if (filters.hasPrice()) {\n            query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.price)\n        }\n\n        // Sort by (orderBy with direction)\n        if (filters.hasSortBy()) {\n            query = query.orderBy(filters.sortBy.toString(), filters.sortDirection)\n        }\n\n        // Limit items\n        query = query.limit(LIMIT.toLong())\n\n        // Update the query\n        adapter.setQuery(query)\n\n        // Set header\n        binding.textCurrentSearch.text = HtmlCompat.fromHtml(\n            filters.getSearchDescription(requireContext()),\n            HtmlCompat.FROM_HTML_MODE_LEGACY,\n        )\n        binding.textCurrentSortBy.text = filters.getOrderDescription(requireContext())\n\n        // Save filters\n        viewModel.filters = filters\n    }\n\n    private fun shouldStartSignIn(): Boolean {\n        return !viewModel.isSigningIn && Firebase.auth.currentUser == null\n    }\n\n    private fun startSignIn() {\n        // Sign in with FirebaseUI\n        val signInLauncher = requireActivity().registerForActivityResult(\n            FirebaseAuthUIActivityResultContract(),\n        ) { result -> this.onSignInResult(result) }\n\n        val intent = AuthUI.getInstance().createSignInIntentBuilder()\n            .setAvailableProviders(listOf(AuthUI.IdpConfig.EmailBuilder().build()))\n            .setCredentialManagerEnabled(false)\n            .build()\n\n        signInLauncher.launch(intent)\n        viewModel.isSigningIn = true\n    }\n\n    private fun onAddItemsClicked() {\n        // Add a bunch of random restaurants\n        val batch = firestore.batch()\n        for (i in 0..9) {\n            val restRef = firestore.collection(\"restaurants\").document()\n\n            // Create random restaurant / ratings\n            val randomRestaurant = RestaurantUtil.getRandom(requireContext())\n            val randomRatings = RatingUtil.getRandomList(randomRestaurant.numRatings)\n            randomRestaurant.avgRating = RatingUtil.getAverageRating(randomRatings)\n\n            // Add restaurant\n            batch.set(restRef, randomRestaurant)\n\n            // Add ratings to subcollection\n            for (rating in randomRatings) {\n                batch.set(restRef.collection(\"ratings\").document(), rating)\n            }\n        }\n\n        batch.commit().addOnCompleteListener { task ->\n            if (task.isSuccessful) {\n                Log.d(TAG, \"Write batch succeeded.\")\n            } else {\n                Log.w(TAG, \"write batch failed.\", task.exception)\n            }\n        }\n    }\n\n    private fun showSignInErrorDialog(@StringRes message: Int) {\n        val dialog = AlertDialog.Builder(requireContext())\n            .setTitle(R.string.title_sign_in_error)\n            .setMessage(message)\n            .setCancelable(false)\n            .setPositiveButton(R.string.option_retry) { _, _ -> startSignIn() }\n            .setNegativeButton(R.string.option_exit) { _, _ -> requireActivity().finish() }.create()\n\n        dialog.show()\n    }\n\n    companion object {\n\n        private const val TAG = \"MainActivity\"\n\n        private const val LIMIT = 50\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RatingDialogFragment.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.DialogFragment\nimport com.google.firebase.auth.auth\nimport com.google.firebase.example.fireeats.databinding.DialogRatingBinding\nimport com.google.firebase.example.fireeats.kotlin.model.Rating\nimport com.google.firebase.Firebase\n\n/**\n * Dialog Fragment containing rating form.\n */\nclass RatingDialogFragment : DialogFragment() {\n\n    private var _binding: DialogRatingBinding? = null\n    private val binding get() = _binding!!\n    private var ratingListener: RatingListener? = null\n\n    internal interface RatingListener {\n\n        fun onRating(rating: Rating)\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View? {\n        _binding = DialogRatingBinding.inflate(inflater, container, false)\n\n        binding.restaurantFormButton.setOnClickListener { onSubmitClicked() }\n        binding.restaurantFormCancel.setOnClickListener { onCancelClicked() }\n\n        return binding.root\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null\n    }\n\n    override fun onAttach(context: Context) {\n        super.onAttach(context)\n\n        if (parentFragment is RatingListener) {\n            ratingListener = parentFragment as RatingListener\n        }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        dialog?.window?.setLayout(\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            ViewGroup.LayoutParams.WRAP_CONTENT,\n        )\n    }\n\n    private fun onSubmitClicked() {\n        val user = Firebase.auth.currentUser\n        user?.let {\n            val rating = Rating(\n                it,\n                binding.restaurantFormRating.rating.toDouble(),\n                binding.restaurantFormText.text.toString(),\n            )\n\n            ratingListener?.onRating(rating)\n        }\n\n        dismiss()\n    }\n\n    private fun onCancelClicked() {\n        dismiss()\n    }\n\n    companion object {\n\n        const val TAG = \"RatingDialog\"\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.inputmethod.InputMethodManager\nimport androidx.fragment.app.Fragment\nimport androidx.navigation.fragment.findNavController\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.bumptech.glide.Glide\nimport com.google.android.gms.tasks.Task\nimport com.google.android.material.snackbar.Snackbar\nimport com.google.firebase.example.fireeats.R\nimport com.google.firebase.example.fireeats.databinding.FragmentRestaurantDetailBinding\nimport com.google.firebase.example.fireeats.kotlin.adapter.RatingAdapter\nimport com.google.firebase.example.fireeats.kotlin.model.Rating\nimport com.google.firebase.example.fireeats.kotlin.model.Restaurant\nimport com.google.firebase.example.fireeats.kotlin.util.RestaurantUtil\nimport com.google.firebase.firestore.DocumentReference\nimport com.google.firebase.firestore.DocumentSnapshot\nimport com.google.firebase.firestore.EventListener\nimport com.google.firebase.firestore.FirebaseFirestore\nimport com.google.firebase.firestore.FirebaseFirestoreException\nimport com.google.firebase.firestore.ListenerRegistration\nimport com.google.firebase.firestore.Query\nimport com.google.firebase.firestore.firestore\nimport com.google.firebase.firestore.toObject\nimport com.google.firebase.Firebase\n\nclass RestaurantDetailFragment :\n    Fragment(),\n    EventListener<DocumentSnapshot>,\n    RatingDialogFragment.RatingListener {\n\n    private var ratingDialog: RatingDialogFragment? = null\n\n    private lateinit var binding: FragmentRestaurantDetailBinding\n    private lateinit var firestore: FirebaseFirestore\n    private lateinit var restaurantRef: DocumentReference\n    private lateinit var ratingAdapter: RatingAdapter\n\n    private var restaurantRegistration: ListenerRegistration? = null\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        binding = FragmentRestaurantDetailBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // Get restaurant ID from extras\n        val restaurantId = RestaurantDetailFragmentArgs.fromBundle(requireArguments()).keyRestaurantId\n\n        // Initialize Firestore\n        firestore = Firebase.firestore\n\n        // Get reference to the restaurant\n        restaurantRef = firestore.collection(\"restaurants\").document(restaurantId)\n\n        // Get ratings\n        val ratingsQuery = restaurantRef\n            .collection(\"ratings\")\n            .orderBy(\"timestamp\", Query.Direction.DESCENDING)\n            .limit(50)\n\n        // RecyclerView\n        ratingAdapter = object : RatingAdapter(ratingsQuery) {\n            override fun onDataChanged() {\n                if (itemCount == 0) {\n                    binding.recyclerRatings.visibility = View.GONE\n                    binding.viewEmptyRatings.visibility = View.VISIBLE\n                } else {\n                    binding.recyclerRatings.visibility = View.VISIBLE\n                    binding.viewEmptyRatings.visibility = View.GONE\n                }\n            }\n        }\n        binding.recyclerRatings.layoutManager = LinearLayoutManager(context)\n        binding.recyclerRatings.adapter = ratingAdapter\n\n        ratingDialog = RatingDialogFragment()\n\n        binding.restaurantButtonBack.setOnClickListener { onBackArrowClicked() }\n        binding.fabShowRatingDialog.setOnClickListener { onAddRatingClicked() }\n    }\n\n    public override fun onStart() {\n        super.onStart()\n\n        ratingAdapter.startListening()\n        restaurantRegistration = restaurantRef.addSnapshotListener(this)\n    }\n\n    public override fun onStop() {\n        super.onStop()\n\n        ratingAdapter.stopListening()\n\n        restaurantRegistration?.remove()\n        restaurantRegistration = null\n    }\n\n    /**\n     * Listener for the Restaurant document ([.restaurantRef]).\n     */\n    override fun onEvent(snapshot: DocumentSnapshot?, e: FirebaseFirestoreException?) {\n        if (e != null) {\n            Log.w(TAG, \"restaurant:onEvent\", e)\n            return\n        }\n\n        snapshot?.let {\n            val restaurant = snapshot.toObject<Restaurant>()\n            if (restaurant != null) {\n                onRestaurantLoaded(restaurant)\n            }\n        }\n    }\n\n    private fun onRestaurantLoaded(restaurant: Restaurant) {\n        binding.restaurantName.text = restaurant.name\n        binding.restaurantRating.rating = restaurant.avgRating.toFloat()\n        binding.restaurantNumRatings.text = getString(R.string.fmt_num_ratings, restaurant.numRatings)\n        binding.restaurantCity.text = restaurant.city\n        binding.restaurantCategory.text = restaurant.category\n        binding.restaurantPrice.text = RestaurantUtil.getPriceString(restaurant)\n\n        // Background image\n        Glide.with(binding.restaurantImage.context)\n            .load(restaurant.photo)\n            .into(binding.restaurantImage)\n    }\n\n    private fun onBackArrowClicked() {\n        findNavController().popBackStack()\n    }\n\n    private fun onAddRatingClicked() {\n        ratingDialog?.show(childFragmentManager, RatingDialogFragment.TAG)\n    }\n\n    override fun onRating(rating: Rating) {\n        // In a transaction, add the new rating and update the aggregate totals\n        addRating(restaurantRef, rating)\n            .addOnSuccessListener(requireActivity()) {\n                Log.d(TAG, \"Rating added\")\n\n                // Hide keyboard and scroll to top\n                hideKeyboard()\n                binding.recyclerRatings.smoothScrollToPosition(0)\n            }\n            .addOnFailureListener(requireActivity()) { e ->\n                Log.w(TAG, \"Add rating failed\", e)\n\n                // Show failure message and hide keyboard\n                hideKeyboard()\n                Snackbar.make(\n                    requireView().findViewById(android.R.id.content),\n                    \"Failed to add rating\",\n                    Snackbar.LENGTH_SHORT,\n                ).show()\n            }\n    }\n\n    private fun addRating(restaurantRef: DocumentReference, rating: Rating): Task<Void> {\n        // Create reference for new rating, for use inside the transaction\n        val ratingRef = restaurantRef.collection(\"ratings\").document()\n\n        // In a transaction, add the new rating and update the aggregate totals\n        return firestore.runTransaction { transaction ->\n            val restaurant = transaction.get(restaurantRef).toObject<Restaurant>()\n                ?: throw Exception(\"Restaurant not found at ${restaurantRef.path}\")\n\n            // Compute new number of ratings\n            val newNumRatings = restaurant.numRatings + 1\n\n            // Compute new average rating\n            val oldRatingTotal = restaurant.avgRating * restaurant.numRatings\n            val newAvgRating = (oldRatingTotal + rating.rating) / newNumRatings\n\n            // Set new restaurant info\n            restaurant.numRatings = newNumRatings\n            restaurant.avgRating = newAvgRating\n\n            // Commit to Firestore\n            transaction.set(restaurantRef, restaurant)\n            transaction.set(ratingRef, rating)\n\n            null\n        }\n    }\n\n    private fun hideKeyboard() {\n        val view = requireActivity().currentFocus\n        if (view != null) {\n            (requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)\n                .hideSoftInputFromWindow(view.windowToken, 0)\n        }\n    }\n\n    companion object {\n\n        private const val TAG = \"RestaurantDetail\"\n\n        const val KEY_RESTAURANT_ID = \"key_restaurant_id\"\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/adapter/FirestoreAdapter.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin.adapter\n\nimport android.util.Log\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.firebase.firestore.DocumentChange\nimport com.google.firebase.firestore.DocumentSnapshot\nimport com.google.firebase.firestore.EventListener\nimport com.google.firebase.firestore.FirebaseFirestoreException\nimport com.google.firebase.firestore.ListenerRegistration\nimport com.google.firebase.firestore.Query\nimport com.google.firebase.firestore.QuerySnapshot\nimport java.util.ArrayList\n\n/**\n * RecyclerView adapter for displaying the results of a Firestore [Query].\n *\n * Note that this class forgoes some efficiency to gain simplicity. For example, the result of\n * [DocumentSnapshot.toObject] is not cached so the same object may be deserialized\n * many times as the user scrolls.\n */\nabstract class FirestoreAdapter<VH : RecyclerView.ViewHolder>(private var query: Query?) :\n    RecyclerView.Adapter<VH>(),\n    EventListener<QuerySnapshot> {\n\n    private var registration: ListenerRegistration? = null\n\n    private val snapshots = ArrayList<DocumentSnapshot>()\n\n    override fun onEvent(documentSnapshots: QuerySnapshot?, e: FirebaseFirestoreException?) {\n        if (e != null) {\n            Log.w(TAG, \"onEvent:error\", e)\n            onError(e)\n            return\n        }\n\n        if (documentSnapshots == null) {\n            return\n        }\n\n        // Dispatch the event\n        Log.d(TAG, \"onEvent:numChanges:\" + documentSnapshots.documentChanges.size)\n        for (change in documentSnapshots.documentChanges) {\n            when (change.type) {\n                DocumentChange.Type.ADDED -> onDocumentAdded(change)\n                DocumentChange.Type.MODIFIED -> onDocumentModified(change)\n                DocumentChange.Type.REMOVED -> onDocumentRemoved(change)\n            }\n        }\n\n        onDataChanged()\n    }\n\n    fun startListening() {\n        if (query != null && registration == null) {\n            registration = query!!.addSnapshotListener(this)\n        }\n    }\n\n    fun stopListening() {\n        registration?.remove()\n        registration = null\n\n        snapshots.clear()\n        notifyDataSetChanged()\n    }\n\n    fun setQuery(query: Query) {\n        // Stop listening\n        stopListening()\n\n        // Clear existing data\n        snapshots.clear()\n        notifyDataSetChanged()\n\n        // Listen to new query\n        this.query = query\n        startListening()\n    }\n\n    open fun onError(e: FirebaseFirestoreException) {\n        Log.w(TAG, \"onError\", e)\n    }\n\n    open fun onDataChanged() {}\n\n    override fun getItemCount(): Int {\n        return snapshots.size\n    }\n\n    protected fun getSnapshot(index: Int): DocumentSnapshot {\n        return snapshots[index]\n    }\n\n    private fun onDocumentAdded(change: DocumentChange) {\n        snapshots.add(change.newIndex, change.document)\n        notifyItemInserted(change.newIndex)\n    }\n\n    private fun onDocumentModified(change: DocumentChange) {\n        if (change.oldIndex == change.newIndex) {\n            // Item changed but remained in same position\n            snapshots[change.oldIndex] = change.document\n            notifyItemChanged(change.oldIndex)\n        } else {\n            // Item changed and changed position\n            snapshots.removeAt(change.oldIndex)\n            snapshots.add(change.newIndex, change.document)\n            notifyItemMoved(change.oldIndex, change.newIndex)\n        }\n    }\n\n    private fun onDocumentRemoved(change: DocumentChange) {\n        snapshots.removeAt(change.oldIndex)\n        notifyItemRemoved(change.oldIndex)\n    }\n\n    companion object {\n\n        private const val TAG = \"FirestoreAdapter\"\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/adapter/RatingAdapter.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin.adapter\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.firebase.example.fireeats.databinding.ItemRatingBinding\nimport com.google.firebase.example.fireeats.kotlin.model.Rating\nimport com.google.firebase.firestore.Query\nimport com.google.firebase.firestore.toObject\nimport java.text.SimpleDateFormat\nimport java.util.Locale\n\n/**\n * RecyclerView adapter for a list of [Rating].\n */\nopen class RatingAdapter(query: Query) : FirestoreAdapter<RatingAdapter.ViewHolder>(query) {\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n        return ViewHolder(ItemRatingBinding.inflate(LayoutInflater.from(parent.context), parent, false))\n    }\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n        holder.bind(getSnapshot(position).toObject<Rating>())\n    }\n\n    class ViewHolder(val binding: ItemRatingBinding) : RecyclerView.ViewHolder(binding.root) {\n\n        fun bind(rating: Rating?) {\n            if (rating == null) {\n                return\n            }\n\n            binding.ratingItemName.text = rating.userName\n            binding.ratingItemRating.rating = rating.rating.toFloat()\n            binding.ratingItemText.text = rating.text\n\n            if (rating.timestamp != null) {\n                binding.ratingItemDate.text = FORMAT.format(rating.timestamp)\n            }\n        }\n\n        companion object {\n\n            private val FORMAT = SimpleDateFormat(\n                \"MM/dd/yyyy\",\n                Locale.US,\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/adapter/RestaurantAdapter.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin.adapter\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport com.bumptech.glide.Glide\nimport com.google.firebase.example.fireeats.R\nimport com.google.firebase.example.fireeats.databinding.ItemRestaurantBinding\nimport com.google.firebase.example.fireeats.kotlin.model.Restaurant\nimport com.google.firebase.example.fireeats.kotlin.util.RestaurantUtil\nimport com.google.firebase.firestore.DocumentSnapshot\nimport com.google.firebase.firestore.Query\nimport com.google.firebase.firestore.toObject\n\n/**\n * RecyclerView adapter for a list of Restaurants.\n */\nopen class RestaurantAdapter(query: Query, private val listener: OnRestaurantSelectedListener) :\n    FirestoreAdapter<RestaurantAdapter.ViewHolder>(query) {\n\n    interface OnRestaurantSelectedListener {\n\n        fun onRestaurantSelected(restaurant: DocumentSnapshot)\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n        return ViewHolder(\n            ItemRestaurantBinding.inflate(\n                LayoutInflater.from(parent.context),\n                parent,\n                false,\n            ),\n        )\n    }\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n        holder.bind(getSnapshot(position), listener)\n    }\n\n    class ViewHolder(val binding: ItemRestaurantBinding) : RecyclerView.ViewHolder(binding.root) {\n\n        fun bind(\n            snapshot: DocumentSnapshot,\n            listener: OnRestaurantSelectedListener?,\n        ) {\n        \n            val restaurant = snapshot.toObject<Restaurant>() ?: return\n\n            val resources = binding.root.resources\n\n            // Load image\n            Glide.with(binding.restaurantItemImage.context)\n                .load(restaurant.photo)\n                .into(binding.restaurantItemImage)\n\n            val numRatings: Int = restaurant.numRatings\n\n            binding.restaurantItemName.text = restaurant.name\n            binding.restaurantItemRating.rating = restaurant.avgRating.toFloat()\n            binding.restaurantItemCity.text = restaurant.city\n            binding.restaurantItemCategory.text = restaurant.category\n            binding.restaurantItemNumRatings.text = resources.getString(\n                R.string.fmt_num_ratings,\n                numRatings,\n            )\n            binding.restaurantItemPrice.text = RestaurantUtil.getPriceString(restaurant)\n\n            // Click listener\n            binding.root.setOnClickListener {\n                listener?.onRestaurantSelected(snapshot)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/model/Rating.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin.model\n\nimport android.text.TextUtils\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.firestore.ServerTimestamp\nimport java.util.Date\n\n/**\n * Model POJO for a rating.\n */\ndata class Rating(\n    var userId: String? = null,\n    var userName: String? = null,\n    var rating: Double = 0.toDouble(),\n    var text: String? = null,\n    @ServerTimestamp var timestamp: Date? = null,\n) {\n\n    constructor(user: FirebaseUser, rating: Double, text: String) : this() {\n        this.userId = user.uid\n        this.userName = user.displayName\n        if (TextUtils.isEmpty(this.userName)) {\n            this.userName = user.email\n        }\n\n        this.rating = rating\n        this.text = text\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/model/Restaurant.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin.model\n\nimport com.google.firebase.firestore.IgnoreExtraProperties\n\n/**\n * Restaurant POJO.\n */\n@IgnoreExtraProperties\ndata class Restaurant(\n    var name: String? = null,\n    var city: String? = null,\n    var category: String? = null,\n    var photo: String? = null,\n    var price: Int = 0,\n    var numRatings: Int = 0,\n    var avgRating: Double = 0.toDouble(),\n) {\n\n    companion object {\n\n        const val FIELD_CITY = \"city\"\n        const val FIELD_CATEGORY = \"category\"\n        const val FIELD_PRICE = \"price\"\n        const val FIELD_POPULARITY = \"numRatings\"\n        const val FIELD_AVG_RATING = \"avgRating\"\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/util/RatingUtil.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin.util\n\nimport com.google.firebase.example.fireeats.kotlin.model.Rating\nimport java.util.ArrayList\nimport java.util.Random\nimport java.util.UUID\nimport kotlin.math.floor\n\n/**\n * Utilities for Ratings.\n */\nobject RatingUtil {\n\n    private val REVIEW_CONTENTS = arrayOf(\n        // 0 - 1 stars\n        \"This was awful! Totally inedible.\",\n\n        // 1 - 2 stars\n        \"This was pretty bad, would not go back.\",\n\n        // 2 - 3 stars\n        \"I was fed, so that's something.\",\n\n        // 3 - 4 stars\n        \"This was a nice meal, I'd go back.\",\n\n        // 4 - 5 stars\n        \"This was fantastic!  Best ever!\",\n    )\n\n    /**\n     * Create a random Rating POJO.\n     */\n    private val random: Rating\n        get() {\n            val rating = Rating()\n\n            val random = Random()\n\n            val score = random.nextDouble() * 5.0\n            val text = REVIEW_CONTENTS[floor(score).toInt()]\n\n            rating.userId = UUID.randomUUID().toString()\n            rating.userName = \"Random User\"\n            rating.rating = score\n            rating.text = text\n\n            return rating\n        }\n\n    /**\n     * Get a list of random Rating POJOs.\n     */\n    fun getRandomList(length: Int): List<Rating> {\n        val result = ArrayList<Rating>()\n\n        for (i in 0 until length) {\n            result.add(random)\n        }\n\n        return result\n    }\n\n    /**\n     * Get the average rating of a List.\n     */\n    fun getAverageRating(ratings: List<Rating>): Double {\n        var sum = 0.0\n\n        for (rating in ratings) {\n            sum += rating.rating\n        }\n\n        return sum / ratings.size\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/util/RestaurantUtil.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin.util\n\nimport android.content.Context\nimport com.google.firebase.example.fireeats.R\nimport com.google.firebase.example.fireeats.kotlin.model.Restaurant\nimport java.util.Arrays\nimport java.util.Locale\nimport java.util.Random\n\n/**\n * Utilities for Restaurants.\n */\nobject RestaurantUtil {\n\n    private const val RESTAURANT_URL_FMT = \"https://storage.googleapis.com/firestorequickstarts.appspot.com/food_%d.png\"\n    private const val MAX_IMAGE_NUM = 22\n\n    private val NAME_FIRST_WORDS = arrayOf(\n        \"Foo\", \"Bar\", \"Baz\", \"Qux\", \"Fire\", \"Sam's\", \"World Famous\", \"Google\", \"The Best\",\n    )\n\n    private val NAME_SECOND_WORDS = arrayOf(\n        \"Restaurant\",\n        \"Cafe\",\n        \"Spot\",\n        \"Eatin' Place\",\n        \"Eatery\",\n        \"Drive Thru\",\n        \"Diner\",\n    )\n\n    /**\n     * Create a random Restaurant POJO.\n     */\n    fun getRandom(context: Context): Restaurant {\n        val restaurant = Restaurant()\n        val random = Random()\n\n        // Cities (first elemnt is 'Any')\n        var cities = context.resources.getStringArray(R.array.cities)\n        cities = cities.copyOfRange(1, cities.size)\n\n        // Categories (first element is 'Any')\n        var categories = context.resources.getStringArray(R.array.categories)\n        categories = categories.copyOfRange(1, categories.size)\n\n        val prices = intArrayOf(1, 2, 3)\n\n        restaurant.name = getRandomName(random)\n        restaurant.city = getRandomString(cities, random)\n        restaurant.category = getRandomString(categories, random)\n        restaurant.photo = getRandomImageUrl(random)\n        restaurant.price = getRandomInt(prices, random)\n        restaurant.numRatings = random.nextInt(20)\n\n        // Note: average rating intentionally not set\n\n        return restaurant\n    }\n\n    /**\n     * Get a random image.\n     */\n    private fun getRandomImageUrl(random: Random): String {\n        // Integer between 1 and MAX_IMAGE_NUM (inclusive)\n        val id = random.nextInt(MAX_IMAGE_NUM) + 1\n\n        return String.format(Locale.getDefault(), RESTAURANT_URL_FMT, id)\n    }\n\n    /**\n     * Get price represented as dollar signs.\n     */\n    fun getPriceString(restaurant: Restaurant): String {\n        return getPriceString(restaurant.price)\n    }\n\n    /**\n     * Get price represented as dollar signs.\n     */\n    fun getPriceString(priceInt: Int): String {\n        return when (priceInt) {\n            1 -> \"$\"\n            2 -> \"$$\"\n            3 -> \"$$$\"\n            else -> \"$$$\"\n        }\n    }\n\n    private fun getRandomName(random: Random): String {\n        return (\n            getRandomString(NAME_FIRST_WORDS, random) + \" \" +\n                getRandomString(NAME_SECOND_WORDS, random)\n            )\n    }\n\n    private fun getRandomString(array: Array<String>, random: Random): String {\n        val ind = random.nextInt(array.size)\n        return array[ind]\n    }\n\n    private fun getRandomInt(array: IntArray, random: Random): Int {\n        val ind = random.nextInt(array.size)\n        return array[ind]\n    }\n}\n"
  },
  {
    "path": "firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/viewmodel/MainActivityViewModel.kt",
    "content": "package com.google.firebase.example.fireeats.kotlin.viewmodel\n\nimport androidx.lifecycle.ViewModel\nimport com.google.firebase.example.fireeats.kotlin.Filters\n\n/**\n * ViewModel for [com.google.firebase.example.fireeats.MainActivity].\n */\n\nclass MainActivityViewModel : ViewModel() {\n\n    var isSigningIn: Boolean = false\n    var filters: Filters = Filters.default\n}\n"
  },
  {
    "path": "firestore/app/src/main/res/anim/slide_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\"\n    android:fromXDelta=\"-100%p\"\n    android:toXDelta=\"0\" />\n"
  },
  {
    "path": "firestore/app/src/main/res/anim/slide_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\"\n    android:fromXDelta=\"100%p\"\n    android:toXDelta=\"0\" />\n"
  },
  {
    "path": "firestore/app/src/main/res/anim/slide_out_to_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\"\n    android:fromXDelta=\"0\"\n    android:toXDelta=\"-100%p\" />\n"
  },
  {
    "path": "firestore/app/src/main/res/anim/slide_out_to_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\"\n    android:fromXDelta=\"0\"\n    android:toXDelta=\"100%p\" />\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/bg_shadow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient\n        android:angle=\"270\"\n        android:endColor=\"@android:color/transparent\"\n        android:startColor=\"#9b404040\" />\n</shape>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/gradient_up.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient\n        android:angle=\"90\"\n        android:endColor=\"@android:color/transparent\"\n        android:startColor=\"#99000000\" />\n</shape>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_add_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_arrow_back_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_close_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_fastfood_white_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M18.06,22.99h1.66c0.84,0 1.53,-0.64 1.63,-1.46L23,5.05h-5L18,1h-1.97v4.05h-4.97l0.3,2.34c1.71,0.47 3.31,1.32 4.27,2.26 1.44,1.42 2.43,2.89 2.43,5.29v8.05zM1,21.99L1,21h15.03v0.99c0,0.55 -0.45,1 -1.01,1L2.01,22.99c-0.56,0 -1.01,-0.45 -1.01,-1zM16.03,14.99c0,-8 -15.03,-8 -15.03,0h15.03zM1.02,17h15v2h-15z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_filter_list_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_local_dining_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M8.1,13.34l2.83,-2.83L3.91,3.5c-1.56,1.56 -1.56,4.09 0,5.66l4.19,4.18zM14.88,11.53c1.53,0.71 3.68,0.21 5.27,-1.38 1.91,-1.91 2.28,-4.65 0.81,-6.12 -1.46,-1.46 -4.2,-1.1 -6.12,0.81 -1.59,1.59 -2.09,3.74 -1.38,5.27L3.7,19.87l1.41,1.41L12,14.41l6.88,6.88 1.41,-1.41L13.41,13l1.47,-1.47z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_monetization_on_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13.41,18.09L13.41,20h-2.67v-1.93c-1.71,-0.36 -3.16,-1.46 -3.27,-3.4h1.96c0.1,1.05 0.82,1.87 2.65,1.87 1.96,0 2.4,-0.98 2.4,-1.59 0,-0.83 -0.44,-1.61 -2.67,-2.14 -2.48,-0.6 -4.18,-1.62 -4.18,-3.67 0,-1.72 1.39,-2.84 3.11,-3.21L10.74,4h2.67v1.95c1.86,0.45 2.79,1.86 2.85,3.39L14.3,9.34c-0.05,-1.11 -0.64,-1.87 -2.22,-1.87 -1.5,0 -2.4,0.68 -2.4,1.64 0,0.84 0.65,1.39 2.67,1.91s4.18,1.39 4.18,3.91c-0.01,1.83 -1.38,2.83 -3.12,3.16z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_place_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_restaurant_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M11,9L9,9L9,2L7,2v7L5,9L5,2L3,2v7c0,2.12 1.66,3.84 3.75,3.97L6.75,22h2.5v-9.03C11.34,12.84 13,11.12 13,9L13,2h-2v7zM16,6v8h2.5v8L21,22L21,2c-2.76,0 -5,2.24 -5,4z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/drawable/ic_sort_white_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:pathData=\"M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z\"\n        android:fillColor=\"#FFFFFF\"/>\n</vector>\n"
  },
  {
    "path": "firestore/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n    tools:viewBindingIgnore=\"true\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/colorPrimary\"\n        android:theme=\"@style/AppTheme\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:logo=\"@drawable/ic_restaurant_white_24px\"\n        app:popupTheme=\"@style/Theme.AppCompat.Light.DarkActionBar\"\n        app:title=\"@string/app_name\"\n        app:titleMarginStart=\"24dp\"\n        app:titleTextColor=\"@android:color/white\" />\n\n    <fragment\n        android:id=\"@+id/nav_host_fragment\"\n        android:name=\"androidx.navigation.fragment.NavHostFragment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:defaultNavHost=\"true\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/toolbar\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "firestore/app/src/main/res/layout/dialog_filters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/filters_form\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:id=\"@+id/filterDialogTitle\"\n        style=\"@style/AppTheme.Title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/header_filters\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <!-- Food Type -->\n    <ImageView\n        android:id=\"@+id/icon_category\"\n        style=\"@style/AppTheme.FilterIcon\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/spinnerCategory\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/spinnerCategory\"\n        app:srcCompat=\"@drawable/ic_fastfood_white_24dp\"\n        app:tint=\"@color/greySecondary\" />\n\n    <Spinner\n        android:id=\"@+id/spinnerCategory\"\n        style=\"@style/AppTheme.FilterSpinner\"\n        android:layout_width=\"0dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:entries=\"@array/categories\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/icon_category\"\n        app:layout_constraintTop_toBottomOf=\"@+id/filterDialogTitle\" />\n\n\n    <!-- Location -->\n    <ImageView\n        android:id=\"@+id/icon_city\"\n        style=\"@style/AppTheme.FilterIcon\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/spinnerCity\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/spinnerCity\"\n        app:srcCompat=\"@drawable/ic_place_white_24px\"\n        app:tint=\"@color/greySecondary\" />\n\n    <Spinner\n        android:id=\"@+id/spinnerCity\"\n        style=\"@style/AppTheme.FilterSpinner\"\n        android:layout_width=\"0dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:entries=\"@array/cities\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/icon_city\"\n        app:layout_constraintTop_toBottomOf=\"@+id/spinnerCategory\" />\n\n    <!-- Price -->\n    <ImageView\n        android:id=\"@+id/icon_price\"\n        style=\"@style/AppTheme.FilterIcon\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/spinnerPrice\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/spinnerPrice\"\n        app:srcCompat=\"@drawable/ic_monetization_on_white_24px\"\n        app:tint=\"@color/greySecondary\" />\n\n    <Spinner\n        android:id=\"@+id/spinnerPrice\"\n        style=\"@style/AppTheme.FilterSpinner\"\n        android:layout_width=\"0dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:entries=\"@array/prices\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/icon_price\"\n        app:layout_constraintTop_toBottomOf=\"@+id/spinnerCity\" />\n\n    <!-- Sort By -->\n    <ImageView\n        android:id=\"@+id/icon_sort\"\n        style=\"@style/AppTheme.FilterIcon\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/spinnerSort\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/spinnerSort\"\n        app:srcCompat=\"@drawable/ic_sort_white_24px\"\n        app:tint=\"@color/greySecondary\" />\n\n    <Spinner\n        android:id=\"@+id/spinnerSort\"\n        style=\"@style/AppTheme.FilterSpinner\"\n        android:layout_width=\"0dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:entries=\"@array/sort_by\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/icon_sort\"\n        app:layout_constraintTop_toBottomOf=\"@+id/spinnerPrice\" />\n\n    <!-- Cancel and apply buttons -->\n    <View\n        android:id=\"@+id/view2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:visibility=\"invisible\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/buttonCancel\"\n        app:layout_constraintEnd_toStartOf=\"@+id/buttonCancel\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/buttonCancel\" />\n\n    <Button\n        android:id=\"@+id/buttonCancel\"\n        style=\"@style/Base.Widget.AppCompat.Button.Borderless\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/cancel\"\n        android:textColor=\"@color/greySecondary\"\n        android:theme=\"@style/ThemeOverlay.FilterButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/buttonSearch\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toEndOf=\"@+id/view2\"\n        app:layout_constraintTop_toTopOf=\"@+id/buttonSearch\" />\n\n\n    <Button\n        android:id=\"@+id/buttonSearch\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:text=\"@string/apply\"\n        android:theme=\"@style/ThemeOverlay.FilterButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toEndOf=\"@+id/buttonCancel\"\n        app:layout_constraintTop_toBottomOf=\"@+id/spinnerSort\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n\n"
  },
  {
    "path": "firestore/app/src/main/res/layout/dialog_rating.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:id=\"@+id/textView\"\n        style=\"@style/AppTheme.Title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/filter_add_review\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <me.zhanghai.android.materialratingbar.MaterialRatingBar\n        android:id=\"@+id/restaurantFormRating\"\n        style=\"@style/Widget.MaterialRatingBar.RatingBar\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:maxHeight=\"24dp\"\n        android:minHeight=\"24dp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/textView\" />\n\n    <EditText\n        android:id=\"@+id/restaurantFormText\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_horizontal\"\n        android:layout_marginTop=\"8dp\"\n        android:hint=\"@string/hint_review\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/restaurantFormRating\" />\n\n\n    <View\n        android:id=\"@+id/view3\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:visibility=\"invisible\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/restaurantFormCancel\"\n        app:layout_constraintEnd_toStartOf=\"@+id/restaurantFormCancel\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/restaurantFormCancel\" />\n\n    <Button\n        android:id=\"@+id/restaurantFormCancel\"\n        style=\"@style/Base.Widget.AppCompat.Button.Borderless\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/cancel\"\n        android:textColor=\"@color/greySecondary\"\n        android:theme=\"@style/ThemeOverlay.FilterButton\"\n        app:layout_constraintEnd_toStartOf=\"@+id/restaurantFormButton\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toEndOf=\"@+id/view3\"\n        app:layout_constraintTop_toTopOf=\"@+id/restaurantFormButton\" />\n\n\n    <Button\n        android:id=\"@+id/restaurantFormButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/submit\"\n        android:theme=\"@style/ThemeOverlay.FilterButton\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toEndOf=\"@+id/restaurantFormCancel\"\n        app:layout_constraintTop_toBottomOf=\"@+id/restaurantFormText\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "firestore/app/src/main/res/layout/fragment_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#E0E0E0\">\n\n    <FrameLayout\n        android:id=\"@+id/filterBarContainer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/colorPrimary\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        android:paddingBottom=\"12dp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <androidx.cardview.widget.CardView\n            android:id=\"@+id/filterBar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:clickable=\"true\"\n            android:elevation=\"12dp\"\n            android:foreground=\"?attr/selectableItemBackground\">\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"8dp\"\n                android:paddingBottom=\"8dp\">\n\n                <ImageView\n                    android:id=\"@+id/buttonFilter\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_marginLeft=\"8dp\"\n                    android:layout_marginRight=\"8dp\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    app:srcCompat=\"@drawable/ic_filter_list_white_24px\"\n                    app:tint=\"@color/greySecondary\" />\n\n                <TextView\n                    android:id=\"@+id/textCurrentSearch\"\n                    style=\"@style/AppTheme.Body1\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"16dp\"\n                    android:layout_marginLeft=\"16dp\"\n                    android:text=\"@string/all_restaurants\"\n                    android:textColor=\"@color/greySecondary\"\n                    app:layout_constraintStart_toEndOf=\"@+id/buttonFilter\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    tools:text=\"Filter\" />\n\n                <TextView\n                    android:id=\"@+id/textCurrentSortBy\"\n                    style=\"@style/AppTheme.Caption\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/sorted_by_rating\"\n                    android:textColor=\"@color/greyDisabled\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"@+id/textCurrentSearch\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/textCurrentSearch\" />\n\n                <ImageView\n                    android:id=\"@+id/buttonClearFilter\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_marginEnd=\"8dp\"\n                    android:layout_marginRight=\"8dp\"\n                    android:padding=\"8dp\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    app:srcCompat=\"@drawable/ic_close_white_24px\"\n                    app:tint=\"@color/greySecondary\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </androidx.cardview.widget.CardView>\n\n    </FrameLayout>\n\n    <!-- Main Restaurants recycler -->\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recyclerRestaurants\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"@android:color/white\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/filterBarContainer\"\n        tools:listitem=\"@layout/item_restaurant\" />\n\n    <!-- Shadow below toolbar -->\n    <View\n        android:id=\"@+id/view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"4dp\"\n        android:background=\"@drawable/bg_shadow\"\n        app:layout_constraintTop_toBottomOf=\"@+id/filterBarContainer\"\n        />\n\n    <!-- Empty list (pizza guy) view -->\n    <LinearLayout\n        android:id=\"@+id/viewEmpty\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"parent\"\n        tools:ignore=\"UseCompoundDrawables\"\n        tools:visibility=\"gone\">\n\n        <ImageView\n            style=\"@style/AppTheme.PizzaGuy\"\n            android:src=\"@drawable/pizza_monster\" />\n\n        <TextView\n            style=\"@style/AppTheme.Body1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/message_no_results\"\n            android:textColor=\"@color/greyDisabled\" />\n\n    </LinearLayout>\n\n    <ProgressBar\n        android:id=\"@+id/progressLoading\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerHorizontal=\"true\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/recyclerRestaurants\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/recyclerRestaurants\"\n        />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "firestore/app/src/main/res/layout/fragment_restaurant_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#E0E0E0\"\n    tools:ignore=\"ContentDescription\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/restaurant_top_card\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:elevation=\"4dp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <ImageView\n            android:id=\"@+id/restaurantImage\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"200dp\"\n            android:alpha=\"1.0\"\n            android:scaleType=\"centerCrop\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            tools:src=\"@drawable/food_1\" />\n\n        <View\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"200dp\"\n            android:background=\"@drawable/gradient_up\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/restaurantImage\"\n            />\n\n        <!-- Back button -->\n        <ImageView\n            android:id=\"@+id/restaurantButtonBack\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:background=\"?attr/selectableItemBackground\"\n            app:srcCompat=\"@drawable/ic_close_white_24px\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"@+id/restaurantName\"\n            />\n\n        <TextView\n            android:id=\"@+id/restaurantName\"\n            style=\"@style/AppTheme.Title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginStart=\"8dp\"\n            android:textColor=\"@android:color/white\"\n            android:textStyle=\"bold\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintBottom_toTopOf=\"@+id/restaurantRating\"\n            tools:text=\"Some Restaurant\"\n            />\n\n        <me.zhanghai.android.materialratingbar.MaterialRatingBar\n            android:id=\"@+id/restaurantRating\"\n            style=\"@style/Widget.MaterialRatingBar.RatingBar.Indicator\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_above=\"@+id/restaurantCategory\"\n            app:mrb_progressTint=\"@android:color/white\"\n            app:mrb_secondaryProgressTint=\"@android:color/white\"\n            app:layout_constraintBottom_toTopOf=\"@+id/restaurantCategory\"\n            app:layout_constraintStart_toStartOf=\"@+id/restaurantName\"\n            />\n\n        <TextView\n            android:id=\"@+id/restaurantNumRatings\"\n            style=\"@style/AppTheme.Body1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"4dp\"\n            android:layout_marginStart=\"4dp\"\n            android:textColor=\"@android:color/white\"\n            app:layout_constraintTop_toTopOf=\"@+id/restaurantRating\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/restaurantRating\"\n            app:layout_constraintStart_toEndOf=\"@+id/restaurantRating\"\n            tools:text=\"(10)\" />\n\n        <TextView\n            android:id=\"@+id/restaurantCategory\"\n            style=\"@style/AppTheme.Subheader\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:textColor=\"@android:color/white\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            tools:text=\"Italian\" />\n\n        <TextView\n            android:id=\"@+id/restaurantCity_divider\"\n            style=\"@style/AppTheme.TextDivider\"\n            android:text=\"@string/divider_bullet\"\n            android:textColor=\"@android:color/white\"\n            app:layout_constraintTop_toTopOf=\"@+id/restaurantCategory\"\n            app:layout_constraintStart_toEndOf=\"@+id/restaurantCategory\"\n            />\n\n        <TextView\n            android:id=\"@+id/restaurantCity\"\n            style=\"@style/AppTheme.Subheader\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textColor=\"@android:color/white\"\n            tools:text=\"San Francisco\"\n            app:layout_constraintTop_toTopOf=\"@+id/restaurantCategory\"\n            app:layout_constraintStart_toEndOf=\"@+id/restaurantCategory\"\n            />\n\n        <TextView\n            android:id=\"@+id/restaurantPrice\"\n            style=\"@style/AppTheme.Body1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:textColor=\"@android:color/white\"\n            android:textStyle=\"bold\"\n            app:layout_constraintTop_toTopOf=\"@+id/restaurantName\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            tools:text=\"$$$\"\n            />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fabShowRatingDialog\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:translationY=\"-28dp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/restaurant_top_card\"\n        app:srcCompat=\"@drawable/ic_add_white_24px\" />\n\n    <!-- Ratings -->\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recyclerRatings\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"@android:color/transparent\"\n        android:clipToPadding=\"false\"\n        android:paddingTop=\"28dp\"\n        android:paddingBottom=\"16dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/restaurant_top_card\"\n        tools:listitem=\"@layout/item_rating\"\n        tools:visibility=\"invisible\" />\n\n    <!-- View for empty ratings -->\n    <LinearLayout\n        android:id=\"@+id/viewEmptyRatings\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerHorizontal=\"true\"\n        android:orientation=\"vertical\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/restaurant_top_card\"\n        tools:visibility=\"visible\"\n        tools:ignore=\"UseCompoundDrawables\">\n\n        <ImageView\n            style=\"@style/AppTheme.PizzaGuy\"\n            android:src=\"@drawable/pizza_monster\"\n            tools:ignore=\"ContentDescription\" />\n\n        <TextView\n            style=\"@style/AppTheme.Body1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/message_no_reviews\"\n            android:textColor=\"@color/greyDisabled\" />\n\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "firestore/app/src/main/res/layout/item_rating.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingTop=\"16dp\"\n    android:paddingLeft=\"16dp\"\n    android:paddingRight=\"16dp\">\n\n    <TextView\n        android:id=\"@+id/ratingItemName\"\n        style=\"@style/AppTheme.Body1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"end\"\n        android:maxWidth=\"120dp\"\n        android:maxLines=\"1\"\n        android:textColor=\"@color/greySecondary\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"John Doe\" />\n\n    <TextView\n        android:id=\"@+id/ratingItemDivider\"\n        style=\"@style/AppTheme.TextDivider\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"4dp\"\n        android:layout_marginLeft=\"4dp\"\n        android:text=\"@string/divider_bullet\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/ratingItemName\"\n        app:layout_constraintStart_toEndOf=\"@+id/ratingItemName\"\n        app:layout_constraintTop_toTopOf=\"@+id/ratingItemName\" />\n\n    <TextView\n        android:id=\"@+id/ratingItemDate\"\n        style=\"@style/AppTheme.Body1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"4dp\"\n        android:layout_marginLeft=\"4dp\"\n        android:textColor=\"@color/greySecondary\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/ratingItemName\"\n        app:layout_constraintStart_toEndOf=\"@+id/ratingItemDivider\"\n        app:layout_constraintTop_toTopOf=\"@+id/ratingItemName\"\n        tools:text=\"9/27/2017\" />\n\n    <me.zhanghai.android.materialratingbar.MaterialRatingBar\n        android:id=\"@+id/ratingItemRating\"\n        style=\"@style/Widget.MaterialRatingBar.RatingBar.Indicator.Small\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/ratingItemText\"\n        style=\"@style/AppTheme.Subheader\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"4dp\"\n        android:textColor=\"@color/greyPrimary\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/ratingItemName\"\n        tools:text=\"I thought it was pretty great! And I really have a ton to say wow.\" />\n\n    <View\n        android:id=\"@+id/view4\"\n        style=\"@style/AppTheme.Divider\"\n        android:layout_marginTop=\"16dp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/ratingItemText\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "firestore/app/src/main/res/layout/item_restaurant.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:orientation=\"vertical\"\n    android:padding=\"8dp\">\n\n    <ImageView\n        android:id=\"@+id/restaurantItemImage\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\"\n        android:background=\"#757575\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@drawable/food_1\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/restaurantItemName\"\n        style=\"@style/AppTheme.Subheader\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:ellipsize=\"end\"\n        android:maxLines=\"1\"\n        app:layout_constraintStart_toEndOf=\"@+id/restaurantItemImage\"\n        app:layout_constraintTop_toTopOf=\"@+id/restaurantItemImage\"\n        tools:text=\"Foo's Bar\" />\n\n    <me.zhanghai.android.materialratingbar.MaterialRatingBar\n        android:id=\"@+id/restaurantItemRating\"\n        style=\"@style/Widget.MaterialRatingBar.RatingBar.Indicator.Small\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"4dp\"\n        app:layout_constraintStart_toStartOf=\"@+id/restaurantItemName\"\n        app:layout_constraintTop_toBottomOf=\"@+id/restaurantItemName\" />\n\n    <TextView\n        android:id=\"@+id/restaurantItemNumRatings\"\n        style=\"@style/AppTheme.Caption\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"4dp\"\n        android:layout_marginLeft=\"4dp\"\n        android:textColor=\"@color/greyDisabled\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/restaurantItemRating\"\n        app:layout_constraintStart_toEndOf=\"@+id/restaurantItemRating\"\n        app:layout_constraintTop_toTopOf=\"@+id/restaurantItemRating\"\n        tools:text=\"(10)\" />\n\n    <TextView\n        android:id=\"@+id/restaurantItemCategory\"\n        style=\"@style/AppTheme.Body1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"4dp\"\n        android:textColor=\"@color/greySecondary\"\n        app:layout_constraintStart_toStartOf=\"@+id/restaurantItemRating\"\n        app:layout_constraintTop_toBottomOf=\"@+id/restaurantItemRating\"\n        tools:text=\"Italian\" />\n\n    <TextView\n        android:id=\"@+id/restaurantItemCityDivider\"\n        style=\"@style/AppTheme.TextDivider\"\n        android:layout_marginStart=\"4dp\"\n        android:layout_marginLeft=\"4dp\"\n        android:text=\"@string/divider_bullet\"\n        app:layout_constraintStart_toEndOf=\"@+id/restaurantItemCategory\"\n        app:layout_constraintTop_toTopOf=\"@+id/restaurantItemCategory\" />\n\n    <TextView\n        android:id=\"@+id/restaurantItemCity\"\n        style=\"@style/AppTheme.Body1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"4dp\"\n        android:layout_marginLeft=\"4dp\"\n        android:textColor=\"@color/greySecondary\"\n        app:layout_constraintStart_toEndOf=\"@+id/restaurantItemCityDivider\"\n        app:layout_constraintTop_toTopOf=\"@+id/restaurantItemCategory\"\n        tools:text=\"San Francisco\" />\n\n    <TextView\n        android:id=\"@+id/restaurantItemPrice\"\n        style=\"@style/AppTheme.Caption\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/greySecondary\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/restaurantItemName\"\n        tools:text=\"$$$\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "firestore/app/src/main/res/menu/menu_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_sign_out\"\n        android:title=\"@string/sign_out\" />\n\n    <item\n        android:id=\"@+id/menu_add_items\"\n        android:title=\"@string/add_random_items\" />\n\n</menu>\n"
  },
  {
    "path": "firestore/app/src/main/res/navigation/nav_graph_java.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/nav_graph_java\"\n    app:startDestination=\"@id/MainFragment\">\n\n    <fragment\n        android:id=\"@+id/MainFragment\"\n        android:name=\"com.google.firebase.example.fireeats.java.MainFragment\"\n        tools:layout=\"@layout/fragment_main\" >\n        <action\n            android:id=\"@+id/action_MainFragment_to_RestaurantDetailFragment\"\n            app:destination=\"@id/RestaurantDetailFragment\"\n            app:enterAnim=\"@anim/slide_in_from_right\"\n            app:exitAnim=\"@anim/slide_out_to_left\"\n            app:popEnterAnim=\"@anim/slide_in_from_left\"\n            app:popExitAnim=\"@anim/slide_out_to_right\" />\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/RestaurantDetailFragment\"\n        android:name=\"com.google.firebase.example.fireeats.java.RestaurantDetailFragment\"\n        tools:layout=\"@layout/fragment_restaurant_detail\" >\n        <argument\n            android:name=\"key_restaurant_id\"\n            app:argType=\"string\" />\n    </fragment>\n\n</navigation>"
  },
  {
    "path": "firestore/app/src/main/res/navigation/nav_graph_kotlin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/nav_graph_kotlin\"\n    app:startDestination=\"@id/MainFragment\">\n\n    <fragment\n        android:id=\"@+id/MainFragment\"\n        android:name=\"com.google.firebase.example.fireeats.kotlin.MainFragment\"\n        tools:layout=\"@layout/fragment_main\" >\n        <action\n            android:id=\"@+id/action_MainFragment_to_RestaurantDetailFragment\"\n            app:destination=\"@id/RestaurantDetailFragment\"\n            app:enterAnim=\"@anim/slide_in_from_right\"\n            app:exitAnim=\"@anim/slide_out_to_left\"\n            app:popEnterAnim=\"@anim/slide_in_from_left\"\n            app:popExitAnim=\"@anim/slide_out_to_right\" />\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/RestaurantDetailFragment\"\n        android:name=\"com.google.firebase.example.fireeats.kotlin.RestaurantDetailFragment\"\n        tools:layout=\"@layout/fragment_restaurant_detail\" >\n        <argument\n            android:name=\"key_restaurant_id\"\n            app:argType=\"string\" />\n    </fragment>\n\n</navigation>\n"
  },
  {
    "path": "firestore/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#4285F4</color>\n    <color name=\"colorPrimaryDark\">#3367D6</color>\n    <color name=\"colorAccent\">#F4B400</color>\n\n    <color name=\"greyPrimary\">#DE000000</color>\n    <color name=\"greySecondary\">#8B000000</color>\n    <color name=\"greyDisabled\">#61000000</color>\n</resources>\n"
  },
  {
    "path": "firestore/app/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n</resources>\n"
  },
  {
    "path": "firestore/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Friendly Eats</string>\n\n    <string name=\"value_any_category\">All food</string>\n    <string name=\"value_any_city\">Anywhere</string>\n    <string name=\"value_any_price\">Any price</string>\n\n    <string name=\"price_1\">$</string>\n    <string name=\"price_2\">$$</string>\n    <string name=\"price_3\">$$$</string>\n\n    <string name=\"label_category\">Category</string>\n    <string name=\"label_city\">City</string>\n    <string name=\"label_price\">Price</string>\n    <string name=\"label_sort_by\">Sort By</string>\n\n    <string name=\"fmt_num_ratings\" translatable=\"false\">(%d)</string>\n    <string name=\"all_restaurants\">All Restaurants</string>\n    <string name=\"header_filters\">Filter</string>\n    <string name=\"search\">Search</string>\n\n    <string name=\"sort_by_rating\">Sort by Rating</string>\n    <string name=\"sort_by_popularity\">Sort by Popularity</string>\n    <string name=\"sort_by_price\">Sort by Price</string>\n\n    <string name=\"sorted_by_rating\">sorted by rating</string>\n    <string name=\"sorted_by_price\">sorted by price</string>\n    <string name=\"sorted_by_popularity\">sorted by popularity</string>\n\n    <string name=\"add_random_items\">Add Random Items</string>\n    <string name=\"sign_out\">Sign Out</string>\n    <string name=\"divider_bullet\">•</string>\n    <string name=\"apply\">Apply</string>\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"message_no_results\">Oops, couldn\\'t find any results\\nthat matched your filter</string>\n    <string name=\"message_no_reviews\">Be the first to leave a review!</string>\n    <string name=\"hint_review\">How was your experience?</string>\n    <string name=\"submit\">Submit</string>\n    <string name=\"filter_add_review\">Add review</string>\n\n    <string name=\"title_sign_in_error\">Sign In Error</string>\n    <string name=\"message_no_network\">No network connection.</string>\n    <string name=\"message_unknown\">Unknown error.</string>\n    <string name=\"option_retry\">Retry</string>\n    <string name=\"option_exit\">Exit</string>\n\n    <!-- Types of food I could think of in 5 minutes -->\n    <string-array name=\"categories\">\n        <item>@string/value_any_category</item>\n\n        <item>Brunch</item>\n        <item>Burgers</item>\n        <item>Coffee</item>\n        <item>Deli</item>\n        <item>Dim Sum</item>\n        <item>Indian</item>\n        <item>Italian</item>\n        <item>Mediterranean</item>\n        <item>Mexican</item>\n        <item>Pizza</item>\n        <item>Ramen</item>\n        <item>Sushi</item>\n    </string-array>\n\n    <!-- 50 most populous cities in the US -->\n    <string-array name=\"cities\">\n        <item>@string/value_any_city</item>\n\n        <item>Albuquerque</item>\n        <item>Arlington</item>\n        <item>Atlanta</item>\n        <item>Austin</item>\n        <item>Baltimore</item>\n        <item>Boston</item>\n        <item>Charlotte</item>\n        <item>Chicago</item>\n        <item>Cleveland</item>\n        <item>Colorado Springs</item>\n        <item>Columbus</item>\n        <item>Dallas</item>\n        <item>Denver</item>\n        <item>Detroit</item>\n        <item>El Paso</item>\n        <item>Fort Worth</item>\n        <item>Fresno</item>\n        <item>Houston</item>\n        <item>Indianapolis</item>\n        <item>Jacksonville</item>\n        <item>Kansas City</item>\n        <item>Las Vegas</item>\n        <item>Long Beach</item>\n        <item>Los Angeles</item>\n        <item>Louisville</item>\n        <item>Memphis</item>\n        <item>Mesa</item>\n        <item>Miami</item>\n        <item>Milwaukee</item>\n        <item>Nashville</item>\n        <item>New York</item>\n        <item>Oakland</item>\n        <item>Oklahoma</item>\n        <item>Omaha</item>\n        <item>Philadelphia</item>\n        <item>Phoenix</item>\n        <item>Portland</item>\n        <item>Raleigh</item>\n        <item>Sacramento</item>\n        <item>San Antonio</item>\n        <item>San Diego</item>\n        <item>San Francisco</item>\n        <item>San Jose</item>\n        <item>Tucson</item>\n        <item>Tulsa</item>\n        <item>Virginia Beach</item>\n        <item>Washington</item>\n    </string-array>\n\n    <!-- Price filters -->\n    <string-array name=\"prices\">\n        <item>@string/value_any_price</item>\n\n        <item>@string/price_1</item>\n        <item>@string/price_2</item>\n        <item>@string/price_3</item>\n    </string-array>\n\n    <!-- Ways you can sort restaurants -->\n    <string-array name=\"sort_by\">\n        <item>@string/sort_by_rating</item>\n        <item>@string/sort_by_popularity</item>\n        <item>@string/sort_by_price</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "firestore/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <style name=\"AppTheme.EntryChoice\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.NoActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n\n        <item name=\"android:textColorPrimary\">@color/greyPrimary</item>\n        <item name=\"android:textColorSecondary\">@color/greySecondary</item>\n\n        <item name=\"android:actionOverflowButtonStyle\">@style/AppTheme.Overflow</item>\n    </style>\n\n    <style name=\"AppTheme.Activity\" parent=\"AppTheme\" />\n\n    <style name=\"AppTheme.Overflow\" parent=\"Base.Widget.AppCompat.ActionButton.Overflow\">\n        <item name=\"android:padding\">0dp</item>\n        <item name=\"tint\">@android:color/white</item>\n    </style>\n\n    <style name=\"AppTheme.FormLabel\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">18dp</item>\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"android:layout_marginBottom\">4dp</item>\n        <item name=\"android:layout_marginTop\">8dp</item>\n    </style>\n\n    <style name=\"AppTheme.Divider\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">1dp</item>\n        <item name=\"android:layout_marginBottom\">8dp</item>\n        <item name=\"android:layout_marginTop\">4dp</item>\n        <item name=\"android:background\">#BDBDBD</item>\n    </style>\n\n    <style name=\"AppTheme.FilterIcon\">\n        <item name=\"android:layout_width\">30dp</item>\n        <item name=\"android:layout_height\">30dp</item>\n        <item name=\"android:layout_marginRight\">8dp</item>\n        <item name=\"android:layout_marginLeft\">8dp</item>\n    </style>\n\n    <style name=\"AppTheme.FilterSpinner\" parent=\"Base.Widget.AppCompat.Spinner.Underlined\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:paddingBottom\">16dp</item>\n        <item name=\"android:layout_marginBottom\">4dp</item>\n    </style>\n\n    <style name=\"ThemeOverlay.FilterButton\" parent=\"ThemeOverlay.AppCompat.Dark\">\n        <item name=\"colorButtonNormal\">@color/colorPrimary</item>\n    </style>\n\n    <style name=\"AppTheme.Headline\">\n        <item name=\"android:fontFamily\">sans-serif</item>\n        <item name=\"android:textSize\">24sp</item>\n    </style>\n\n    <style name=\"AppTheme.Title\">\n        <item name=\"android:fontFamily\">sans-serif-medium</item>\n        <item name=\"android:textSize\">20sp</item>\n    </style>\n\n    <style name=\"AppTheme.Subheader\">\n        <item name=\"android:fontFamily\">sans-serif</item>\n        <item name=\"android:textSize\">16sp</item>\n    </style>\n\n    <style name=\"AppTheme.Body1\">\n        <item name=\"android:fontFamily\">sans-serif</item>\n        <item name=\"android:textSize\">14sp</item>\n    </style>\n\n    <style name=\"AppTheme.Body2\">\n        <item name=\"android:fontFamily\">sans-serif-medium</item>\n        <item name=\"android:textSize\">14sp</item>\n    </style>\n\n    <style name=\"AppTheme.Caption\">\n        <item name=\"android:fontFamily\">sans-serif</item>\n        <item name=\"android:textSize\">12sp</item>\n    </style>\n\n    <style name=\"AppTheme.TextDivider\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_marginLeft\">4dp</item>\n        <item name=\"android:layout_marginRight\">4dp</item>\n        <item name=\"android:textColor\">@color/greySecondary</item>\n    </style>\n\n    <style name=\"AppTheme.PizzaGuy\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">150dp</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "firestore/app/src/main/res/values-v21/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"AppTheme.Activity\" parent=\"AppTheme\">\n        <item name=\"android:statusBarColor\">@color/colorPrimary</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "firestore/app/test-proguard-rules.pro",
    "content": "-include proguard-rules.pro\n-keepattributes SourceFile,LineNumberTable\n-dontwarn org.xmlpull.v1.**\n-dontnote org.xmlpull.v1.**\n-keep class org.xmlpull.** { *; }\n-keepclassmembers class org.xmlpull.** { *; }\n-keep class com.google.firebase.auth.** {*;}"
  },
  {
    "path": "firestore/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n    alias(libs.plugins.navigation.safeargs) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "firestore/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "firestore/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\n# Don't use the gradle build cache since this sample uses experimental SDKs\nandroid.enableBuildCache=false\nandroid.useAndroidX=true"
  },
  {
    "path": "firestore/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "firestore/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "firestore/indexes.json",
    "content": "{\n  \"indexes\": [\n    {\n      \"collectionId\": \"restaurants\",\n      \"fields\": [\n        { \"fieldPath\": \"city\", \"mode\": \"ASCENDING\" },\n        { \"fieldPath\": \"avgRating\", \"mode\": \"DESCENDING\" }\n      ]\n    },\n    {\n      \"collectionId\": \"restaurants\",\n      \"fields\": [\n        { \"fieldPath\": \"category\", \"mode\": \"ASCENDING\" },\n        { \"fieldPath\": \"avgRating\", \"mode\": \"DESCENDING\" }\n      ]\n    },\n    {\n      \"collectionId\": \"restaurants\",\n      \"fields\": [\n        { \"fieldPath\": \"price\", \"mode\": \"ASCENDING\" },\n        { \"fieldPath\": \"avgRating\", \"mode\": \"DESCENDING\" }\n      ]\n    },\n    {\n      \"collectionId\": \"restaurants\",\n      \"fields\": [\n        { \"fieldPath\": \"city\", \"mode\": \"ASCENDING\" },\n        { \"fieldPath\": \"numRatings\", \"mode\": \"DESCENDING\" }\n      ]\n    },\n    {\n      \"collectionId\": \"restaurants\",\n      \"fields\": [\n        { \"fieldPath\": \"category\", \"mode\": \"ASCENDING\" },\n        { \"fieldPath\": \"numRatings\", \"mode\": \"DESCENDING\" }\n      ]\n    },\n    {\n      \"collectionId\": \"restaurants\",\n      \"fields\": [\n        { \"fieldPath\": \"price\", \"mode\": \"ASCENDING\" },\n        { \"fieldPath\": \"numRatings\", \"mode\": \"DESCENDING\" }\n      ]\n    },\n    {\n      \"collectionId\": \"restaurants\",\n      \"fields\": [\n        { \"fieldPath\": \"city\", \"mode\": \"ASCENDING\" },\n        { \"fieldPath\": \"price\", \"mode\": \"ASCENDING\" }\n      ]\n    },\n    {\n      \"collectionId\": \"restaurants\",\n      \"fields\": [\n        { \"fieldPath\": \"category\", \"mode\": \"ASCENDING\" },\n        { \"fieldPath\": \"price\", \"mode\": \"ASCENDING\" }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "firestore/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "firestore/test_setup.sh",
    "content": "#!/bin/bash\nset -o nounset\nset -e\n\n#delete the restaurants collection\necho \"Deleting the restaurants collection under project\"\nfirebase firestore:delete \"restaurants\" -r -y --project=\"$PROJECT_ID\"\n\n#create a test account test@mailinator.com\necho \"Creating test accounts\"\nfirebase auth:import accounts.json --hash-algo=SHA256 --rounds=1 --project=\"$PROJECT_ID\"\n"
  },
  {
    "path": "functions/.gitignore",
    "content": "/build\n.firebaserc\n\n*-debug.log\n"
  },
  {
    "path": "functions/README.md",
    "content": "Firebase Functions Quickstart\n=============================\n\nIntroduction\n------------\n\nThis quickstart demontstrates **Callable Functions** which are HTTPS Cloud Functions\nthat can be invoked directly from your mobile application.\n\n- [Read more about callable functions](https://firebase.google.com/docs/functions/callable)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n- Deploy the provided cloud functions:\n\n  ```bash\n  # Move to the `functions` subdirectory of quickstart-android\n  cd functions\n\n  # Install all of the dependencies of the cloud functions\n  cd functions\n  npm install\n  cd ../\n\n  # Deploy functions to your Firebase project\n  firebase --project=YOUR_PROJECT_ID deploy --only functions\n  ```\n\n- Run the sample on Android device or emulator.\n\nScreenshots\n-----------\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/google-cloud-functions)\n- [Firebase Support](https://firebase.google.com/support/)\n\nLicense\n-------\n\nCopyright 2018 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "functions/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "functions/app/build.gradle.kts",
    "content": "\nplugins {\n    id(\"com.android.application\")\n    id(\"com.google.gms.google-services\")\n}\n\nandroid {\n    namespace = \"com.google.samples.quickstart.functions\"\n    // Changes the test build type for instrumented tests to \"stage\".\n    testBuildType = \"release\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.samples.quickstart.functions\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            testProguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"test-proguard-rules.pro\")\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n            signingConfig = signingConfigs.getByName(\"debug\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    implementation(\"androidx.activity:activity-ktx:1.12.1\")\n    implementation(\"androidx.fragment:fragment-ktx:1.8.9\")\n    implementation(\"androidx.appcompat:appcompat:1.7.1\")\n    implementation(\"com.google.android.material:material:1.13.0\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Cloud Functions for Firebase\n    implementation(\"com.google.firebase:firebase-functions\")\n\n    // Firebase Authentication\n    implementation(\"com.google.firebase:firebase-auth\")\n\n    // Firebase Cloud Messaging\n    implementation(\"com.google.firebase:firebase-messaging\")\n\n    // Firebase UI\n    implementation(\"com.firebaseui:firebase-ui-auth:9.1.1\")\n    \n    // Google Play services\n    implementation(\"com.google.android.gms:play-services-auth:21.2.0\")\n\n    testImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test.uiautomator:uiautomator:2.3.0\")\n}\n"
  },
  {
    "path": "functions/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n\n# https://github.com/firebase/FirebaseUI-Android/issues/1227\n-dontwarn com.firebase.ui.auth.data.remote.**\n"
  },
  {
    "path": "functions/app/src/androidTest/AndroidManifest.xml",
    "content": "<manifest xmlns:tools=\"http://schemas.android.com/tools\">\n\n  <uses-sdk tools:overrideLibrary=\"android.support.test.uiautomator.v18\"/>\n\n</manifest>\n"
  },
  {
    "path": "functions/app/src/androidTest/java/com/google/samples/quickstart/functions/MainActivityTest.java",
    "content": "package com.google.samples.quickstart.functions;\n\n\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.runner.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport com.google.samples.quickstart.functions.java.MainActivity;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertTrue;\n\n@LargeTest\n@RunWith(AndroidJUnit4.class)\npublic class MainActivityTest {\n\n    @Rule\n    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);\n\n    @Test\n    public void mainActivityTest() {\n        assertTrue(1 + 1 == 2);\n    }\n}\n"
  },
  {
    "path": "functions/app/src/androidTest/java/com/google/samples/quickstart/functions/TestAddNumber.java",
    "content": "package com.google.samples.quickstart.functions;\n\nimport androidx.test.espresso.ViewInteraction;\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.runner.AndroidJUnit4;\nimport androidx.test.uiautomator.UiDevice;\nimport androidx.test.uiautomator.UiObject;\nimport androidx.test.uiautomator.UiSelector;\nimport androidx.test.filters.LargeTest;\n\nimport com.google.samples.quickstart.functions.java.MainActivity;\n\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static androidx.test.InstrumentationRegistry.getInstrumentation;\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.action.ViewActions.replaceText;\nimport static androidx.test.espresso.action.ViewActions.scrollTo;\nimport static androidx.test.espresso.assertion.ViewAssertions.matches;\nimport static androidx.test.espresso.matcher.ViewMatchers.withId;\nimport static androidx.test.espresso.matcher.ViewMatchers.withText;\n\n@LargeTest @RunWith(AndroidJUnit4.class)\npublic class TestAddNumber {\n  @Rule public ActivityTestRule<MainActivity> mActivityTestRule =\n      new ActivityTestRule<>(MainActivity.class);\n\n  @Before public void setUp() {\n    UiDevice.getInstance(getInstrumentation());\n  }\n\n  @Test\n  public void testAddNumber() {\n    ViewInteraction firstNumber = onView(withId(R.id.fieldFirstNumber));\n    ViewInteraction secondNumber = onView(withId(R.id.fieldSecondNumber));\n    ViewInteraction addButton = onView(withId(R.id.buttonCalculate));\n\n    firstNumber.perform(replaceText(\"32\"));\n    secondNumber.perform(replaceText(\"16\"));\n\n    addButton.perform(scrollTo(), click());\n\n    Assert.assertTrue(\n        new UiObject(new UiSelector()\n          .resourceId(\"com.google.samples.quickstart.functions:id/fieldAddResult\").text(\"48\"))\n        .waitForExists(60000));\n  }\n}\n"
  },
  {
    "path": "functions/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\nCopyright Google Inc. All rights reserved.\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n      http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\"\n        android:networkSecurityConfig=\"@xml/network_security_config\">\n\n\n        <activity android:name=\".java.MainActivity\"/>\n        <activity android:name=\".kotlin.MainActivity\"/>\n\n        <activity android:name=\".EntryChoiceActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <service\n            android:name=\".java.FunctionsMessagingService\"\n            android:enabled=\"true\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n            </intent-filter>\n        </service>\n\n        <service\n            android:name=\".kotlin.FunctionsMessagingService\"\n            android:enabled=\"true\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n            </intent-filter>\n        </service>\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "functions/app/src/main/java/com/google/samples/quickstart/functions/EntryChoiceActivity.kt",
    "content": "package com.google.samples.quickstart.functions\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Functions quickstart written in Java.\",\n                Intent(this, com.google.samples.quickstart.functions.java.MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Functions quickstart written in Kotlin.\",\n                Intent(\n                    this,\n                    com.google.samples.quickstart.functions.kotlin.MainActivity::class.java,\n                ),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "functions/app/src/main/java/com/google/samples/quickstart/functions/java/FunctionsMessagingService.java",
    "content": "package com.google.samples.quickstart.functions.java;\n\nimport android.Manifest;\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\n\nimport androidx.core.app.NotificationCompat;\nimport androidx.core.app.NotificationManagerCompat;\nimport androidx.core.content.ContextCompat;\n\nimport android.util.Log;\n\nimport com.google.firebase.messaging.FirebaseMessagingService;\nimport com.google.firebase.messaging.RemoteMessage;\nimport com.google.samples.quickstart.functions.R;\n\npublic class FunctionsMessagingService extends FirebaseMessagingService {\n    private static final String TAG = \"MessagingService\";\n\n    public FunctionsMessagingService() {\n    }\n\n    private void createNotificationChannel() {\n        // Create the NotificationChannel, but only on API 26+ because\n        // the NotificationChannel class is new and not in the support library\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            int importance = NotificationManager.IMPORTANCE_DEFAULT;\n            NotificationChannel channel = new NotificationChannel(\"Messages\", \"Messages\", importance);\n            channel.setDescription(\"All messages.\");\n            // Register the channel with the system; you can't change the importance\n            // or other notification behaviors after this\n            NotificationManager notificationManager = getSystemService(NotificationManager.class);\n            notificationManager.createNotificationChannel(channel);\n        }\n    }\n\n    @Override\n    public void onMessageReceived(RemoteMessage remoteMessage) {\n        createNotificationChannel();\n\n        // Check if message contains a data payload.\n        if (remoteMessage.getData().size() > 0) {\n            Log.d(TAG, \"Message data payload: \" + remoteMessage.getData());\n            // Check if permission to post notifications has been granted\n            if (ContextCompat.checkSelfPermission(this,\n                    Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {\n                NotificationManagerCompat manager = NotificationManagerCompat.from(this);\n                Notification notification = new NotificationCompat.Builder(this, \"Messages\")\n                        .setContentText(remoteMessage.getData().get(\"text\"))\n                        .setContentTitle(\"New message\")\n                        .setSmallIcon(R.drawable.ic_stat_notification)\n                        .build();\n                manager.notify(0, notification);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "functions/app/src/main/java/com/google/samples/quickstart/functions/java/MainActivity.java",
    "content": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.samples.quickstart.functions.java;\n\nimport android.Manifest;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport android.os.Bundle;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\n\nimport com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;\nimport com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult;\nimport com.google.android.material.snackbar.Snackbar;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.content.ContextCompat;\n\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.Toast;\n\nimport com.firebase.ui.auth.AuthUI;\nimport com.firebase.ui.auth.IdpResponse;\nimport com.google.android.gms.tasks.Continuation;\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.functions.FirebaseFunctions;\nimport com.google.firebase.functions.FirebaseFunctionsException;\nimport com.google.firebase.functions.HttpsCallableResult;\nimport com.google.samples.quickstart.functions.R;\nimport com.google.samples.quickstart.functions.databinding.ActivityMainBinding;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * This activity demonstrates the Android SDK for Callable Functions.\n * <p>\n * For more information, see the documentation for Cloud Functions for Firebase:\n * https://firebase.google.com/docs/functions/\n */\npublic class MainActivity extends AppCompatActivity implements View.OnClickListener {\n\n    private static final String TAG = \"MainActivity\";\n\n    private ActivityMainBinding binding;\n\n    // [START define_functions_instance]\n    private FirebaseFunctions mFunctions;\n    // [END define_functions_instance]\n\n    private final ActivityResultLauncher<String> requestPermissionLauncher =\n            registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {\n                if (isGranted) {\n                    Toast.makeText(this, \"Notifications permission granted\", Toast.LENGTH_SHORT)\n                            .show();\n                } else {\n                    Toast.makeText(this,\n                            \"FCM can't post notifications without POST_NOTIFICATIONS permission\",\n                            Toast.LENGTH_LONG).show();\n                }\n            });\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        binding.buttonCalculate.setOnClickListener(this);\n        binding.buttonAddMessage.setOnClickListener(this);\n        binding.buttonSignIn.setOnClickListener(this);\n\n        // [START initialize_functions_instance]\n        mFunctions = FirebaseFunctions.getInstance();\n        // [END initialize_functions_instance]\n\n        askNotificationPermission();\n    }\n\n    // [START function_add_numbers]\n    private Task<Integer> addNumbers(int a, int b) {\n        // Create the arguments to the callable function, which are two integers\n        Map<String, Object> data = new HashMap<>();\n        data.put(\"firstNumber\", a);\n        data.put(\"secondNumber\", b);\n\n        // Call the function and extract the operation from the result\n        return mFunctions\n                .getHttpsCallable(\"addNumbers\")\n                .call(data)\n                .continueWith(new Continuation<HttpsCallableResult, Integer>() {\n                    @Override\n                    public Integer then(@NonNull Task<HttpsCallableResult> task) throws Exception {\n                        // This continuation runs on either success or failure, but if the task\n                        // has failed then getResult() will throw an Exception which will be\n                        // propagated down.\n                        Map<String, Object> result = (Map<String, Object>) task.getResult().getData();\n                        return (Integer) result.get(\"operationResult\");\n                    }\n                });\n    }\n    // [END function_add_numbers]\n\n    // [START function_add_message]\n    private Task<String> addMessage(String text) {\n        // Create the arguments to the callable function.\n        Map<String, Object> data = new HashMap<>();\n        data.put(\"text\", text);\n        data.put(\"push\", true);\n\n        return mFunctions\n                .getHttpsCallable(\"addMessage\")\n                .call(data)\n                .continueWith(new Continuation<HttpsCallableResult, String>() {\n                    @Override\n                    public String then(@NonNull Task<HttpsCallableResult> task) throws Exception {\n                        // This continuation runs on either success or failure, but if the task\n                        // has failed then getResult() will throw an Exception which will be\n                        // propagated down.\n                        String result = (String) task.getResult().getData();\n                        return result;\n                    }\n                });\n    }\n    // [END function_add_message]\n\n    private void onCalculateClicked() {\n        int firstNumber;\n        int secondNumber;\n\n        hideKeyboard();\n\n        try {\n            firstNumber = Integer.parseInt(binding.fieldFirstNumber.getText().toString());\n            secondNumber = Integer.parseInt(binding.fieldSecondNumber.getText().toString());\n        } catch (NumberFormatException e) {\n            showSnackbar(\"Please enter two numbers.\");\n            return;\n        }\n\n        // [START call_add_numbers]\n        addNumbers(firstNumber, secondNumber)\n                .addOnCompleteListener(new OnCompleteListener<Integer>() {\n                    @Override\n                    public void onComplete(@NonNull Task<Integer> task) {\n                        if (!task.isSuccessful()) {\n                            Exception e = task.getException();\n                            if (e instanceof FirebaseFunctionsException) {\n                                FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;\n\n                                // Function error code, will be INTERNAL if the failure\n                                // was not handled properly in the function call.\n                                FirebaseFunctionsException.Code code = ffe.getCode();\n\n                                // Arbitrary error details passed back from the function,\n                                // usually a Map<String, Object>.\n                                Object details = ffe.getDetails();\n                            }\n\n                            // [START_EXCLUDE]\n                            Log.w(TAG, \"addNumbers:onFailure\", e);\n                            showSnackbar(\"An error occurred.\");\n                            return;\n                            // [END_EXCLUDE]\n                        }\n\n                        // [START_EXCLUDE]\n                        Integer result = task.getResult();\n                        binding.fieldAddResult.setText(String.valueOf(result));\n                        // [END_EXCLUDE]\n                    }\n                });\n        // [END call_add_numbers]\n    }\n\n    private void onAddMessageClicked() {\n        String inputMessage = binding.fieldMessageInput.getText().toString();\n\n        if (TextUtils.isEmpty(inputMessage)) {\n            showSnackbar(\"Please enter a message.\");\n            return;\n        }\n\n        // [START call_add_message]\n        addMessage(inputMessage)\n                .addOnCompleteListener(new OnCompleteListener<String>() {\n                    @Override\n                    public void onComplete(@NonNull Task<String> task) {\n                        if (!task.isSuccessful()) {\n                            Exception e = task.getException();\n                            if (e instanceof FirebaseFunctionsException) {\n                                FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;\n                                FirebaseFunctionsException.Code code = ffe.getCode();\n                                Object details = ffe.getDetails();\n                            }\n\n                            // [START_EXCLUDE]\n                            Log.w(TAG, \"addMessage:onFailure\", e);\n                            showSnackbar(\"An error occurred.\");\n                            return;\n                            // [END_EXCLUDE]\n                        }\n\n                        // [START_EXCLUDE]\n                        String result = task.getResult();\n                        binding.fieldMessageOutput.setText(result);\n                        // [END_EXCLUDE]\n                    }\n                });\n        // [END call_add_message]\n    }\n\n    private void onSignInClicked() {\n        if (FirebaseAuth.getInstance().getCurrentUser() != null) {\n            showSnackbar(\"Signed in.\");\n            return;\n        }\n\n        signIn();\n    }\n\n    private void showSnackbar(String message) {\n        Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show();\n    }\n\n    private void signIn() {\n        ActivityResultLauncher<Intent> signInLauncher =\n                registerForActivityResult(new FirebaseAuthUIActivityResultContract(), this::onSignInResult);\n\n        Intent signInIntent = AuthUI.getInstance()\n                .createSignInIntentBuilder()\n                .setAvailableProviders(Collections.singletonList(\n                        new AuthUI.IdpConfig.EmailBuilder().build()))\n                .setCredentialManagerEnabled(false)\n                .build();\n\n        signInLauncher.launch(signInIntent);\n    }\n\n    private void hideKeyboard() {\n        View view = this.getCurrentFocus();\n        if (view != null) {\n            InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);\n            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);\n        }\n    }\n\n    private void onSignInResult(FirebaseAuthUIAuthenticationResult result) {\n        if (result.getResultCode() == RESULT_OK) {\n            showSnackbar(\"Signed in.\");\n        } else {\n            showSnackbar(\"Error signing in.\");\n\n            IdpResponse response = result.getIdpResponse();\n            Log.w(TAG, \"signIn\", response.getError());\n        }\n    }\n\n    @Override\n    public void onClick(View view) {\n        //Due to bump in Java version, we can not use view ids in switch\n        //(see: http://tools.android.com/tips/non-constant-fields), so we\n        //need to use if/else:\n\n        int viewId = view.getId();\n        if (viewId == R.id.buttonCalculate) {\n            onCalculateClicked();\n        } else if (viewId == R.id.buttonAddMessage) {\n            onAddMessageClicked();\n        } else if (viewId == R.id.buttonSignIn) {\n            onSignInClicked();\n        }\n    }\n\n    private void askNotificationPermission() {\n        // This is only necessary for API level >= 33 (TIRAMISU)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==\n                    PackageManager.PERMISSION_GRANTED) {\n                // FCM SDK (and your app) can post notifications.\n            } else{\n                // Directly ask for the permission\n                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "functions/app/src/main/java/com/google/samples/quickstart/functions/kotlin/FunctionsMessagingService.kt",
    "content": "package com.google.samples.quickstart.functions.kotlin\n\nimport android.Manifest\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport android.util.Log\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.content.ContextCompat\nimport com.google.firebase.messaging.FirebaseMessagingService\nimport com.google.firebase.messaging.RemoteMessage\nimport com.google.samples.quickstart.functions.R\n\nclass FunctionsMessagingService : FirebaseMessagingService() {\n\n    private fun createNotificationChannel() {\n        // Create the NotificationChannel, but only on API 26+ because\n        // the NotificationChannel class is new and not in the support library\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            val importance = NotificationManager.IMPORTANCE_DEFAULT\n            val channel = NotificationChannel(\"Messages\", \"Messages\", importance)\n            channel.description = \"All messages.\"\n            // Register the channel with the system; you can't change the importance\n            // or other notification behaviors after this\n            val notificationManager = getSystemService(NotificationManager::class.java)\n            notificationManager?.createNotificationChannel(channel)\n        }\n    }\n\n    override fun onMessageReceived(remoteMessage: RemoteMessage) {\n        createNotificationChannel()\n\n        // Check if message contains a data payload.\n        if (remoteMessage.data.isNotEmpty()) {\n            Log.d(TAG, \"Message data payload: \" + remoteMessage.data)\n            // Check if permission to post notifications has been granted\n            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==\n                PackageManager.PERMISSION_GRANTED\n            ) {\n                val manager = NotificationManagerCompat.from(this)\n                val notification = NotificationCompat.Builder(this, \"Messages\")\n                    .setContentText(remoteMessage.data[\"text\"])\n                    .setContentTitle(\"New message\")\n                    .setSmallIcon(R.drawable.ic_stat_notification)\n                    .build()\n                manager.notify(0, notification)\n            }\n        }\n    }\n\n    companion object {\n        private const val TAG = \"MessagingService\"\n    }\n}\n"
  },
  {
    "path": "functions/app/src/main/java/com/google/samples/quickstart/functions/kotlin/MainActivity.kt",
    "content": "package com.google.samples.quickstart.functions.kotlin\n\nimport android.Manifest\nimport android.app.Activity\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.content.ContextCompat\nimport com.firebase.ui.auth.AuthUI\nimport com.firebase.ui.auth.FirebaseAuthUIActivityResultContract\nimport com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult\nimport com.google.android.gms.tasks.OnCompleteListener\nimport com.google.android.gms.tasks.Task\nimport com.google.android.material.snackbar.Snackbar\nimport com.google.firebase.auth.auth\nimport com.google.firebase.functions.FirebaseFunctions\nimport com.google.firebase.functions.FirebaseFunctionsException\nimport com.google.firebase.functions.functions\nimport com.google.firebase.Firebase\nimport com.google.samples.quickstart.functions.R\nimport com.google.samples.quickstart.functions.databinding.ActivityMainBinding\n\n/**\n * This activity demonstrates the Android SDK for Callable Functions.\n *\n * For more information, see the documentation for Cloud Functions for Firebase:\n * https://firebase.google.com/docs/functions/\n */\nclass MainActivity : AppCompatActivity(), View.OnClickListener {\n\n    // [START define_functions_instance]\n    private lateinit var functions: FirebaseFunctions\n    // [END define_functions_instance]\n\n    private lateinit var binding: ActivityMainBinding\n\n    private val requestPermissionLauncher = registerForActivityResult(\n        ActivityResultContracts.RequestPermission(),\n    ) { isGranted: Boolean ->\n        if (isGranted) {\n            Toast.makeText(this, \"Notifications permission granted\", Toast.LENGTH_SHORT)\n                .show()\n        } else {\n            Toast.makeText(\n                this,\n                \"FCM can't post notifications without POST_NOTIFICATIONS permission\",\n                Toast.LENGTH_LONG,\n            ).show()\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        with(binding) {\n            buttonCalculate.setOnClickListener(this@MainActivity)\n            buttonAddMessage.setOnClickListener(this@MainActivity)\n            buttonSignIn.setOnClickListener(this@MainActivity)\n        }\n\n        // [START initialize_functions_instance]\n        functions = Firebase.functions\n        // [END initialize_functions_instance]\n\n        askNotificationPermission()\n    }\n\n    // [START function_add_numbers]\n    private fun addNumbers(a: Int, b: Int): Task<Int> {\n        // Create the arguments to the callable function, which are two integers\n        val data = hashMapOf(\n            \"firstNumber\" to a,\n            \"secondNumber\" to b,\n        )\n\n        // Call the function and extract the operation from the result\n        return functions\n            .getHttpsCallable(\"addNumbers\")\n            .call(data)\n            .continueWith { task ->\n                // This continuation runs on either success or failure, but if the task\n                // has failed then task.result will throw an Exception which will be\n                // propagated down.\n                val result = task.result?.data as Map<String, Any>\n                result[\"operationResult\"] as Int\n            }\n    }\n    // [END function_add_numbers]\n\n    // [START function_add_message]\n    private fun addMessage(text: String): Task<String> {\n        // Create the arguments to the callable function.\n        val data = hashMapOf(\n            \"text\" to text,\n            \"push\" to true,\n        )\n\n        return functions\n            .getHttpsCallable(\"addMessage\")\n            .call(data)\n            .continueWith { task ->\n                // This continuation runs on either success or failure, but if the task\n                // has failed then result will throw an Exception which will be\n                // propagated down.\n                val result = task.result?.data as String\n                result\n            }\n    }\n    // [END function_add_message]\n\n    private fun onCalculateClicked() {\n        val firstNumber: Int\n        val secondNumber: Int\n\n        hideKeyboard()\n\n        try {\n            firstNumber = Integer.parseInt(binding.fieldFirstNumber.text.toString())\n            secondNumber = Integer.parseInt(binding.fieldSecondNumber.text.toString())\n        } catch (e: NumberFormatException) {\n            showSnackbar(\"Please enter two numbers.\")\n            return\n        }\n\n        // [START call_add_numbers]\n        addNumbers(firstNumber, secondNumber)\n            .addOnCompleteListener { task ->\n                if (!task.isSuccessful) {\n                    val e = task.exception\n                    if (e is FirebaseFunctionsException) {\n                        // Function error code, will be INTERNAL if the failure\n                        // was not handled properly in the function call.\n                        val code = e.code\n\n                        // Arbitrary error details passed back from the function,\n                        // usually a Map<String, Any>.\n                        val details = e.details\n                    }\n\n                    // [START_EXCLUDE]\n                    Log.w(TAG, \"addNumbers:onFailure\", e)\n                    showSnackbar(\"An error occurred.\")\n                    return@addOnCompleteListener\n                    // [END_EXCLUDE]\n                }\n\n                // [START_EXCLUDE]\n                val result = task.result\n                binding.fieldAddResult.setText(result.toString())\n                // [END_EXCLUDE]\n            }\n        // [END call_add_numbers]\n    }\n\n    private fun onAddMessageClicked() {\n        val inputMessage = binding.fieldMessageInput.text.toString()\n\n        if (TextUtils.isEmpty(inputMessage)) {\n            showSnackbar(\"Please enter a message.\")\n            return\n        }\n\n        // [START call_add_message]\n        addMessage(inputMessage)\n            .addOnCompleteListener(\n                OnCompleteListener { task ->\n                    if (!task.isSuccessful) {\n                        val e = task.exception\n                        if (e is FirebaseFunctionsException) {\n                            val code = e.code\n                            val details = e.details\n                        }\n\n                        // [START_EXCLUDE]\n                        Log.w(TAG, \"addMessage:onFailure\", e)\n                        showSnackbar(\"An error occurred.\")\n                        return@OnCompleteListener\n                        // [END_EXCLUDE]\n                    }\n\n                    // [START_EXCLUDE]\n                    val result = task.result\n                    binding.fieldMessageOutput.setText(result)\n                    // [END_EXCLUDE]\n                },\n            )\n        // [END call_add_message]\n    }\n\n    private fun onSignInClicked() {\n        if (Firebase.auth.currentUser != null) {\n            showSnackbar(\"Signed in.\")\n            return\n        }\n\n        signIn()\n    }\n\n    private fun showSnackbar(message: String) {\n        Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show()\n    }\n\n    private fun signIn() {\n        val signInLauncher = registerForActivityResult(\n            FirebaseAuthUIActivityResultContract(),\n        ) { result -> this.onSignInResult(result) }\n\n        val signInIntent = AuthUI.getInstance()\n            .createSignInIntentBuilder()\n            .setAvailableProviders(listOf(AuthUI.IdpConfig.EmailBuilder().build()))\n            .setCredentialManagerEnabled(false)\n            .build()\n\n        signInLauncher.launch(signInIntent)\n    }\n\n    private fun hideKeyboard() {\n        val view = this.currentFocus\n        if (view != null) {\n            val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager\n            imm.hideSoftInputFromWindow(view.windowToken, 0)\n        }\n    }\n\n    private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) {\n        if (result.resultCode == Activity.RESULT_OK) {\n            showSnackbar(\"Signed in.\")\n        } else {\n            showSnackbar(\"Error signing in.\")\n\n            val response = result.idpResponse\n            Log.w(TAG, \"signIn\", response?.error)\n        }\n    }\n\n    override fun onClick(view: View) {\n        when (view.id) {\n            R.id.buttonCalculate -> onCalculateClicked()\n            R.id.buttonAddMessage -> onAddMessageClicked()\n            R.id.buttonSignIn -> onSignInClicked()\n        }\n    }\n\n    private fun askNotificationPermission() {\n        // This is only necessary for API Level > 33 (TIRAMISU)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==\n                PackageManager.PERMISSION_GRANTED\n            ) {\n                // FCM SDK (and your app) can post notifications.\n            } else {\n                // Directly ask for the permission\n                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)\n            }\n        }\n    }\n\n    companion object {\n        private const val TAG = \"MainActivity\"\n    }\n}\n"
  },
  {
    "path": "functions/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\nCopyright Google Inc. All rights reserved.\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at\n      http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n-->\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.google.samples.quickstart.functions.java.MainActivity\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingBottom=\"@dimen/activity_vertical_margin\"\n        android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n        android:paddingRight=\"@dimen/activity_horizontal_margin\"\n        android:paddingTop=\"@dimen/activity_vertical_margin\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\">\n\n        <ImageView\n            android:id=\"@+id/icon\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"16dp\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <com.google.android.material.card.MaterialCardView\n            android:id=\"@+id/card_add_numbers\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"20dp\"\n            app:cardBackgroundColor=\"@android:color/white\"\n            app:cardUseCompatPadding=\"true\">\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"16dp\">\n\n                <TextView\n                    android:id=\"@+id/header_add_numbers\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/heading_add_numbers\"\n                    android:textSize=\"20sp\"\n                    android:textStyle=\"bold\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <EditText\n                    android:id=\"@+id/fieldFirstNumber\"\n                    android:layout_width=\"50dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:gravity=\"center\"\n                    android:inputType=\"number\"\n                    app:layout_constraintStart_toStartOf=\"@+id/header_add_numbers\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/header_add_numbers\"\n                    tools:text=\"1\" />\n\n                <TextView\n                    android:id=\"@+id/labelPlus\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/plus\"\n                    android:textSize=\"14sp\"\n                    android:textStyle=\"bold\"\n                    app:layout_constraintBottom_toBottomOf=\"@+id/fieldFirstNumber\"\n                    app:layout_constraintStart_toEndOf=\"@+id/fieldFirstNumber\"\n                    app:layout_constraintTop_toTopOf=\"@+id/fieldFirstNumber\" />\n\n                <EditText\n                    android:id=\"@+id/fieldSecondNumber\"\n                    android:layout_width=\"50dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:gravity=\"center\"\n                    android:inputType=\"number\"\n                    tools:text=\"1\"\n                    app:layout_constraintBottom_toBottomOf=\"@+id/labelPlus\"\n                    app:layout_constraintStart_toEndOf=\"@+id/labelPlus\"\n                    app:layout_constraintTop_toTopOf=\"@+id/labelPlus\" />\n\n                <TextView\n                    android:id=\"@+id/labelEquals\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/equals\"\n                    android:textSize=\"14sp\"\n                    android:textStyle=\"bold\"\n                    app:layout_constraintBottom_toBottomOf=\"@+id/fieldSecondNumber\"\n                    app:layout_constraintStart_toEndOf=\"@+id/fieldSecondNumber\"\n                    app:layout_constraintTop_toTopOf=\"@+id/fieldSecondNumber\" />\n\n                <EditText\n                    android:id=\"@+id/fieldAddResult\"\n                    android:layout_width=\"50dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:enabled=\"false\"\n                    android:gravity=\"center\"\n                    android:inputType=\"number\"\n                    tools:text=\"2\"\n                    app:layout_constraintBottom_toBottomOf=\"@+id/labelEquals\"\n                    app:layout_constraintStart_toEndOf=\"@+id/labelEquals\"\n                    app:layout_constraintTop_toTopOf=\"@+id/labelEquals\" />\n\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/buttonCalculate\"\n                    style=\"@style/Widget.MaterialComponents.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/calculate\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/fieldFirstNumber\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </com.google.android.material.card.MaterialCardView>\n\n        <com.google.android.material.card.MaterialCardView\n            android:id=\"@+id/card_add_message\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:cardBackgroundColor=\"@android:color/white\"\n            app:cardUseCompatPadding=\"true\">\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"16dp\">\n\n                <TextView\n                    android:id=\"@+id/header_add_message\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/heading_add_message\"\n                    android:textSize=\"20sp\"\n                    android:textStyle=\"bold\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <EditText\n                    android:id=\"@+id/fieldMessageInput\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/input\"\n                    android:inputType=\"text\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/header_add_message\"\n                    tools:text=\"some bad message\" />\n\n                <EditText\n                    android:id=\"@+id/fieldMessageOutput\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:enabled=\"false\"\n                    android:hint=\"@string/output\"\n                    android:inputType=\"text\"\n                    app:layout_constraintEnd_toEndOf=\"@+id/fieldMessageInput\"\n                    app:layout_constraintStart_toStartOf=\"@+id/fieldMessageInput\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/fieldMessageInput\"\n                    tools:text=\"some clean message\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/buttonSignIn\"\n                    style=\"@style/Widget.MaterialComponents.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_alignTop=\"@+id/buttonAddMessage\"\n                    android:layout_toStartOf=\"@+id/buttonAddMessage\"\n                    android:layout_toLeftOf=\"@+id/buttonAddMessage\"\n                    android:text=\"@string/sign_in\"\n                    app:layout_constraintEnd_toStartOf=\"@+id/buttonAddMessage\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/fieldMessageOutput\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/buttonAddMessage\"\n                    style=\"@style/Widget.MaterialComponents.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/add_message\"\n                    app:layout_constraintEnd_toEndOf=\"@+id/fieldMessageOutput\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/fieldMessageOutput\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </com.google.android.material.card.MaterialCardView>\n\n    </LinearLayout>\n\n</ScrollView>\n"
  },
  {
    "path": "functions/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n</resources>\n"
  },
  {
    "path": "functions/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "functions/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase Functions</string>\n\n    <string name=\"heading_add_numbers\">Add two numbers</string>\n    <string name=\"plus\">+</string>\n    <string name=\"equals\">=</string>\n    <string name=\"calculate\">Calculate</string>\n\n    <string name=\"heading_add_message\">Sanitize a message</string>\n    <string name=\"input\">Input</string>\n    <string name=\"output\">Output</string>\n    <string name=\"sign_in\">Sign In</string>\n    <string name=\"add_message\">Add Message</string>\n</resources>\n"
  },
  {
    "path": "functions/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "functions/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "functions/app/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <!-- Allow communication with the Firebase Emulator Suite -->\n    <domain-config cleartextTrafficPermitted=\"true\">\n        <domain includeSubdomains=\"true\">10.0.2.2</domain>\n    </domain-config>\n</network-security-config>"
  },
  {
    "path": "functions/app/test-proguard-rules.pro",
    "content": "-dontobfuscate\n-dontwarn\n\n-dontwarn org.xmlpull.v1.**\n-dontnote org.xmlpull.v1.**\n-keep class org.xmlpull.** { *; }\n-keepclassmembers class org.xmlpull.** { *; }\n"
  },
  {
    "path": "functions/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "functions/firebase.json",
    "content": "{\n  \"functions\": [\n    {\n      \"source\": \"functions\",\n      \"codebase\": \"default\",\n      \"ignore\": [\n        \"node_modules\",\n        \".git\",\n        \"firebase-debug.log\",\n        \"firebase-debug.*.log\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "functions/functions/.gitignore",
    "content": "*.log\nnode_modules\n"
  },
  {
    "path": "functions/functions/index.js",
    "content": "/**\n * Copyright 2018 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\nconst {onCall, HttpsError} = require('firebase-functions/v2/https')\nconst {initializeApp} = require(\"firebase-admin/app\");\n\nconst sanitizer = require('./sanitizer');\n\ninitializeApp();\n\n// [START allAdd]\n// [START addFunctionTrigger]\n// Adds two numbers to each other.\nexports.addNumbers = onCall((data) => {\n// [END addFunctionTrigger]\n  // [START readAddData]\n  // Numbers passed from the client.\n  const firstNumber = data.firstNumber;\n  const secondNumber = data.secondNumber;\n  // [END readAddData]\n\n  // [START addHttpsError]\n  // Checking that attributes are present and are numbers.\n  if (!Number.isFinite(firstNumber) || !Number.isFinite(secondNumber)) {\n    // Throwing an HttpsError so that the client gets the error details.\n    throw new HttpsError('invalid-argument', 'The function must be called with ' +\n        'two arguments \"firstNumber\" and \"secondNumber\" which must both be numbers.');\n  }\n  // [END addHttpsError]\n\n  // [START returnAddData]\n  // returning result.\n  return {\n    firstNumber: firstNumber,\n    secondNumber: secondNumber,\n    operator: '+',\n    operationResult: firstNumber + secondNumber,\n  };\n  // [END returnAddData]\n});\n// [END allAdd]\n\n// [START messageFunctionTrigger]\n// Saves a message to the Firebase Realtime Database but sanitizes the text by removing swearwords.\nexports.addMessage = onCall((data, context) => {\n  // [START_EXCLUDE]\n  // [START readMessageData]\n  // Message text passed from the client.\n  const text = data.text;\n  // [END readMessageData]\n  // [START messageHttpsErrors]\n  // Checking attribute.\n  if (!(typeof text === 'string') || text.length === 0) {\n    // Throwing an HttpsError so that the client gets the error details.\n    throw new HttpsError('invalid-argument', 'The function must be called with ' +\n        'one arguments \"text\" containing the message text to add.');\n  }\n  // Checking that the user is authenticated.\n  if (!context.auth) {\n    // Throwing an HttpsError so that the client gets the error details.\n    throw new HttpsError('failed-precondition', 'The function must be called ' +\n        'while authenticated.');\n  }\n  // [END messageHttpsErrors]\n\n  // [START authIntegration]\n  // Authentication / user information is automatically added to the request.\n  const uid = context.auth.uid;\n  const name = context.auth.token.name || null;\n  const picture = context.auth.token.picture || null;\n  const email = context.auth.token.email || null;\n  // [END authIntegration]\n\n  // [START returnMessageAsync]\n  // Saving the new message to the Realtime Database.\n  const sanitizedMessage = sanitizeText(text); // Sanitize the message.\n  return admin.database().ref('/messages').push({\n    text: sanitizedMessage,\n    author: { uid, name, picture, email },\n  }).then(() => {\n    // Optionally send a push notification with the message.\n    if (data.push && context.instanceIdToken) {\n      return admin.messaging().send({\n        token: context.instanceIdToken,\n        data: { text: sanitizedMessage },\n      });\n    }\n  }).then(() => {\n    console.log('New Message written');\n    return sanitizedMessage;\n  }).catch((error) => {\n    // Re-throwing the error as an HttpsError so that the client gets the error details.\n    throw new HttpsError('unknown', error.message, error);\n  });\n  // [END returnMessageAsync]\n  // [END_EXCLUDE]\n});\n// [END messageFunctionTrigger]\n"
  },
  {
    "path": "functions/functions/package.json",
    "content": "{\n  \"name\": \"functions\",\n  \"description\": \"Cloud Functions for Android Functions Quickstart\",\n  \"engines\": {\n    \"node\": \"20\"\n  },\n  \"dependencies\": {\n    \"bad-words\": \"^1.3.1\",\n    \"capitalize-sentence\": \"^0.1.2\",\n    \"firebase-admin\": \"^12.0.0\",\n    \"firebase-functions\": \"^6.1.0\"\n  },\n  \"private\": true\n}\n"
  },
  {
    "path": "functions/functions/sanitizer.js",
    "content": "/**\n * Copyright 2018 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\nconst capitalizeSentence = require('capitalize-sentence');\nconst Filter = require('bad-words');\nconst badWordsFilter = new Filter();\n\n// Sanitizes the given text if needed by replacing bad words with '*'.\nexports.sanitizeText = (text) => {\n  // Re-capitalize if the user is Shouting.\n  if (isShouting(text)) {\n    console.log('User is shouting. Fixing sentence case...');\n    text = stopShouting(text);\n  }\n\n  // Moderate if the user uses SwearWords.\n  if (containsSwearwords(text)) {\n    console.log('User is swearing. moderating...');\n    text = replaceSwearwords(text);\n  }\n\n  return text;\n};\n\n// Returns true if the string contains swearwords.\nfunction containsSwearwords(message) {\n  return message !== badWordsFilter.clean(message);\n}\n\n// Hide all swearwords. e.g: Crap => ****.\nfunction replaceSwearwords(message) {\n  return badWordsFilter.clean(message);\n}\n\n// Detect if the current message is shouting. i.e. there are too many Uppercase\n// characters or exclamation points.\nfunction isShouting(message) {\n  return message.replace(/[^A-Z]/g, '').length > message.length / 2 || message.replace(/[^!]/g, '').length >= 3;\n}\n\n// Correctly capitalize the string as a sentence (e.g. uppercase after dots)\n// and remove exclamation points.\nfunction stopShouting(message) {\n  return capitalizeSentence(message.toLowerCase()).replace(/!+/g, '.');\n}\n"
  },
  {
    "path": "functions/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "functions/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "functions/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "functions/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "functions/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "functions/test_setup.sh",
    "content": "#!/bin/bash\nset -o nounset\nset -e\n\n# Install all of the dependencies of the cloud functions\npushd functions\nnpm install\npopd\n\n# Deploy functions to your Firebase project\nfirebase --project=\"$PROJECT_ID\" deploy --only functions\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nactivityCompose = \"1.12.1\"\nagp = \"9.0.0\"\ncamerax = \"1.5.2\"\ncoil3Compose = \"3.3.0\"\ncomposeBom = \"2025.12.00\"\ncomposeNavigation = \"2.9.6\"\ncoreKtx = \"1.17.0\"\nespressoCore = \"3.7.0\"\nfirebaseBom = \"34.7.0\"\ngoogleServices = \"4.4.4\"\nfirebaseCrashlytics = \"3.0.6\"\nfirebasePerf = \"2.0.2\"\ngradleVersions = \"0.53.0\"\njunit = \"4.13.2\"\njunitVersion = \"1.3.0\"\nkotlin = \"2.3.0\"\nkotlinxSerializationCore = \"1.9.0\"\nlifecycle = \"2.10.0\"\nmaterial = \"1.13.0\"\nmaterialIcons = \"1.7.8\"\nwebkit = \"1.14.0\"\n\n[libraries]\nandroidx-activity-compose = { group = \"androidx.activity\", name = \"activity-compose\", version.ref = \"activityCompose\" }\nandroidx-camera-camera2 = { module = \"androidx.camera:camera-camera2\", version.ref = \"camerax\" }\nandroidx-camera-core = { module = \"androidx.camera:camera-core\", version.ref = \"camerax\" }\nandroidx-camera-extensions = { module = \"androidx.camera:camera-extensions\", version.ref = \"camerax\" }\nandroidx-camera-lifecycle = { module = \"androidx.camera:camera-lifecycle\", version.ref = \"camerax\" }\nandroidx-camera-view = { module = \"androidx.camera:camera-view\", version.ref = \"camerax\" }\nandroidx-compose-bom = { group = \"androidx.compose\", name = \"compose-bom\", version.ref = \"composeBom\" }\nandroidx-core-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"coreKtx\" }\nandroidx-espresso-core = { group = \"androidx.test.espresso\", name = \"espresso-core\", version.ref = \"espressoCore\" }\nandroidx-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"junitVersion\" }\nandroidx-lifecycle-runtime-compose-android = { module = \"androidx.lifecycle:lifecycle-runtime-compose-android\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-runtime-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-ktx\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-viewmodel-compose = { module = \"androidx.lifecycle:lifecycle-viewmodel-compose\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-viewmodel-ktx = { module = \"androidx.lifecycle:lifecycle-viewmodel-ktx\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-viewmodel-savedstate = { module = \"androidx.lifecycle:lifecycle-viewmodel-savedstate\", version.ref = \"lifecycle\" }\nandroidx-material-icons-extended = { module = \"androidx.compose.material:material-icons-extended\" }\nandroidx-material3 = { group = \"androidx.compose.material3\", name = \"material3\" }\nandroidx-material3-adaptive-navigation-suite = { module = \"androidx.compose.material3:material3-adaptive-navigation-suite\" }\nandroidx-ui = { group = \"androidx.compose.ui\", name = \"ui\" }\nandroidx-ui-graphics = { group = \"androidx.compose.ui\", name = \"ui-graphics\" }\nandroidx-ui-test-junit4 = { group = \"androidx.compose.ui\", name = \"ui-test-junit4\" }\nandroidx-ui-test-manifest = { group = \"androidx.compose.ui\", name = \"ui-test-manifest\" }\nandroidx-ui-tooling = { group = \"androidx.compose.ui\", name = \"ui-tooling\" }\nandroidx-ui-tooling-preview = { group = \"androidx.compose.ui\", name = \"ui-tooling-preview\" }\nandroidx-webkit = { module = \"androidx.webkit:webkit\", version.ref = \"webkit\" }\ncoil-compose = { module = \"io.coil-kt.coil3:coil-compose\", version.ref = \"coil3Compose\" }\ncoil-network-okhttp = { module = \"io.coil-kt.coil3:coil-network-okhttp\", version.ref = \"coil3Compose\" }\ncoil-svg = { module = \"io.coil-kt.coil3:coil-svg\", version.ref = \"coil3Compose\" }\ncompose-navigation = { group = \"androidx.navigation\", name = \"navigation-compose\", version.ref = \"composeNavigation\"}\nfirebase-ai = { module = \"com.google.firebase:firebase-ai\" }\nfirebase-bom = { module = \"com.google.firebase:firebase-bom\", version.ref = \"firebaseBom\" }\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" }\nkotlinx-serialization-core = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-core\", version.ref = \"kotlinxSerializationCore\" }\nkotlinx-serialization-json = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-json\", version.ref = \"kotlinxSerializationCore\" }\nmaterial = { module = \"com.google.android.material:material\", version.ref = \"material\" }\ncompose-material-icons = { group = \"androidx.compose.material\", name = \"material-icons-core\",  version.ref = \"materialIcons\"}\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\nandroid-library = { id = \"com.android.library\", version.ref = \"agp\" }\njetbrains-kotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\nkotlin-serialization = { id = \"org.jetbrains.kotlin.plugin.serialization\", version.ref = \"kotlin\" }\ngoogle-services = { id = \"com.google.gms.google-services\", version.ref = \"googleServices\" }\ncompose-compiler = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }\nfirebase-crashlytics = { id = \"com.google.firebase.crashlytics\", version.ref = \"firebaseCrashlytics\" }\nfirebase-perf = { id = \"com.google.firebase.firebase-perf\", version.ref = \"firebasePerf\" }\nnavigation-safeargs = { id = \"androidx.navigation.safeargs\", version.ref = \"composeNavigation\" }\ngradle-versions = { id = \"com.github.ben-manes.versions\", version.ref = \"gradleVersions\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=6g -XX:ReservedCodeCacheSize=2g -Dfile.encoding=UTF-8\n# TODO there's a bug when using parallel modules: https://issuetracker.google.com/issues/62805436\norg.gradle.parallel=true\norg.gradle.configureondemand=true\norg.gradle.caching=true\n\n## Play and Firebase moving to AndroidX\nandroid.useAndroidX=true\nandroid.nonTransitiveRClass=false\nandroid.nonFinalResIds=false\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "inappmessaging/.gitignore",
    "content": ".gradle\nlocal.properties\n.idea\nbuild/\n.DS_Store\n*.iml\n*.apk\n*.aar\n*.zip\ngoogle-services.json\n"
  },
  {
    "path": "inappmessaging/README.md",
    "content": "# Firebase In-App Messaging Quickstart\n\n## Introduction\n\nThis quickstart demonstrates basic usage of Firebase In-App Messaging.\n\n## Getting Started\n\n  * Follow the instructions to [add Firebase to your Android app][add-firebase-android].\n  * In the Firebase console, navigate to the **In-App Messaging** section.\n  * Click **New Campaign**\n    * In the **Style and content** section, design a campaign using one of the provided layouts (Modal, Image, or Banner),\n      then click **Next**.\n    * In the **Target** section, enter any campaign name and description. In the dropdown, choose to target the app\n      you just created, then click **Next**.\n    * In the **Scheduling** section, choose any reasonable start and end date. Choose `engagement_party` for the trigger event,\n      then click **Next**.\n    * In the **Conversion events** tab choose the `ecommerce_purchase` conversion event.\n  * **Run** the sample app on your device\n    * Press the **Trigger** button to fire the event.\n\n\n## Result\n\nIf you successfully trigger a message, you should see something like this:\n\n<img src=\"docs/result.png\" height=\"600\" />\n\n[add-firebase-android]: https://firebase.google.com/docs/android/setup"
  },
  {
    "path": "inappmessaging/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "inappmessaging/app/build.gradle.kts",
    "content": "\nplugins {\n    id(\"com.android.application\")\n    id(\"com.google.gms.google-services\")\n}\n\nandroid {\n    namespace = \"com.google.firebase.fiamquickstart\"\n    compileSdk = 36\n    defaultConfig {\n        applicationId = \"com.google.firebase.fiamquickstart\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n\n        multiDexEnabled = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n    lint {\n        warning.add(\"InvalidPackage\")\n        // TODO(thatfiredev): Remove this once\n        //  https://github.com/bumptech/glide/issues/4940 is fixed\n        disable.add(\"NotificationPermission\")\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.constraintlayout:constraintlayout:2.2.1\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // FIAM\n    implementation(\"com.google.firebase:firebase-inappmessaging-display\")\n\n    // The Firebase SDK for Google Analytics is required to use In-App Messaging\n    // Analytics\n    implementation(\"com.google.firebase:firebase-analytics\")\n\n    implementation(\"com.google.firebase:firebase-installations:19.0.1\")\n\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test.uiautomator:uiautomator:2.3.0\")\n}\n"
  },
  {
    "path": "inappmessaging/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "inappmessaging/app/src/androidTest/java/com/google/firebase/fiamquickstart/InstrumentedTest.java",
    "content": "package com.google.firebase.fiamquickstart;\n\nimport android.os.RemoteException;\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.test.espresso.Root;\nimport androidx.test.espresso.ViewInteraction;\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.runner.AndroidJUnit4;\nimport androidx.test.uiautomator.UiDevice;\n\nimport org.hamcrest.Matcher;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static androidx.test.InstrumentationRegistry.getInstrumentation;\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.assertion.ViewAssertions.matches;\nimport static androidx.test.espresso.matcher.RootMatchers.withDecorView;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static androidx.test.espresso.matcher.ViewMatchers.withId;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.not;\n\nimport com.google.firebase.fiamquickstart.R;\n\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n */\n@RunWith(AndroidJUnit4.class)\npublic class InstrumentedTest {\n\n  @Rule\n  public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);\n\n  private Matcher<Root> rootMatcher;\n\n  @Before\n  public void setUp() {\n    rootMatcher = withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView())));\n  }\n\n  @After\n  public void tearDown() {\n      getView(R.id.collapse_button).perform(click());\n  }\n\n\n  @Test\n  public void testFiamDisplaysOnForegroundCampaign() {\n    reopen_app(); // reopen app to correctly trigger fetch\n    getView(R.id.modal_root).check(matches(isDisplayed()));\n  }\n\n  @Test\n  public void testFiamDisplaysContextualTriggerCampaign() {\n    onView(withId(R.id.eventTriggerButton)).perform(click());\n    getView(R.id.modal_root).check(matches(isDisplayed()));\n  }\n\n  private void reopen_app() {\n    press_recent();\n    press_back();\n  }\n\n  private void press_recent() {\n    try {\n      UiDevice.getInstance(getInstrumentation()).pressRecentApps();\n    } catch (RemoteException e) {\n      e.printStackTrace();\n    }\n    sleep();\n  }\n\n  private void press_back() {\n    UiDevice.getInstance(getInstrumentation()).pressBack();\n    sleep();\n\n  }\n\n  private void sleep() {\n    try {\n      Thread.sleep(3000);\n    } catch (Exception e) {\n      e.printStackTrace();\n    }\n  }\n\n  @NonNull\n  private ViewInteraction getView(@IdRes int id) {\n    return onView(withId(id)).inRoot(rootMatcher);\n  }\n}\n"
  },
  {
    "path": "inappmessaging/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity\n            android:name=\"com.google.firebase.fiamquickstart.java.MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme\">\n        </activity>\n\n        <activity\n            android:name=\"com.google.firebase.fiamquickstart.kotlin.KotlinMainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme\">\n        </activity>\n\n        <activity android:name=\"com.google.firebase.fiamquickstart.EntryChoiceActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "inappmessaging/app/src/main/java/com/google/firebase/fiamquickstart/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.fiamquickstart\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\nimport com.google.firebase.fiamquickstart.java.MainActivity\nimport com.google.firebase.fiamquickstart.kotlin.KotlinMainActivity\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase In App Messaging quickstart written in Java.\",\n                Intent(this, MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase In App Messaging quickstart written in Kotlin.\",\n                Intent(this, KotlinMainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "inappmessaging/app/src/main/java/com/google/firebase/fiamquickstart/java/MainActivity.java",
    "content": "package com.google.firebase.fiamquickstart.java;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\n\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport com.google.android.gms.tasks.OnSuccessListener;\nimport com.google.android.material.snackbar.Snackbar;\nimport com.google.firebase.analytics.FirebaseAnalytics;\nimport com.google.firebase.fiamquickstart.R;\nimport com.google.firebase.fiamquickstart.databinding.ActivityMainBinding;\nimport com.google.firebase.inappmessaging.FirebaseInAppMessaging;\nimport com.google.firebase.installations.FirebaseInstallations;\n\npublic class MainActivity extends AppCompatActivity {\n\n    private static final String TAG = \"FIAM-Quickstart\";\n\n    private FirebaseAnalytics mFirebaseAnalytics;\n    private FirebaseInAppMessaging mInAppMessaging;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        final ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);\n        mInAppMessaging = FirebaseInAppMessaging.getInstance();\n\n        mInAppMessaging.setAutomaticDataCollectionEnabled(true);\n        mInAppMessaging.setMessagesSuppressed(false);\n\n        binding.eventTriggerButton.setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(View view) {\n                        mFirebaseAnalytics.logEvent(\"engagement_party\", new Bundle());\n                        Snackbar.make(view, \"'engagement_party' event triggered!\", Snackbar.LENGTH_LONG)\n                                .setAction(\"Action\", null)\n                                .show();\n                    }\n                });\n\n        // Get and display/log the Instance ID\n        FirebaseInstallations.getInstance().getId()\n                .addOnSuccessListener(new OnSuccessListener<String>() {\n                    @Override\n                    public void onSuccess(String id) {\n                        binding.installationIdText.setText(getString(R.string.installation_id_fmt, id));\n                        Log.d(TAG, \"Installation id: \" + id);\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "inappmessaging/app/src/main/java/com/google/firebase/fiamquickstart/kotlin/KotlinMainActivity.kt",
    "content": "package com.google.firebase.fiamquickstart.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport androidx.appcompat.app.AppCompatActivity\nimport com.google.android.material.snackbar.Snackbar\nimport com.google.firebase.analytics.FirebaseAnalytics\nimport com.google.firebase.fiamquickstart.R\nimport com.google.firebase.fiamquickstart.databinding.ActivityMainBinding\nimport com.google.firebase.inappmessaging.FirebaseInAppMessaging\nimport com.google.firebase.inappmessaging.inAppMessaging\nimport com.google.firebase.installations.installations\nimport com.google.firebase.Firebase\nimport com.google.firebase.analytics.analytics\n\nclass KotlinMainActivity : AppCompatActivity() {\n\n    private lateinit var firebaseAnalytics: FirebaseAnalytics\n    private lateinit var firebaseIam: FirebaseInAppMessaging\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        firebaseAnalytics = Firebase.analytics\n        firebaseIam = Firebase.inAppMessaging\n\n        firebaseIam.isAutomaticDataCollectionEnabled = true\n        firebaseIam.setMessagesSuppressed(false)\n\n        binding.eventTriggerButton.setOnClickListener { view ->\n            firebaseAnalytics.logEvent(\"engagement_party\", Bundle())\n            Snackbar.make(view, \"'engagement_party' event triggered!\", Snackbar.LENGTH_LONG)\n                .setAction(\"Action\", null)\n                .show()\n        }\n\n        // Get and display/log the installation id\n        Firebase.installations.getId()\n            .addOnSuccessListener { id ->\n                binding.installationIdText.text = getString(R.string.installation_id_fmt, id)\n                Log.d(TAG, \"Installation ID: $id\")\n            }\n    }\n\n    companion object {\n\n        private const val TAG = \"FIAM-Quickstart\"\n    }\n}\n"
  },
  {
    "path": "inappmessaging/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n    android:height=\"108dp\"\n    android:width=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#26A69A\"\n          android:pathData=\"M0,0h108v108h-108z\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M9,0L9,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,0L19,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M29,0L29,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M39,0L39,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M49,0L49,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M59,0L59,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M69,0L69,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M79,0L79,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M89,0L89,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M99,0L99,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,9L108,9\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,19L108,19\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,29L108,29\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,39L108,39\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,49L108,49\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,59L108,59\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,69L108,69\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,79L108,79\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,89L108,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,99L108,99\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,29L89,29\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,39L89,39\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,49L89,49\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,59L89,59\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,69L89,69\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,79L89,79\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M29,19L29,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M39,19L39,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M49,19L49,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M59,19L59,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M69,19L69,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M79,19L79,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n</vector>\n"
  },
  {
    "path": "inappmessaging/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"78.5885\"\n                android:endY=\"90.9159\"\n                android:startX=\"48.7653\"\n                android:startY=\"61.0927\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\" />\n</vector>\n"
  },
  {
    "path": "inappmessaging/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\"\n    tools:context=\".MainActivity\">\n\n    <ImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/textView\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:text=\"@string/textview_text\"\n        android:textSize=\"18sp\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/imageView\" />\n\n    <TextView\n        android:id=\"@+id/textView2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"16dp\"\n        android:text=\"@string/warning_fresh_install\"\n        android:textSize=\"18sp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/textView\" />\n\n    <TextView\n        android:id=\"@+id/installationIdText\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"16dp\"\n        android:text=\"@string/warning_fresh_install\"\n        android:textSize=\"18sp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/textView2\"\n        tools:text=\"Device Instance ID: 1234\" />\n\n    <Button\n        android:id=\"@+id/eventTriggerButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:text=\"@string/button_text\"\n        android:theme=\"@style/ThemeOverlay.MyDarkButton\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/textView2\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "inappmessaging/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n\n    <color name=\"primary_text\">#212121</color>\n    <color name=\"secondary_text\">#727272</color>\n    <color name=\"icons\">#212121</color>\n    <color name=\"divider\">#B6B6B6</color>\n\n    <color name=\"grey_500\">#9E9E9E</color>\n</resources>\n"
  },
  {
    "path": "inappmessaging/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <dimen name=\"fab_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "inappmessaging/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">FIAM Quickstart</string>\n    <string name=\"textview_text\">Welcome to the Firebase In-App Messaging Quickstart app. Press the button to trigger an analytics event!</string>\n    <string name=\"warning_fresh_install\">You may need to background and then re-foreground the app after a fresh install.</string>\n    <string name=\"button_text\">Trigger \\'engagement_party\\' event</string>\n\n    <string name=\"installation_id_fmt\">Firebase Installation ID: %s</string>\n</resources>\n"
  },
  {
    "path": "inappmessaging/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n    <!-- Dark Buttons -->\n    <style name=\"ThemeOverlay.MyDarkButton\" parent=\"ThemeOverlay.AppCompat.Dark\">\n        <item name=\"colorButtonNormal\">@color/colorAccent</item>\n        <item name=\"android:layout_marginRight\">4dp</item>\n        <item name=\"android:layout_marginLeft\">4dp</item>\n        <item name=\"android:textColor\">@android:color/white</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "inappmessaging/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "inappmessaging/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "inappmessaging/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "inappmessaging/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "inappmessaging/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "inappmessaging/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "internal/chooserx/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "internal/chooserx/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.firebase.example.internal\"\n    compileSdk = 36\n\n    defaultConfig {\n        minSdk = 16\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n\n    lint {\n        targetSdk = 36\n    }\n\n}\n\ndependencies {\n    api(\"com.google.android.material:material:1.13.0\")\n    api(\"androidx.recyclerview:recyclerview:1.4.0\")\n    api(\"androidx.constraintlayout:constraintlayout:2.2.1\")\n}\n"
  },
  {
    "path": "internal/chooserx/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "internal/chooserx/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n</manifest>\n"
  },
  {
    "path": "internal/chooserx/src/main/java/com/firebase/example/internal/BaseEntryChoiceActivity.java",
    "content": "package com.firebase.example.internal;\n\nimport android.os.Bundle;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport java.util.List;\n\npublic abstract class BaseEntryChoiceActivity extends AppCompatActivity {\n\n    private RecyclerView mRecycler;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_entry_choice);\n\n        mRecycler = findViewById(R.id.choices_recycler);\n        mRecycler.setLayoutManager(new LinearLayoutManager(this));\n        mRecycler.setAdapter(new ChoiceAdapter(this, getChoices()));\n    }\n\n    protected abstract List<Choice> getChoices();\n}\n"
  },
  {
    "path": "internal/chooserx/src/main/java/com/firebase/example/internal/Choice.java",
    "content": "package com.firebase.example.internal;\n\nimport android.content.Intent;\n\npublic class Choice {\n\n    public final String title;\n    public final String description;\n    public final Intent launchIntent;\n\n    public Choice(String title, String description, Intent launchIntent) {\n        this.title = title;\n        this.description = description;\n        this.launchIntent = launchIntent;\n    }\n\n}\n"
  },
  {
    "path": "internal/chooserx/src/main/java/com/firebase/example/internal/ChoiceAdapter.java",
    "content": "package com.firebase.example.internal;\n\nimport android.app.Activity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport java.util.List;\n\npublic class ChoiceAdapter extends RecyclerView.Adapter<ChoiceAdapter.ViewHolder> {\n\n    private final Activity activity;\n    private List<Choice> choices;\n\n    public ChoiceAdapter(Activity activity, List<Choice> choices) {\n        this.activity = activity;\n        this.choices = choices;\n    }\n\n    @NonNull\n    @Override\n    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {\n        View view = LayoutInflater.from(parent.getContext())\n                .inflate(R.layout.item_choice, parent, false);\n        return new ViewHolder(view);\n    }\n\n    @Override\n    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {\n        Choice choice = choices.get(position);\n        holder.bind(choice);\n    }\n\n    @Override\n    public int getItemCount() {\n        return choices.size();\n    }\n\n    public class ViewHolder extends RecyclerView.ViewHolder {\n\n        private final TextView titleText;\n        private final TextView descText;\n        private final Button launchButton;\n\n        public ViewHolder(View itemView) {\n            super(itemView);\n            titleText = itemView.findViewById(R.id.item_title);\n            descText = itemView.findViewById(R.id.item_description);\n            launchButton = itemView.findViewById(R.id.item_launch_button);\n        }\n\n        public void bind(final Choice choice) {\n            titleText.setText(choice.title);\n            descText.setText(choice.description);\n            launchButton.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    activity.startActivity(choice.launchIntent);\n                }\n            });\n        }\n    }\n\n}\n"
  },
  {
    "path": "internal/chooserx/src/main/res/layout/activity_entry_choice.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.firebase.example.internal.BaseEntryChoiceActivity\"\n    android:paddingTop=\"8dp\"\n    android:paddingBottom=\"8dp\"\n    android:clipToPadding=\"false\">\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/choices_recycler\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        tools:listitem=\"@layout/item_choice\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "internal/chooserx/src/main/res/layout/item_choice.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipToPadding=\"false\"\n    android:paddingBottom=\"8dp\"\n    android:paddingLeft=\"8dp\"\n    android:paddingRight=\"8dp\">\n\n    <com.google.android.material.card.MaterialCardView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <TextView\n                android:id=\"@+id/item_title\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"16dp\"\n                android:layout_marginStart=\"16dp\"\n                android:layout_marginTop=\"8dp\"\n                android:textSize=\"16sp\"\n                android:textStyle=\"bold\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintHorizontal_bias=\"0\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"Choice Title\" />\n\n\n            <TextView\n                android:id=\"@+id/item_description\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"16dp\"\n                android:layout_marginRight=\"16dp\"\n                android:layout_marginTop=\"8dp\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintHorizontal_bias=\"0\"\n                app:layout_constraintStart_toStartOf=\"@+id/item_title\"\n                app:layout_constraintTop_toBottomOf=\"@+id/item_title\"\n                tools:text=\"Choice Description\" />\n\n            <com.google.android.material.button.MaterialButton\n                android:id=\"@+id/item_launch_button\"\n                style=\"@style/Widget.MaterialComponents.Button.TextButton\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"8dp\"\n                android:text=\"Open\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/item_description\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </com.google.android.material.card.MaterialCardView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "internal/lint/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "internal/lint/bin/main/com/firebase/lint/HungarianNotationDetector.kt",
    "content": "package com.firebase.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity\nimport org.jetbrains.uast.UField\n\nval ISSUE_HUNGARIAN_NOTATION = Issue.create(\n    \"HungarianNotation\",\n    \"Using mHungarianNotation in a Kotlin file!\",\n    \"mFriends don’t let sFriends use Hungarian notation! -Jake Wharton\",\n    Category.MESSAGES,\n    9,\n    Severity.ERROR,\n    Implementation(\n        HungarianNotationDetector::class.java,\n        Scope.JAVA_FILE_SCOPE,\n    ),\n)\n\nclass HungarianNotationDetector : Detector(), Detector.UastScanner {\n\n    override fun getApplicableUastTypes() = listOf(UField::class.java)\n\n    override fun createUastHandler(context: JavaContext) = HungarianNotationHandler(context)\n\n    class HungarianNotationHandler(private val context: JavaContext) : UElementHandler() {\n\n        override fun visitField(node: UField) {\n            val varName = node.name\n            val isKotlin = context.file.name.endsWith(\"kt\")\n            val isHungarian = varName.matches(RE_HUNGARIAN)\n\n            if (isKotlin && isHungarian) {\n                node.uastAnchor?.let {\n                    context.report(ISSUE_HUNGARIAN_NOTATION, node, context.getLocation(it), SHORT_MESSAGE)\n                }\n            }\n        }\n    }\n\n    companion object {\n        const val SHORT_MESSAGE = \"Invalid Field Name: hungarian notation in a Kotlin file.\"\n\n        val RE_HUNGARIAN = Regex(\"^m[A-Z].*\")\n    }\n}\n"
  },
  {
    "path": "internal/lint/bin/main/com/firebase/lint/InvalidImportDetector.kt",
    "content": "package com.firebase.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity\nimport org.jetbrains.uast.UImportStatement\n\nval ISSUE_INVALID_IMPORT = Issue.create(\n    \"SuspiciousImport\",\n    \"importing files from the `java` package in a kotlin file\",\n    \"Importing files from the java package is usually not intentional; it sometimes happens when \" +\n        \"you have classes with the same name in both `java` and `kotlin` package.\",\n    Category.CORRECTNESS,\n    9,\n    Severity.ERROR,\n    Implementation(\n        InvalidImportDetector::class.java,\n        Scope.JAVA_FILE_SCOPE,\n    ),\n)\n\nclass InvalidImportDetector : Detector(), Detector.UastScanner {\n\n    override fun getApplicableUastTypes() = listOf(UImportStatement::class.java)\n\n    override fun createUastHandler(context: JavaContext) = InvalidImportHandler(context)\n\n    class InvalidImportHandler(private val context: JavaContext) : UElementHandler() {\n\n        override fun visitImportStatement(node: UImportStatement) {\n            var importedPackageName = \"\"\n            val classPackageName = context.uastFile?.packageName.toString()\n\n            node.importReference?.let {\n                importedPackageName = it.asSourceString()\n            }\n\n            val classPackageSubFolders = classPackageName.split(\".\")\n            val importedPackageSubFolders = importedPackageName.split(\".\")\n\n            var i = 0\n            while (i < classPackageSubFolders.size && i < importedPackageSubFolders.size) {\n                if (classPackageSubFolders[i] == \"kotlin\" && importedPackageSubFolders[i] == \"java\") {\n                    node.importReference?.let {\n                        context.report(ISSUE_INVALID_IMPORT, node, context.getLocation(it), SHORT_MESSAGE)\n                    }\n                }\n                i++\n            }\n        }\n    }\n\n    companion object {\n        const val SHORT_MESSAGE = \"Invalid Import: java package imported from kotlin package.\"\n    }\n}\n"
  },
  {
    "path": "internal/lint/bin/main/com/firebase/lint/QuickstartIssueRegistry.kt",
    "content": "package com.firebase.lint\n\nimport com.android.tools.lint.client.api.IssueRegistry\nimport com.android.tools.lint.detector.api.CURRENT_API\nimport com.android.tools.lint.detector.api.Issue\n\n@Suppress(\"unused\")\nclass QuickstartIssueRegistry : IssueRegistry() {\n\n    override val api = CURRENT_API\n\n    override val issues: List<Issue>\n        get() = listOf(ISSUE_INVALID_IMPORT, ISSUE_HUNGARIAN_NOTATION)\n}\n"
  },
  {
    "path": "internal/lint/bin/test/com/firebase/lint/InvalidImportDetectorTest.kt",
    "content": "package com.firebase.lint\n\nimport com.android.tools.lint.checks.infrastructure.LintDetectorTest.java\nimport com.android.tools.lint.checks.infrastructure.TestLintTask.lint\nimport com.firebase.lint.InvalidImportDetector.Companion.SHORT_MESSAGE\nimport org.junit.Test\n\nclass InvalidImportDetectorTest {\n\n    private val javaPackage = java(\n        \"\"\"\n      package com.google.firebase.java;\n\n      public final class Hello {\n        public static final class drawable {\n        }\n      }\"\"\",\n    ).indented()\n\n    @Test\n    fun normalRImport() {\n        lint()\n            .allowMissingSdk()\n            .files(\n                javaPackage,\n                java(\n                    \"\"\"\n          package com.google.firebase.kotlin;\n\n          import com.google.firebase.Hello;\n\n          class Example {\n          }\"\"\",\n                ).indented(),\n            )\n            .issues(ISSUE_INVALID_IMPORT)\n            .run()\n            .expectClean()\n    }\n\n    @Test\n    fun wrongImport() {\n        lint()\n            .allowMissingSdk()\n            .files(\n                javaPackage,\n                java(\n                    \"\"\"\n          package com.google.firebase.kotlin;\n\n          import com.google.firebase.java.Hello;\n\n          class Example {\n          }\"\"\",\n                ).indented(),\n            )\n            .issues(ISSUE_INVALID_IMPORT)\n            .run()\n            .expect(\n                \"\"\"\n          |src/com/google/firebase/kotlin/Example.java:3: Error: $SHORT_MESSAGE [SuspiciousImport]\n          |import com.google.firebase.java.Hello;\n          |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          |1 errors, 0 warnings\n                \"\"\".trimMargin(),\n            )\n    }\n}\n"
  },
  {
    "path": "internal/lint/build.gradle.kts",
    "content": "plugins {\n    id(\"java-library\")\n    id(\"kotlin\")\n}\n\njava {\n    targetCompatibility = JavaVersion.VERSION_17\n    sourceCompatibility = JavaVersion.VERSION_17\n}\n\ndependencies {\n    compileOnly(\"com.android.tools.lint:lint-api:31.13.2\")\n    testImplementation(\"com.android.tools.lint:lint:31.13.2\")\n    testImplementation(\"com.android.tools.lint:lint-tests:31.13.2\")\n    testImplementation(\"junit:junit:4.13.2\")\n}\n"
  },
  {
    "path": "internal/lint/src/main/java/com/firebase/lint/HungarianNotationDetector.kt",
    "content": "package com.firebase.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity\nimport org.jetbrains.uast.UField\n\nval ISSUE_HUNGARIAN_NOTATION = Issue.create(\n    \"HungarianNotation\",\n    \"Using mHungarianNotation in a Kotlin file!\",\n    \"mFriends don’t let sFriends use Hungarian notation! -Jake Wharton\",\n    Category.MESSAGES,\n    9,\n    Severity.ERROR,\n    Implementation(\n        HungarianNotationDetector::class.java,\n        Scope.JAVA_FILE_SCOPE,\n    ),\n)\n\nclass HungarianNotationDetector : Detector(), Detector.UastScanner {\n\n    override fun getApplicableUastTypes() = listOf(UField::class.java)\n\n    override fun createUastHandler(context: JavaContext) = HungarianNotationHandler(context)\n\n    class HungarianNotationHandler(private val context: JavaContext) : UElementHandler() {\n\n        override fun visitField(node: UField) {\n            val varName = node.name\n            val isKotlin = context.file.name.endsWith(\"kt\")\n            val isHungarian = varName.matches(RE_HUNGARIAN)\n\n            if (isKotlin && isHungarian) {\n                node.uastAnchor?.let {\n                    context.report(ISSUE_HUNGARIAN_NOTATION, node, context.getLocation(it), SHORT_MESSAGE)\n                }\n            }\n        }\n    }\n\n    companion object {\n        const val SHORT_MESSAGE = \"Invalid Field Name: hungarian notation in a Kotlin file.\"\n\n        val RE_HUNGARIAN = Regex(\"^m[A-Z].*\")\n    }\n}\n"
  },
  {
    "path": "internal/lint/src/main/java/com/firebase/lint/InvalidImportDetector.kt",
    "content": "package com.firebase.lint\n\nimport com.android.tools.lint.client.api.UElementHandler\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Scope\nimport com.android.tools.lint.detector.api.Severity\nimport org.jetbrains.uast.UImportStatement\n\nval ISSUE_INVALID_IMPORT = Issue.create(\n    \"SuspiciousImport\",\n    \"importing files from the `java` package in a kotlin file\",\n    \"Importing files from the java package is usually not intentional; it sometimes happens when \" +\n        \"you have classes with the same name in both `java` and `kotlin` package.\",\n    Category.CORRECTNESS,\n    9,\n    Severity.ERROR,\n    Implementation(\n        InvalidImportDetector::class.java,\n        Scope.JAVA_FILE_SCOPE,\n    ),\n)\n\nclass InvalidImportDetector : Detector(), Detector.UastScanner {\n\n    override fun getApplicableUastTypes() = listOf(UImportStatement::class.java)\n\n    override fun createUastHandler(context: JavaContext) = InvalidImportHandler(context)\n\n    class InvalidImportHandler(private val context: JavaContext) : UElementHandler() {\n\n        override fun visitImportStatement(node: UImportStatement) {\n            var importedPackageName = \"\"\n            val classPackageName = context.uastFile?.packageName.toString()\n\n            node.importReference?.let {\n                importedPackageName = it.asSourceString()\n            }\n\n            val classPackageSubFolders = classPackageName.split(\".\")\n            val importedPackageSubFolders = importedPackageName.split(\".\")\n\n            var i = 0\n            while (i < classPackageSubFolders.size && i < importedPackageSubFolders.size) {\n                if (classPackageSubFolders[i] == \"kotlin\" && importedPackageSubFolders[i] == \"java\") {\n                    node.importReference?.let {\n                        context.report(ISSUE_INVALID_IMPORT, node, context.getLocation(it), SHORT_MESSAGE)\n                    }\n                }\n                i++\n            }\n        }\n    }\n\n    companion object {\n        const val SHORT_MESSAGE = \"Invalid Import: java package imported from kotlin package.\"\n    }\n}\n"
  },
  {
    "path": "internal/lint/src/main/java/com/firebase/lint/QuickstartIssueRegistry.kt",
    "content": "package com.firebase.lint\n\nimport com.android.tools.lint.client.api.IssueRegistry\nimport com.android.tools.lint.detector.api.CURRENT_API\nimport com.android.tools.lint.detector.api.Issue\n\n@Suppress(\"unused\")\nclass QuickstartIssueRegistry : IssueRegistry() {\n\n    override val api = CURRENT_API\n\n    override val issues: List<Issue>\n        get() = listOf(ISSUE_INVALID_IMPORT, ISSUE_HUNGARIAN_NOTATION)\n}\n"
  },
  {
    "path": "internal/lint/src/test/java/com/firebase/lint/InvalidImportDetectorTest.kt",
    "content": "package com.firebase.lint\n\nimport com.android.tools.lint.checks.infrastructure.LintDetectorTest.java\nimport com.android.tools.lint.checks.infrastructure.TestLintTask.lint\nimport com.firebase.lint.InvalidImportDetector.Companion.SHORT_MESSAGE\nimport org.junit.Test\n\nclass InvalidImportDetectorTest {\n\n    private val javaPackage = java(\n        \"\"\"\n      package com.google.firebase.java;\n\n      public final class Hello {\n        public static final class drawable {\n        }\n      }\"\"\",\n    ).indented()\n\n    @Test\n    fun normalRImport() {\n        lint()\n            .allowMissingSdk()\n            .files(\n                javaPackage,\n                java(\n                    \"\"\"\n          package com.google.firebase.java;\n\n          import com.google.firebase.java.Hello;\n\n          class Example {\n          }\"\"\",\n                ).indented(),\n            )\n            .issues(ISSUE_INVALID_IMPORT)\n            .run()\n            .expectClean()\n    }\n\n    @Test\n    fun wrongImport() {\n        lint()\n            .allowMissingSdk()\n            .files(\n                javaPackage,\n                java(\n                    \"\"\"\n          package com.google.firebase.kotlin;\n\n          import com.google.firebase.java.Hello;\n\n          class Example {\n          }\"\"\",\n                ).indented(),\n            )\n            .issues(ISSUE_INVALID_IMPORT)\n            .run()\n            .expect(\n                \"\"\"\n          |src/com/google/firebase/kotlin/Example.java:3: Error: $SHORT_MESSAGE [SuspiciousImport]\n          |import com.google.firebase.java.Hello;\n          |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n          |1 errors, 0 warnings\n                \"\"\".trimMargin(),\n            )\n    }\n}\n"
  },
  {
    "path": "internal/lintchecks/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "internal/lintchecks/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\nandroid {\n    namespace = \"com.firebase.lintchecks\"\n    compileSdk = 36\n\n    defaultConfig {\n        minSdk = 16\n    }\n\n    lint {\n        targetSdk = 36\n    }\n}\n\ndependencies {\n    lintChecks(project(\":internal:lint\"))\n}\n"
  },
  {
    "path": "internal/lintchecks/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "internal/lintchecks/src/main/AndroidManifest.xml",
    "content": "<manifest />\n"
  },
  {
    "path": "messaging/.gitignore",
    "content": ".gradle\n.idea/\n*.iml\nbuild/\n/local.properties\n.DS_Store\n/captures\ngoogle-services.json\n"
  },
  {
    "path": "messaging/README.md",
    "content": "Firebase Cloud Messaging Quickstart\n==============================\n\nThe Firebase Cloud Messaging Android Quickstart app demonstrates registering\nan Android app for notifications and handling the receipt of a message.\n**InstanceID** allows easy registration while **FirebaseMessagingService** and **FirebaseInstanceIDService**\nenable token refreshes and message handling on the client.\n\nIntroduction\n------------\n\n- [Read more about Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n- Run the sample on Android device or emulator.\n\n**NOTE**: This sample contains identical code for both Java and Kotlin.\nHowever, there can only be one service in each app that receives FCM\nmessages. If multiple are declared in the Manifest then the first\none will be chosen.\n\nIn order to make the Java messaging sample functional, you must\nremove the following from the `.kotlin.MyFirebaseMessagingService` entry\nin the `AndroidManifest.xml`:\n\n```\n<intent-filter>\n  <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n</intent-filter>\n```\n\nSending Notifications\n---------------------\n\nUse Firebase console to send FCM messages to device or emulator.\n\n## Send to a single device\n\n- From Firebase console Notification section, click **New Message**.\n- Enter the text of your message in the Message Text field.\n- Set the target to **Single Device**.\n- Check the logs for the **InstanceID** token, copy and paste it into the Firebase console Token field.\n  - If you cannot find the token in your logs, click on the **LOG TOKEN** button in the application and the token will\n  be logged in **logcat**.\n- Click on the **Send Message** button.\n- If your application is in the foreground you should see the incoming\n  message printed in the logs. If it is in the background, a system notification should be\n  displayed. When the notification is tapped, the application should return to the quickstart application.\n\n## Send to a topic\n\n- From Firebase console Notification section, click **New Message**.\n- Enter the text of your message in the Message Text field.\n- Click on the **SUBSCRIBE TO NEWS** button to subscribe to the news topic.\n- Set the target to **Topic**.\n- Select the news topic from the list of topics (\"news\" in this sample). \n  You must subscribe from the device or emulator before the topic will will be visible in the console.\n- Click on the **Send Message** button.\n- If your application is in the foreground you should see the incoming\n  message printed in the logs. If it is in the background, a system notification should be\n  displayed. When the notification is tapped, the application should return to the quickstart application.\n\nBest Practices\n--------------\n\n## Android notification channels\n\n### Set default channel\n\nIf incoming FCM messages do not specify an Android notification channel, you can indicate\nto FCM what channel should be used as the default by adding a metadata element to your\napplication manifest. In the metadata element specify the ID of the channel that should\nbe used by default by FCM.\n\n    <meta-data\n        android:name=\"com.google.firebase.messaging.default_notification_channel_id\"\n        android:value=\"default_channel_id\"/>\n\nNote: You are still required to create a notification channel in code with an ID that\nmatches the one defined in the manifest. See the Android [docs](https://goo.gl/x9fh5X) for more.\n\n## Customize default notification\n\n### Custom default icon\n\nSetting a custom default icon allows you to specify what icon is used for notification\nmessages if no icon is set in the notification payload. Also use the custom default\nicon to set the icon used by notification messages sent from the Firebase console.\nIf no custom default icon is set and no icon is set in the notification payload,\nthe application icon (rendered in white) is used.\n\n### Custom default Color\n\nYou can also define what color is used with your notification. Different android\nversions use this settings in different ways: Android < N use this as background color\nfor the icon. Android >= N use this to color the icon and the app name.\n\nSee the [docs](https://firebase.google.com/docs/cloud-messaging/android/receive#edit-the-app-manifest) for more.\n\nResult\n-----------\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-cloud-messaging)\n- [Firebase Support](https://firebase.google.com/support/)\n\nLicense\n-------\n\nCopyright 2016 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "messaging/app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nplugins {\n    id(\"com.android.application\")\n    id(\"com.google.gms.google-services\")\n}\n\ntasks {\n    check.dependsOn(\"assembleDebugAndroidTest\")\n}\n\nandroid {\n    namespace = \"com.google.firebase.quickstart.fcm\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.quickstart.fcm\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n    packaging {\n        resources.excludes.add(\"LICENSE.txt\")\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n    lint {\n        abortOnError = false\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n    implementation(\"androidx.annotation:annotation:1.9.1\")\n    implementation(\"androidx.vectordrawable:vectordrawable-animated:1.2.0\")\n    implementation(\"androidx.core:core-ktx:1.17.0\")\n\n    // Required when asking for permission to post notifications (starting in Android 13)\n    implementation(\"androidx.activity:activity-ktx:1.12.1\")\n    implementation(\"androidx.fragment:fragment-ktx:1.8.9\")\n\n    implementation(\"com.google.android.material:material:1.13.0\")\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Firebase Cloud Messaging\n    implementation(\"com.google.firebase:firebase-messaging\")\n\n    // For an optimal experience using FCM, add the Firebase SDK\n    // for Google Analytics. This is recommended, but not required.\n    implementation(\"com.google.firebase:firebase-analytics\")\n\n    implementation(\"com.google.firebase:firebase-installations:19.0.1\")\n\n    implementation(\"androidx.work:work-runtime:2.11.0\")\n\n    // Testing dependencies\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.annotation:annotation:1.9.1\")\n}\n"
  },
  {
    "path": "messaging/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n"
  },
  {
    "path": "messaging/app/src/androidTest/java/com/google/firebase/quickstart/fcm/MainActivityEspressoTest.java",
    "content": "/**\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.fcm;\n\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.runner.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\nimport android.view.View;\n\nimport com.google.firebase.quickstart.fcm.java.MainActivity;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.assertion.ViewAssertions.matches;\nimport static androidx.test.espresso.matcher.RootMatchers.withDecorView;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static androidx.test.espresso.matcher.ViewMatchers.withId;\nimport static androidx.test.espresso.matcher.ViewMatchers.withText;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.is;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.startsWith;\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class MainActivityEspressoTest {\n\n    @Rule\n    public ActivityTestRule<MainActivity> mActivityRule =\n            new ActivityTestRule<>(MainActivity.class);\n\n    @Test\n    public void testSubscribeAndLog() throws InterruptedException {\n        onView(withId(R.id.informationTextView)).check(matches(isDisplayed()));\n\n        // Click subscribe button and check toast\n        onView(allOf(withId(R.id.subscribeButton), withText(R.string.subscribe_to_weather)))\n                .check(matches(isDisplayed()))\n                .perform(click());\n        confirmToastStartsWith(mActivityRule.getActivity().getString(R.string.msg_subscribed));\n\n        // Sleep so the Toast goes away, this is lazy but it works (Toast.LENGTH_SHORT = 2000)\n        Thread.sleep(2000);\n\n        // Click log token and check toast\n        onView(allOf(withId(R.id.logTokenButton), withText(R.string.log_token)))\n                .check(matches(isDisplayed()))\n                .perform(click());\n        confirmToastStartsWith(mActivityRule.getActivity().getString(R.string.msg_token_fmt, \"\"));\n    }\n\n    private void confirmToastStartsWith(String string) {\n        View activityWindowDecorView = mActivityRule.getActivity().getWindow().getDecorView();\n        onView(withText(startsWith(string)))\n                .inRoot(withDecorView(not(is(activityWindowDecorView))))\n                .check(matches(isDisplayed()));\n    }\n\n}\n"
  },
  {
    "path": "messaging/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n        <!-- [START fcm_default_icon] -->\n        <!-- Set custom default icon. This is used when no icon is set for incoming notification messages.\n             See README(https://goo.gl/l4GJaQ) for more. -->\n        <meta-data\n            android:name=\"com.google.firebase.messaging.default_notification_icon\"\n            android:resource=\"@drawable/ic_stat_ic_notification\" />\n        <!-- Set color used with incoming notification messages. This is used when no color is set for the incoming\n             notification message. See README(https://goo.gl/6BKBk7) for more. -->\n        <meta-data\n            android:name=\"com.google.firebase.messaging.default_notification_color\"\n            android:resource=\"@color/colorAccent\" />\n        <!-- [END fcm_default_icon] -->\n        <!-- [START fcm_default_channel] -->\n        <meta-data\n            android:name=\"com.google.firebase.messaging.default_notification_channel_id\"\n            android:value=\"@string/default_notification_channel_id\" />\n        <!-- [END fcm_default_channel] -->\n        <activity\n            android:name=\".EntryChoiceActivity\"\n            android:label=\"@string/app_name\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity android:name=\".kotlin.MainActivity\" />\n        <activity android:name=\".java.MainActivity\" />\n\n        <service\n            android:name=\".kotlin.MyFirebaseMessagingService\"\n            android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n            </intent-filter>\n        </service>\n\n        <!-- [START firebase_service] -->\n        <service\n            android:name=\".java.MyFirebaseMessagingService\"\n            android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n            </intent-filter>\n        </service>\n        <!-- [END firebase_service] -->\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "messaging/app/src/main/java/com/google/firebase/quickstart/fcm/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.quickstart.fcm\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Cloud Messaging quickstart written in Java.\",\n                Intent(this, com.google.firebase.quickstart.fcm.java.MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Cloud Messaging written in Kotlin.\",\n                Intent(this, com.google.firebase.quickstart.fcm.kotlin.MainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "messaging/app/src/main/java/com/google/firebase/quickstart/fcm/java/MainActivity.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.fcm.java;\n\nimport android.Manifest;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.content.ContextCompat;\n\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.messaging.FirebaseMessaging;\nimport com.google.firebase.quickstart.fcm.R;\nimport com.google.firebase.quickstart.fcm.databinding.ActivityMainBinding;\n\npublic class MainActivity extends AppCompatActivity {\n\n    private static final String TAG = \"MainActivity\";\n    private final ActivityResultLauncher<String> requestPermissionLauncher =\n            registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {\n                if (isGranted) {\n                    Toast.makeText(this, \"Notifications permission granted\",Toast.LENGTH_SHORT)\n                            .show();\n                } else {\n                    Toast.makeText(this, \"FCM can't post notifications without POST_NOTIFICATIONS permission\",\n                            Toast.LENGTH_LONG).show();\n                }\n            });\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            // Create channel to show notifications.\n            String channelId  = getString(R.string.default_notification_channel_id);\n            String channelName = getString(R.string.default_notification_channel_name);\n            NotificationManager notificationManager =\n                    getSystemService(NotificationManager.class);\n            notificationManager.createNotificationChannel(new NotificationChannel(channelId,\n                    channelName, NotificationManager.IMPORTANCE_LOW));\n        }\n\n        // If a notification message is tapped, any data accompanying the notification\n        // message is available in the intent extras. In this sample the launcher\n        // intent is fired when the notification is tapped, so any accompanying data would\n        // be handled here. If you want a different intent fired, set the click_action\n        // field of the notification message to the desired intent. The launcher intent\n        // is used when no click_action is specified.\n        //\n        // Handle possible data accompanying notification message.\n        // [START handle_data_extras]\n        if (getIntent().getExtras() != null) {\n            for (String key : getIntent().getExtras().keySet()) {\n                Object value = getIntent().getExtras().get(key);\n                Log.d(TAG, \"Key: \" + key + \" Value: \" + value);\n            }\n        }\n        // [END handle_data_extras]\n\n        binding.subscribeButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                Log.d(TAG, \"Subscribing to weather topic\");\n                // [START subscribe_topics]\n                FirebaseMessaging.getInstance().subscribeToTopic(\"weather\")\n                        .addOnCompleteListener(new OnCompleteListener<Void>() {\n                            @Override\n                            public void onComplete(@NonNull Task<Void> task) {\n                                String msg = getString(R.string.msg_subscribed);\n                                if (!task.isSuccessful()) {\n                                    msg = getString(R.string.msg_subscribe_failed);\n                                }\n                                Log.d(TAG, msg);\n                                Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();\n                            }\n                        });\n                // [END subscribe_topics]\n            }\n        });\n\n        binding.logTokenButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                // Get token\n                // [START log_reg_token]\n                FirebaseMessaging.getInstance().getToken()\n                    .addOnCompleteListener(new OnCompleteListener<String>() {\n                        @Override\n                        public void onComplete(@NonNull Task<String> task) {\n                          if (!task.isSuccessful()) {\n                            Log.w(TAG, \"Fetching FCM registration token failed\", task.getException());\n                            return;\n                          }\n\n                          // Get new FCM registration token\n                          String token = task.getResult();\n\n                          // Log and toast\n                          String msg = getString(R.string.msg_token_fmt, token);\n                          Log.d(TAG, msg);\n                          Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();\n                        }\n                    });\n                // [END log_reg_token]\n            }\n        });\n\n        askNotificationPermission();\n    }\n\n    private void askNotificationPermission() {\n        // This is only necessary for API Level > 33 (TIRAMISU)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==\n                    PackageManager.PERMISSION_GRANTED) {\n                // FCM SDK (and your app) can post notifications.\n            } else {\n                // Directly ask for the permission\n                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "messaging/app/src/main/java/com/google/firebase/quickstart/fcm/java/MyFirebaseMessagingService.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.fcm.java;\n\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.media.RingtoneManager;\nimport android.net.Uri;\nimport android.os.Build;\nimport androidx.core.app.NotificationCompat;\nimport android.util.Log;\n\nimport com.google.firebase.messaging.FirebaseMessagingService;\nimport com.google.firebase.messaging.RemoteMessage;\nimport com.google.firebase.quickstart.fcm.R;\n\nimport androidx.work.OneTimeWorkRequest;\nimport androidx.work.WorkManager;\n\n/**\n * NOTE: There can only be one service in each app that receives FCM messages. If multiple\n * are declared in the Manifest then the first one will be chosen.\n *\n * In order to make this Java sample functional, you must remove the following from the Kotlin messaging\n * service in the AndroidManifest.xml:\n *\n * <intent-filter>\n *   <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n * </intent-filter>\n */\npublic class MyFirebaseMessagingService extends FirebaseMessagingService {\n\n    private static final String TAG = \"MyFirebaseMsgService\";\n\n    /**\n     * Called when message is received.\n     *\n     * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.\n     */\n    // [START receive_message]\n    @Override\n    public void onMessageReceived(RemoteMessage remoteMessage) {\n        // [START_EXCLUDE]\n        // There are two types of messages data messages and notification messages. Data messages\n        // are handled\n        // here in onMessageReceived whether the app is in the foreground or background. Data\n        // messages are the type\n        // traditionally used with GCM. Notification messages are only received here in\n        // onMessageReceived when the app\n        // is in the foreground. When the app is in the background an automatically generated\n        // notification is displayed.\n        // When the user taps on the notification they are returned to the app. Messages\n        // containing both notification\n        // and data payloads are treated as notification messages. The Firebase console always\n        // sends notification\n        // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options\n        // [END_EXCLUDE]\n\n        // TODO(developer): Handle FCM messages here.\n        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ\n        Log.d(TAG, \"From: \" + remoteMessage.getFrom());\n\n        // Check if message contains a data payload.\n        if (remoteMessage.getData().size() > 0) {\n            Log.d(TAG, \"Message data payload: \" + remoteMessage.getData());\n\n            if (/* Check if data needs to be processed by long running job */ true) {\n                // For long-running tasks (10 seconds or more) use WorkManager.\n                scheduleJob();\n            } else {\n                // Handle message within 10 seconds\n                handleNow();\n            }\n\n        }\n\n        // Check if message contains a notification payload.\n        if (remoteMessage.getNotification() != null) {\n            Log.d(TAG, \"Message Notification Body: \" + remoteMessage.getNotification().getBody());\n            String notificationBody = remoteMessage.getNotification().getBody();\n            if (remoteMessage.getNotification().getBody() != null) {\n                sendNotification(notificationBody);\n            }\n        }\n\n        // Also if you intend on generating your own notifications as a result of a received FCM\n        // message, here is where that should be initiated. See sendNotification method below.\n    }\n    // [END receive_message]\n\n\n    // [START on_new_token]\n    /**\n     * There are two scenarios when onNewToken is called:\n     * 1) When a new token is generated on initial app startup\n     * 2) Whenever an existing token is changed\n     * Under #2, there are three scenarios when the existing token is changed:\n     * A) App is restored to a new device\n     * B) User uninstalls/reinstalls the app\n     * C) User clears app data\n     */\n    @Override\n    public void onNewToken(String token) {\n        Log.d(TAG, \"Refreshed token: \" + token);\n\n        // If you want to send messages to this application instance or\n        // manage this apps subscriptions on the server side, send the\n        // FCM registration token to your app server.\n        sendRegistrationToServer(token);\n    }\n    // [END on_new_token]\n\n    /**\n     * Schedule async work using WorkManager.\n     */\n    private void scheduleJob() {\n        // [START dispatch_job]\n        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(MyWorker.class)\n                .build();\n        WorkManager.getInstance(this).beginWith(work).enqueue();\n        // [END dispatch_job]\n    }\n\n    /**\n     * Handle time allotted to BroadcastReceivers.\n     */\n    private void handleNow() {\n        Log.d(TAG, \"Short lived task is done.\");\n    }\n\n    /**\n     * Persist token to third-party servers.\n     *\n     * Modify this method to associate the user's FCM registration token with any\n     * server-side account maintained by your application.\n     *\n     * @param token The new token.\n     */\n    private void sendRegistrationToServer(String token) {\n        // TODO: Implement this method to send token to your app server.\n    }\n\n    /**\n     * Create and show a simple notification containing the received FCM message.\n     *\n     * @param messageBody FCM message body received.\n     */\n    private void sendNotification(String messageBody) {\n        Intent intent = new Intent(this, MainActivity.class);\n        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\n        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,\n                PendingIntent.FLAG_IMMUTABLE);\n\n        String channelId = getString(R.string.default_notification_channel_id);\n        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);\n        NotificationCompat.Builder notificationBuilder =\n                new NotificationCompat.Builder(this, channelId)\n                        .setSmallIcon(R.drawable.ic_stat_ic_notification)\n                        .setContentTitle(getString(R.string.fcm_message))\n                        .setContentText(messageBody)\n                        .setAutoCancel(true)\n                        .setSound(defaultSoundUri)\n                        .setContentIntent(pendingIntent);\n\n        NotificationManager notificationManager =\n                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n\n        // Since android Oreo notification channel is needed.\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            NotificationChannel channel = new NotificationChannel(channelId,\n                    \"Channel human readable title\",\n                    NotificationManager.IMPORTANCE_DEFAULT);\n            notificationManager.createNotificationChannel(channel);\n        }\n\n        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());\n    }\n}\n"
  },
  {
    "path": "messaging/app/src/main/java/com/google/firebase/quickstart/fcm/java/MyWorker.java",
    "content": "package com.google.firebase.quickstart.fcm.java;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport android.util.Log;\n\nimport androidx.work.Worker;\nimport androidx.work.WorkerParameters;\n\npublic class MyWorker extends Worker {\n\n    private static final String TAG = \"MyWorker\";\n\n    public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {\n        super(appContext, workerParams);\n    }\n\n    @NonNull\n    @Override\n    public Result doWork() {\n        Log.d(TAG, \"Performing long running task in scheduled job\");\n        // TODO(developer): add long running task here.\n        return Result.success();\n    }\n}\n"
  },
  {
    "path": "messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt",
    "content": "package com.google.firebase.quickstart.fcm.kotlin\n\nimport android.Manifest\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.Log\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.content.ContextCompat\nimport com.google.android.gms.tasks.OnCompleteListener\nimport com.google.firebase.Firebase\nimport com.google.firebase.messaging.messaging\nimport com.google.firebase.quickstart.fcm.R\nimport com.google.firebase.quickstart.fcm.databinding.ActivityMainBinding\n\nclass MainActivity : AppCompatActivity() {\n\n    private val requestPermissionLauncher = registerForActivityResult(\n        ActivityResultContracts.RequestPermission(),\n    ) { isGranted: Boolean ->\n        if (isGranted) {\n            Toast.makeText(this, \"Notifications permission granted\", Toast.LENGTH_SHORT)\n                .show()\n        } else {\n            Toast.makeText(\n                this,\n                \"FCM can't post notifications without POST_NOTIFICATIONS permission\",\n                Toast.LENGTH_LONG,\n            ).show()\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            // Create channel to show notifications.\n            val channelId = getString(R.string.default_notification_channel_id)\n            val channelName = getString(R.string.default_notification_channel_name)\n            val notificationManager = getSystemService(NotificationManager::class.java)\n            notificationManager?.createNotificationChannel(\n                NotificationChannel(\n                    channelId,\n                    channelName,\n                    NotificationManager.IMPORTANCE_LOW,\n                ),\n            )\n        }\n\n        // If a notification message is tapped, any data accompanying the notification\n        // message is available in the intent extras. In this sample the launcher\n        // intent is fired when the notification is tapped, so any accompanying data would\n        // be handled here. If you want a different intent fired, set the click_action\n        // field of the notification message to the desired intent. The launcher intent\n        // is used when no click_action is specified.\n        //\n        // Handle possible data accompanying notification message.\n        // [START handle_data_extras]\n        intent.extras?.let {\n            for (key in it.keySet()) {\n                val value = intent.extras?.getString(key)\n                Log.d(TAG, \"Key: $key Value: $value\")\n            }\n        }\n        // [END handle_data_extras]\n\n        binding.subscribeButton.setOnClickListener {\n            Log.d(TAG, \"Subscribing to weather topic\")\n            // [START subscribe_topics]\n            Firebase.messaging.subscribeToTopic(\"weather\")\n                .addOnCompleteListener { task ->\n                    var msg = getString(R.string.msg_subscribed)\n                    if (!task.isSuccessful) {\n                        msg = getString(R.string.msg_subscribe_failed)\n                    }\n                    Log.d(TAG, msg)\n                    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()\n                }\n            // [END subscribe_topics]\n        }\n\n        binding.logTokenButton.setOnClickListener {\n            // Get token\n            // [START log_reg_token]\n            Firebase.messaging.token.addOnCompleteListener(\n                OnCompleteListener { task ->\n                    if (!task.isSuccessful) {\n                        Log.w(TAG, \"Fetching FCM registration token failed\", task.exception)\n                        return@OnCompleteListener\n                    }\n\n                    // Get new FCM registration token\n                    val token = task.result\n\n                    // Log and toast\n                    val msg = getString(R.string.msg_token_fmt, token)\n                    Log.d(TAG, msg)\n                    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()\n                },\n            )\n            // [END log_reg_token]\n        }\n\n        Toast.makeText(this, \"See README for setup instructions\", Toast.LENGTH_SHORT).show()\n        askNotificationPermission()\n    }\n\n    private fun askNotificationPermission() {\n        // This is only necessary for API Level > 33 (TIRAMISU)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==\n                PackageManager.PERMISSION_GRANTED\n            ) {\n                // FCM SDK (and your app) can post notifications.\n            } else {\n                // Directly ask for the permission\n                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)\n            }\n        }\n    }\n\n    companion object {\n\n        private const val TAG = \"MainActivity\"\n    }\n}\n"
  },
  {
    "path": "messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt",
    "content": "package com.google.firebase.quickstart.fcm.kotlin\n\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.media.RingtoneManager\nimport android.os.Build\nimport android.util.Log\nimport androidx.core.app.NotificationCompat\nimport androidx.work.OneTimeWorkRequest\nimport androidx.work.WorkManager\nimport com.google.firebase.messaging.FirebaseMessagingService\nimport com.google.firebase.messaging.RemoteMessage\nimport com.google.firebase.quickstart.fcm.R\n\nclass MyFirebaseMessagingService : FirebaseMessagingService() {\n\n    /**\n     * Called when message is received.\n     *\n     * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.\n     */\n    // [START receive_message]\n    override fun onMessageReceived(remoteMessage: RemoteMessage) {\n        // [START_EXCLUDE]\n        // There are two types of messages data messages and notification messages. Data messages are handled\n        // here in onMessageReceived whether the app is in the foreground or background. Data messages are the type\n        // traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app\n        // is in the foreground. When the app is in the background an automatically generated notification is displayed.\n        // When the user taps on the notification they are returned to the app. Messages containing both notification\n        // and data payloads are treated as notification messages. The Firebase console always sends notification\n        // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options\n        // [END_EXCLUDE]\n\n        // TODO(developer): Handle FCM messages here.\n        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ\n        Log.d(TAG, \"From: ${remoteMessage.from}\")\n\n        // Check if message contains a data payload.\n        if (remoteMessage.data.isNotEmpty()) {\n            Log.d(TAG, \"Message data payload: ${remoteMessage.data}\")\n\n            // Check if data needs to be processed by long running job\n            if (isLongRunningJob()) {\n                // For long-running tasks (10 seconds or more) use WorkManager.\n                scheduleJob()\n            } else {\n                // Handle message within 10 seconds\n                handleNow()\n            }\n        }\n\n        // Check if message contains a notification payload.\n        remoteMessage.notification?.let {\n            Log.d(TAG, \"Message Notification Body: ${it.body}\")\n            it.body?.let { body -> sendNotification(body) }\n        }\n\n        // Also if you intend on generating your own notifications as a result of a received FCM\n        // message, here is where that should be initiated. See sendNotification method below.\n    }\n    // [END receive_message]\n\n    private fun isLongRunningJob() = true\n\n    // [START on_new_token]\n    /**\n     * Called if the FCM registration token is updated. This may occur if the security of\n     * the previous token had been compromised. Note that this is called when the\n     * FCM registration token is initially generated so this is where you would retrieve the token.\n     */\n    override fun onNewToken(token: String) {\n        Log.d(TAG, \"Refreshed token: $token\")\n\n        // If you want to send messages to this application instance or\n        // manage this apps subscriptions on the server side, send the\n        // FCM registration token to your app server.\n        sendRegistrationToServer(token)\n    }\n    // [END on_new_token]\n\n    /**\n     * Schedule async work using WorkManager.\n     */\n    private fun scheduleJob() {\n        // [START dispatch_job]\n        val work = OneTimeWorkRequest.Builder(MyWorker::class.java).build()\n        WorkManager.getInstance(this).beginWith(work).enqueue()\n        // [END dispatch_job]\n    }\n\n    /**\n     * Handle time allotted to BroadcastReceivers.\n     */\n    private fun handleNow() {\n        Log.d(TAG, \"Short lived task is done.\")\n    }\n\n    /**\n     * Persist token to third-party servers.\n     *\n     * Modify this method to associate the user's FCM registration token with any server-side account\n     * maintained by your application.\n     *\n     * @param token The new token.\n     */\n    private fun sendRegistrationToServer(token: String?) {\n        // TODO: Implement this method to send token to your app server.\n        Log.d(TAG, \"sendRegistrationTokenToServer($token)\")\n    }\n\n    /**\n     * Create and show a simple notification containing the received FCM message.\n     *\n     * @param messageBody FCM message body received.\n     */\n    private fun sendNotification(messageBody: String) {\n        val requestCode = 0\n        val intent = Intent(this, MainActivity::class.java)\n        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)\n        val pendingIntent = PendingIntent.getActivity(\n            this,\n            requestCode,\n            intent,\n            PendingIntent.FLAG_IMMUTABLE,\n        )\n\n        val channelId = getString(R.string.default_notification_channel_id)\n        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)\n        val notificationBuilder = NotificationCompat.Builder(this, channelId)\n            .setSmallIcon(R.drawable.ic_stat_ic_notification)\n            .setContentTitle(getString(R.string.fcm_message))\n            .setContentText(messageBody)\n            .setAutoCancel(true)\n            .setSound(defaultSoundUri)\n            .setContentIntent(pendingIntent)\n\n        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager\n\n        // Since android Oreo notification channel is needed.\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            val channel = NotificationChannel(\n                channelId,\n                \"Channel human readable title\",\n                NotificationManager.IMPORTANCE_DEFAULT,\n            )\n            notificationManager.createNotificationChannel(channel)\n        }\n\n        val notificationId = 0\n        notificationManager.notify(notificationId, notificationBuilder.build())\n    }\n\n    companion object {\n\n        private const val TAG = \"MyFirebaseMsgService\"\n    }\n}\n"
  },
  {
    "path": "messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyWorker.kt",
    "content": "package com.google.firebase.quickstart.fcm.kotlin\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.work.Worker\nimport androidx.work.WorkerParameters\n\nclass MyWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {\n\n    override fun doWork(): Result {\n        Log.d(TAG, \"Performing long running task in scheduled job\")\n        // TODO(developer): add long running task here.\n        return Result.success()\n    }\n\n    companion object {\n        private val TAG = \"MyWorker\"\n    }\n}\n"
  },
  {
    "path": "messaging/app/src/main/res/layout/activity_main.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    tools:context=\"com.google.firebase.quickstart.fcm.java.MainActivity\">\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"16dp\"\n        android:layout_gravity=\"center_horizontal\"\n        android:src=\"@drawable/firebase_lockup_400\" />\n\n    <TextView\n        android:id=\"@+id/informationTextView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_horizontal\"\n        android:gravity=\"center_horizontal\"\n        android:text=\"@string/quickstart_message\" />\n\n    <Button\n        android:id=\"@+id/subscribeButton\"\n        android:layout_width=\"@dimen/standard_field_width\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_horizontal\"\n        android:layout_marginTop=\"20dp\"\n        android:text=\"@string/subscribe_to_weather\" />\n\n    <Button\n        android:id=\"@+id/logTokenButton\"\n        android:layout_width=\"@dimen/standard_field_width\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_horizontal\"\n        android:text=\"@string/log_token\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "messaging/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n\n    <color name=\"blue_grey_500\">#607D8B</color>\n    <color name=\"blue_grey_600\">#546E7A</color>\n    <color name=\"blue_grey_700\">#455A64</color>\n    <color name=\"blue_grey_800\">#37474F</color>\n    <color name=\"blue_grey_900\">#263238</color>\n</resources>\n"
  },
  {
    "path": "messaging/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n\n    <dimen name=\"standard_field_width\">200dp</dimen>\n</resources>\n"
  },
  {
    "path": "messaging/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase Cloud Messaging</string>\n    <string name=\"quickstart_message\">Click the SUBSCRIBE TO WEATHER button below to subscribe to the\n        weather topic. Messages sent to the weather topic will be received. The LOG TOKEN button logs the\n        FCM registration token to logcat.</string>\n    <string name=\"subscribe_to_weather\">Subscribe To Weather</string>\n    <string name=\"log_token\">Log Token</string>\n\n    <string name=\"msg_subscribed\">Subscribed to weather topic</string>\n    <string name=\"msg_token_fmt\" translatable=\"false\">FCM registration Token: %s</string>\n\n    <string name=\"default_notification_channel_id\" translatable=\"false\">fcm_default_channel</string>\n    <!--\n        This is the name that users will see when interacting with this channel.\n        It should describe the category of notifications that will be sent through this channel\n     -->\n    <!-- [START fcm_default_icon_string] -->\n    <string name=\"default_notification_channel_name\" translatable=\"true\">Weather</string>\n    <!-- [END fcm_default_icon_string] -->\n    <string name=\"msg_subscribe_failed\">Failed to subscribe to weather topic</string>\n    <string name=\"fcm_message\">FCM Message</string>\n</resources>\n"
  },
  {
    "path": "messaging/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "messaging/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "messaging/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n"
  },
  {
    "path": "messaging/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "messaging/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "messaging/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "messaging/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "messaging/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "mock-google-services.json",
    "content": "{\n  \"project_info\": {\n    \"project_id\": \"mockproject-1234\",\n    \"project_number\": \"123456789000\",\n    \"name\": \"FirebaseQuickstarts\",\n    \"firebase_url\": \"https://mockproject-1234.firebaseio.com\"\n  },\n  \"client\": [\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.samples.quickstart.admobexample\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.samples.quickstart.admobexample\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.samples.quickstart.admobexample\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.quickstart.analytics\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.analytics\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.quickstart.analytics\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.samples.quickstart.appindexing\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.samples.quickstart.appindexing\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.samples.quickstart.appindexing\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.quickstart.auth\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.auth\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.quickstart.auth\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.samples.quickstart.config\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.samples.quickstart.config\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.samples.quickstart.config\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.samples.quickstart.crash\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.samples.quickstart.crash\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.samples.quickstart.crash\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.quickstart.database\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.database\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.quickstart.database\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.quickstart.deeplinks\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.deeplinks\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.quickstart.deeplinks\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.quickstart.invites\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.invites\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.quickstart.invites\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.example.fireeats\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.example.fireeats\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.example.fireeats\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.quickstart.fcm\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.fcm\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.quickstart.fcm\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"com.google.firebase.quickstart.perfmon\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.perfmon\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.quickstart.perfmon\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.quickstart.firebasestorage\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.firebasestorage\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.quickstart.firebasestorage\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.samples.quickstart.functions\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.samples.quickstart.functions\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.samples.quickstart.functions\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.samples.apps.mlkit\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.samples.apps.mlkit\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.samples.apps.mlkit\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.appdistributionquickstart\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.appdistributionquickstart\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.appdistributionquickstart\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"client_id\": \"android:com.google.firebase.fiamquickstart\",\n        \"client_type\": 1,\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.fiamquickstart\",\n          \"certificate_hash\": []\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.google.firebase.fiamquickstart\",\n            \"certificate_hash\": \"4C20644DE36B8F89D25650C7D1FF9FBAE650FDF7\"\n          }\n        },\n        {\n          \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"cloud_messaging_service\": {\n          \"status\": 2,\n          \"apns_config\": []\n        },\n        \"appinvite_service\": {\n          \"status\": 2,\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"123456789000-e4uksm38sne0bqrj6uvkbo4oiu4hvigl.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        },\n        \"google_signin_service\": {\n          \"status\": 2\n        },\n        \"ads_service\": {\n          \"status\": 2,\n          \"test_banner_ad_unit_id\": \"ca-app-pub-3940256099942544/6300978111\",\n          \"test_interstitial_ad_unit_id\": \"ca-app-pub-3940256099942544/1033173712\"\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:474448463284:android:c76572afd2f0ba8d97e8e1\",\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.example.dataconnect\"\n        }\n      },\n      \"oauth_client\": [],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyCPndbsEs_QWumL5_B0BpNLuMkvVSecvL0\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": []\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:474448463284:android:c76572afd2f0ba8d97e8e1\",\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.vertexai\"\n        }\n      },\n      \"oauth_client\": [],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyCPndbsEs_QWumL5_B0BpNLuMkvVSecvL0\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": []\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:474448463284:android:c76572afd2f0ba8d97e8e1\",\n        \"android_client_info\": {\n          \"package_name\": \"com.google.firebase.quickstart.ai\"\n        }\n      },\n      \"oauth_client\": [],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyCPndbsEs_QWumL5_B0BpNLuMkvVSecvL0\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": []\n        }\n      }\n    }\n  ],\n  \"client_info\": [],\n  \"ARTIFACT_VERSION\": \"1\"\n}\n"
  },
  {
    "path": "perf/.gitignore",
    "content": ".gradle\n*.iml\n/.idea/libraries\n/.idea/workspace.xml\n.DS_Store\n/build\n/local.properties\n.idea/\ngoogle-services.json\n"
  },
  {
    "path": "perf/README.md",
    "content": "Firebase Performance Quickstart\n==============================\n\nThe Firebase Performance Android Quickstart app demonstrates measuring the performance of\nactivities in your application.\n\nIntroduction\n------------\n\n- [Read more about Firebase Performance](https://firebase.google.com/docs/perf-mon/)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\n- Run the sample on Android device or emulator.\n\nResult\n-----------\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nSupport\n-------\n\n- [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-performance)\n- [Firebase Support](https://firebase.google.com/support/).\n\nLicense\n-------\n\nCopyright 2017 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "perf/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "perf/app/build.gradle.kts",
    "content": "\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.services)\n    alias(libs.plugins.firebase.perf)\n}\n\nandroid {\n    namespace = \"com.google.firebase.quickstart.perfmon\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.quickstart.perfmon\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n        getByName(\"debug\") {\n//            configure<FirebasePerfExtension> {\n//                 Set this flag to 'false' to disable @AddTrace annotation processing and\n//                 automatic HTTP/S network request monitoring\n//                 for a specific build variant at compile time.\n//                setInstrumentationEnabled(true)\n//            }\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n    lint {\n        // TODO(thatfiredev): Remove this once\n        //  https://github.com/bumptech/glide/issues/4940 is fixed\n        disable.add(\"NotificationPermission\")\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Firebase Performance Monitoring\n    implementation(\"com.google.firebase:firebase-perf\")\n\n    implementation(\"com.google.android.material:material:1.13.0\")\n    implementation(\"androidx.constraintlayout:constraintlayout:2.2.1\")\n    implementation(\"androidx.lifecycle:lifecycle-runtime-ktx:2.10.0\")\n\n    implementation(\"com.github.bumptech.glide:glide:4.12.0\")\n\n    testImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n}\n"
  },
  {
    "path": "perf/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /usr/local/google/home/gkal/Android/Sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "perf/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity android:name=\".java.MainActivity\"/>\n\n        <activity android:name=\".kotlin.MainActivity\"/>\n\n        <activity android:name=\".EntryChoiceActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "perf/app/src/main/assets/default_content.txt",
    "content": "This is the main text content.\n\nHere are some random strings:\nx3EIZZm4Z0J0ngq2S3L1jf2wr4DW\nH0OnfpDjhRZMAbV9hLFMUTrjqhoV\nKUuRv6ulGEme3layWx86qQrzHt1p\nJ5Qfx737fBhfOAobnt9CxilIgPQ2"
  },
  {
    "path": "perf/app/src/main/java/com/google/firebase/quickstart/perfmon/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.quickstart.perfmon\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Performance Monitoring quickstart written in Java.\",\n                Intent(\n                    this,\n                    com.google.firebase.quickstart.perfmon.java.MainActivity::class.java,\n                ),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase Performance Monitoring quickstart written in Kotlin.\",\n                Intent(\n                    this,\n                    com.google.firebase.quickstart.perfmon.kotlin.MainActivity::class.java,\n                ),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "perf/app/src/main/java/com/google/firebase/quickstart/perfmon/java/MainActivity.java",
    "content": "package com.google.firebase.quickstart.perfmon.java;\n\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.ContextCompat;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.google.android.gms.tasks.OnCompleteListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.gms.tasks.TaskCompletionSource;\nimport com.google.firebase.perf.FirebasePerformance;\nimport com.google.firebase.perf.metrics.Trace;\nimport com.google.firebase.quickstart.perfmon.R;\nimport com.google.firebase.quickstart.perfmon.databinding.ActivityMainBinding;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Random;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.Executors;\n\npublic class MainActivity extends AppCompatActivity {\n    private static final String TAG = \"MainActivity\";\n\n    private static final String DEFAULT_CONTENT_FILE = \"default_content.txt\";\n    private static final String CONTENT_FILE = \"content.txt\";\n    private static final String IMAGE_URL =\n            \"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png\";\n\n    private Trace mTrace;\n\n    private String STARTUP_TRACE_NAME = \"startup_trace\";\n    private String REQUESTS_COUNTER_NAME = \"requests sent\";\n    private String FILE_SIZE_COUNTER_NAME = \"file size\";\n    private CountDownLatch mNumStartupTasks = new CountDownLatch(2);\n\n    private ActivityMainBinding binding;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        binding.button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                // write 40 chars of random text to file\n                File contentFile = new File(MainActivity.this.getFilesDir(), CONTENT_FILE);\n\n                writeStringToFile(contentFile.getAbsolutePath(), getRandomString(40) + \"\\n\")\n                        .addOnCompleteListener(new OnCompleteListener<Void>() {\n                            @Override\n                            public void onComplete(@NonNull Task<Void> task) {\n                                if (!task.isSuccessful()) {\n                                    Log.e(TAG, \"Unable to write to file\", task.getException());\n                                    return;\n                                }\n\n                                loadFileFromDisk();\n                            }\n                        });\n                }\n        });\n\n        // Begin tracing app startup tasks.\n        mTrace = FirebasePerformance.getInstance().newTrace(STARTUP_TRACE_NAME);\n        Log.d(TAG, \"Starting trace\");\n        mTrace.start();\n        loadImageFromWeb();\n        // Increment the counter of number of requests sent in the trace.\n        Log.d(TAG, \"Incrementing number of requests counter in trace\");\n        mTrace.incrementMetric(REQUESTS_COUNTER_NAME, 1);\n        loadFileFromDisk();\n        // Wait for app startup tasks to complete asynchronously and stop the trace.\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    mNumStartupTasks.await();\n                } catch (InterruptedException e) {\n                    Log.e(TAG, \"Unable to wait for startup task completion.\");\n                } finally {\n                    Log.d(TAG, \"Stopping trace\");\n                    mTrace.stop();\n                    MainActivity.this.runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n                            Toast.makeText(MainActivity.this, \"Trace completed\",\n                                    Toast.LENGTH_SHORT).show();\n                        }\n                    });\n                }\n            }\n        }).start();\n    }\n\n    private void loadImageFromWeb() {\n        Glide.with(this).\n                load(IMAGE_URL)\n                .placeholder(new ColorDrawable(ContextCompat.getColor(this, R.color.colorAccent)))\n                .listener(new RequestListener<Drawable>() {\n                    @Override\n                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {\n                        mNumStartupTasks.countDown();\n                        return false;\n                    }\n\n                    @Override\n                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {\n                        mNumStartupTasks.countDown();\n                        return false;\n                    }\n                }).into(binding.headerIcon);\n    }\n\n    private Task<Void> writeStringToFile(final String filename, final String content) {\n        final TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();\n        Executors.newSingleThreadExecutor().execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    FileOutputStream fos = new FileOutputStream(filename, true);\n                    fos.write(content.getBytes());\n                    fos.close();\n                    taskCompletionSource.setResult(null);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n\n            }\n        });\n        return taskCompletionSource.getTask();\n    }\n\n    private Task<String> loadStringFromFile() {\n        final TaskCompletionSource<String> taskCompletionSource = new TaskCompletionSource<>();\n        Executors.newSingleThreadExecutor().execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    File contentFile = new File(getFilesDir(), CONTENT_FILE);\n                    if (contentFile.createNewFile()) {\n                        // Content file exist did not exist in internal storage and new file was created.\n                        // Copy in the default content.\n                        InputStream is;\n                        is = getAssets().open(DEFAULT_CONTENT_FILE);\n                        int size = is.available();\n                        byte[] buffer = new byte[size];\n                        is.read(buffer);\n                        is.close();\n                        FileOutputStream fos = new FileOutputStream(contentFile);\n                        fos.write(buffer);\n                        fos.close();\n                    }\n                    FileInputStream fis = new FileInputStream(contentFile);\n                    byte[] content = new byte[(int) contentFile.length()];\n                    fis.read(content);\n                    taskCompletionSource.setResult(new String(content));\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n\n            }\n        });\n        return taskCompletionSource.getTask();\n    }\n\n    private void loadFileFromDisk() {\n        loadStringFromFile()\n                .addOnCompleteListener(new OnCompleteListener<String>() {\n                    @Override\n                    public void onComplete(@NonNull Task<String> task) {\n                        if (!task.isSuccessful()) {\n                            Log.e(TAG, \"Couldn't read text file.\");\n                            Toast.makeText(\n                                    MainActivity.this, getString(R.string.text_read_error), Toast.LENGTH_LONG)\n                                    .show();\n                            return;\n                        }\n\n                        String fileContent = task.getResult();\n                        binding.textViewContent.setText(task.getResult());\n                        // Increment a counter with the file size that was read.\n                        Log.d(TAG, \"Incrementing file size counter in trace\");\n                        mTrace.incrementMetric(FILE_SIZE_COUNTER_NAME, fileContent.getBytes().length);\n                        mNumStartupTasks.countDown();\n                    }\n                });\n    }\n\n    private String getRandomString(int length) {\n        char[] chars = \"abcdefghijklmnopqrstuvwxyz\".toCharArray();\n        StringBuilder sb = new StringBuilder();\n        Random random = new Random();\n        for (int i = 0; i < length; i++) {\n            char c = chars[random.nextInt(chars.length)];\n            sb.append(c);\n        }\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "perf/app/src/main/java/com/google/firebase/quickstart/perfmon/kotlin/MainActivity.kt",
    "content": "package com.google.firebase.quickstart.perfmon.kotlin\n\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport android.util.Log\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.lifecycleScope\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.target.Target\nimport com.google.android.gms.tasks.OnCompleteListener\nimport com.google.android.gms.tasks.Task\nimport com.google.android.gms.tasks.TaskCompletionSource\nimport com.google.firebase.Firebase\nimport com.google.firebase.perf.performance\nimport com.google.firebase.perf.metrics.Trace\nimport com.google.firebase.quickstart.perfmon.R\nimport com.google.firebase.quickstart.perfmon.databinding.ActivityMainBinding\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.io.InputStream\nimport java.util.Random\nimport java.util.concurrent.CountDownLatch\n\nclass MainActivity : AppCompatActivity() {\n\n    private lateinit var trace: Trace\n\n    private val numStartupTasks = CountDownLatch(2)\n\n    private lateinit var binding: ActivityMainBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        binding.button.setOnClickListener {\n            // write 40 chars of random text to file\n            val contentFile = File(this.filesDir, CONTENT_FILE)\n\n            writeStringToFile(contentFile.absolutePath, \"${getRandomString(40)}\\n\")\n                .addOnCompleteListener { task ->\n                    if (!task.isSuccessful) {\n                        Log.e(TAG, \"Unable to write to file\", task.exception)\n                        return@addOnCompleteListener\n                    }\n\n                    loadFileFromDisk()\n                }\n        }\n\n        // Begin tracing app startup tasks.\n        trace = Firebase.performance.newTrace(STARTUP_TRACE_NAME)\n        Log.d(TAG, \"Starting trace\")\n        trace.start()\n        loadImageFromWeb()\n        // Increment the counter of number of requests sent in the trace.\n        Log.d(TAG, \"Incrementing number of requests counter in trace\")\n        trace.incrementMetric(REQUESTS_COUNTER_NAME, 1)\n        loadFileFromDisk()\n        // Wait for app startup tasks to complete asynchronously and stop the trace.\n        Thread(\n            Runnable {\n                try {\n                    numStartupTasks.await()\n                } catch (e: InterruptedException) {\n                    Log.e(TAG, \"Unable to wait for startup task completion.\")\n                } finally {\n                    Log.d(TAG, \"Stopping trace\")\n                    trace.stop()\n                    runOnUiThread {\n                        Toast.makeText(\n                            this,\n                            \"Trace completed\",\n                            Toast.LENGTH_SHORT,\n                        ).show()\n                    }\n                }\n            },\n        ).start()\n    }\n\n    private fun loadImageFromWeb() {\n        Glide.with(this).load(IMAGE_URL)\n            .placeholder(ColorDrawable(ContextCompat.getColor(this, R.color.colorAccent)))\n            .listener(object : RequestListener<Drawable> {\n                override fun onLoadFailed(\n                    e: GlideException?,\n                    model: Any?,\n                    target: Target<Drawable>?,\n                    isFirstResource: Boolean,\n                ): Boolean {\n                    numStartupTasks.countDown() // Signal end of image load task.\n                    return false\n                }\n\n                override fun onResourceReady(\n                    resource: Drawable?,\n                    model: Any?,\n                    target: Target<Drawable>?,\n                    dataSource: DataSource?,\n                    isFirstResource: Boolean,\n                ): Boolean {\n                    numStartupTasks.countDown() // Signal end of image load task.\n                    return false\n                }\n            }).into(binding.headerIcon)\n    }\n\n    private fun writeStringToFile(filename: String, content: String): Task<Void> {\n        val taskCompletionSource = TaskCompletionSource<Void>()\n        lifecycleScope.launch {\n            withContext(Dispatchers.IO) {\n                val fos = FileOutputStream(filename, true)\n                fos.write(content.toByteArray())\n                fos.close()\n                taskCompletionSource.setResult(null)\n            }\n        }\n        return taskCompletionSource.task\n    }\n\n    private fun loadStringFromFile(): Task<String> {\n        val taskCompletionSource = TaskCompletionSource<String>()\n        lifecycleScope.launch {\n            withContext(Dispatchers.IO) {\n                val contentFile = File(filesDir, CONTENT_FILE)\n                if (contentFile.createNewFile()) {\n                    // Content file exist did not exist in internal storage and new file was created.\n                    // Copy in the default content.\n                    val `is`: InputStream = assets.open(DEFAULT_CONTENT_FILE)\n                    val size = `is`.available()\n                    val buffer = ByteArray(size)\n                    `is`.read(buffer)\n                    `is`.close()\n                    val fos = FileOutputStream(contentFile)\n                    fos.write(buffer)\n                    fos.close()\n                }\n                val fis = FileInputStream(contentFile)\n                val content = ByteArray(contentFile.length().toInt())\n                fis.read(content)\n                taskCompletionSource.setResult(String(content))\n            }\n        }\n        return taskCompletionSource.task\n    }\n\n    private fun loadFileFromDisk() {\n        loadStringFromFile()\n            .addOnCompleteListener(\n                OnCompleteListener { task ->\n                    if (!task.isSuccessful) {\n                        Log.e(TAG, \"Couldn't read text file.\")\n                        Toast.makeText(\n                            this,\n                            getString(R.string.text_read_error),\n                            Toast.LENGTH_LONG,\n                        )\n                            .show()\n                        return@OnCompleteListener\n                    }\n\n                    val fileContent = task.result\n                    binding.textViewContent.text = task.result\n                    // Increment a counter with the file size that was read.\n                    Log.d(TAG, \"Incrementing file size counter in trace\")\n                    trace.incrementMetric(\n                        FILE_SIZE_COUNTER_NAME,\n                        fileContent!!.toByteArray().size.toLong(),\n                    )\n                    numStartupTasks.countDown()\n                },\n            )\n    }\n\n    private fun getRandomString(length: Int): String {\n        val chars = \"abcdefghijklmnopqrstuvwxyz\".toCharArray()\n        val sb = StringBuilder()\n        val random = Random()\n        for (i in 0 until length) {\n            val c = chars[random.nextInt(chars.size)]\n            sb.append(c)\n        }\n        return sb.toString()\n    }\n\n    companion object {\n        private const val TAG = \"MainActivity\"\n\n        private const val DEFAULT_CONTENT_FILE = \"default_content.txt\"\n        private const val CONTENT_FILE = \"content.txt\"\n        private const val IMAGE_URL =\n            \"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png\"\n\n        private const val STARTUP_TRACE_NAME = \"startup_trace\"\n        private const val REQUESTS_COUNTER_NAME = \"requests sent\"\n        private const val FILE_SIZE_COUNTER_NAME = \"file size\"\n    }\n}\n"
  },
  {
    "path": "perf/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\"\n    tools:context=\"com.google.firebase.quickstart.perfmon.java.MainActivity\">\n\n    <ImageView\n        android:id=\"@+id/headerIcon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:src=\"@drawable/firebase_lockup_400\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <ScrollView\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:textAlignment=\"viewStart\"\n        app:layout_constraintBottom_toTopOf=\"@+id/button\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/headerIcon\">\n\n        <TextView\n            android:id=\"@+id/textViewContent\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Loading text from file...\"\n            android:textAlignment=\"viewStart\" />\n\n    </ScrollView>\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:text=\"@string/generate_random_text\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "perf/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n</resources>\n"
  },
  {
    "path": "perf/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Firebase Performance Monitoring</string>\n    <string name=\"generate_random_text\">Add Random Text</string>\n    <string name=\"text_read_error\">Error reading text file</string>\n</resources>\n"
  },
  {
    "path": "perf/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "perf/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n    alias(libs.plugins.firebase.perf) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "perf/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "perf/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "perf/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "perf/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "perf/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "scripts/checksnippets.py",
    "content": "\"\"\"Script for checking that Java and Kotlin snippets are in line.\n\nExample:\n$ python scripts/checksnippets.py\nChecking snippets in folder: storage/app/src/\nERROR: Missing kotlin file for java file FirebaseUIActivity.java\nERROR: The following snippets are missing from StorageActivity.kt: set(['storage_custom_app'])\nERROR: Missing kotlin file for java file UploadActivity.java\nERROR: Missing kotlin file for java file DownloadActivity.java\n\"\"\"\nimport sys\nimport glob\nimport fnmatch\nimport os\nimport re\n\n_RE_REGION_TAG_START = re.compile(r'\\[START ([\\w_\\-]+)\\]')\n_RE_REGION_TAG_END = re.compile(r'\\[END ([\\w_\\-]+)\\]')\n\nclass MissingKotlinFile(Exception):\n\n    def __init__(self, javaName):\n        self.javaName = javaName\n    \n    def __str__(self):\n        return 'ERROR: Missing kotlin file for java file {}'.format(self.javaName)\n\n\nclass RegionTagMismatch(Exception):\n\n    def __init__(self, kotlinName, regionDiff):\n        self.kotlinName = kotlinName\n        self.regionDiff = regionDiff\n\n    def __str__(self):\n         return 'ERROR: The following snippets are missing from {}: {}'.format(\n             self.kotlinName, self.regionDiff)\n\n\nclass MissingEndTag(Exception):\n\n    def __init__(self, fileName, missing):\n        self.fileName = fileName\n        self.missing = missing\n\n    def __str__(self):\n        return 'ERROR: The following snippets in {} are missing END tags: {}'.format(\n            self.fileName, self.missing)\n\n\ndef checkSnippets(folder):\n    print('Checking snippets in folder: {}'.format(folder))\n    javaFiles = findFileWithPattern(folder, '*.java')\n\n    for f in javaFiles:\n        checkJavaFile(folder, f)\n\n    print('Done')\n\n\ndef checkJavaFile(folder, javaFile):\n    javaRegions = regionsInFile(javaFile)\n\n    javaName = os.path.basename(javaFile)\n    kotlinName = javaName.replace(\".java\", \".kt\")\n\n    # If the Java file has no snippet tags, we don't care about kotlin\n    if len(javaRegions) == 0:\n        return\n\n    # Check to make sure a matching kotlin file exists\n    kotlinFiles = findFileWithPattern(folder, kotlinName)\n    if len(kotlinFiles) == 0:\n        raise MissingKotlinFile(javaName)\n\n    # Find all regions in the kotlin file, and check if they differ from the java file\n    kotlinFile = kotlinFiles[0]\n    kotlinRegions = regionsInFile(kotlinFile)\n\n    regionDiff = javaRegions.difference(kotlinRegions)\n    if len(regionDiff) > 0:\n        raise RegionTagMismatch(kotlinName, regionDiff)\n\n    print('SUCCESS: {} <--> {}'.format(javaName, kotlinName))\n\n\ndef regionsInFile(path):\n    start_tags = set()\n    end_tags = set()\n    with open(path, 'r') as f:\n        lines = f.read().split('\\n')\n        for line in lines:\n            start_match = _RE_REGION_TAG_START.search(line)\n            if start_match:\n                start_tags.add(start_match.group(1))\n\n            end_match = _RE_REGION_TAG_END.search(line)\n            if end_match:\n                end_tags.add(end_match.group(1))\n\n    startEndDiff = start_tags.difference(end_tags)\n    if len(startEndDiff) > 0:\n        raise MissingEndTag(path, startEndDiff)\n\n    return start_tags\n        \n\ndef findFileWithPattern(folder, pattern):\n    matches = []\n    for root, dirnames, filenames in os.walk(folder):\n        for dirname in fnmatch.filter(dirnames, pattern):\n            matches.append(os.path.join(root, dirname))\n\n        for filename in fnmatch.filter(filenames, pattern):\n            matches.append(os.path.join(root, filename))\n\n    return matches\n\nif __name__ == \"__main__\":\n    sourceFolders = findFileWithPattern('.', 'src')\n    for folder in sourceFolders:\n        checkSnippets(folder)\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":admob:app\",\n        \":firebase-ai:app\",\n        \":analytics:app\",\n        \":appdistribution:app\",\n        \":auth:app\",\n        \":config:app\",\n        \":crash:app\",\n        \":database:app\",\n        \":dataconnect:app\",\n        \":firestore:app\",\n        \":functions:app\",\n        \":internal:chooserx\",\n        \":internal:lint\",\n        \":internal:lintchecks\",\n        \":inappmessaging:app\",\n        \":messaging:app\",\n        \":perf:app\",\n        \":storage:app\"\n)\n"
  },
  {
    "path": "storage/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n\nservice_account.json\n"
  },
  {
    "path": "storage/README.md",
    "content": "Cloud Storage for Firebase Quickstart\n==============================\n\nIntroduction\n------------\n\n- [Read more about Cloud Storage for Firebase](https://firebase.google.com/docs/storage)\n\nGetting Started\n---------------\n\n- [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).\nThe package name you'll use is `com.google.firebase.quickstart.firebasestorage`.\n- Run the Android application on your Android device or emulator. The app prompts you to\nsign in, so make sure to [enable anonymous authentication](https://console.firebase.google.com/project/_/authentication/providers)\nfor your project.\n\nScreenshots\n-----------\n<img src=\"app/src/screen.png\" height=\"534\" width=\"300\"/>\n\nSupport\n-------\n\nhttps://firebase.google.com/support/\n\nLicense\n-------\n\nCopyright 2018 Google, Inc.\n\nLicensed to the Apache Software Foundation (ASF) under one or more contributor\nlicense agreements.  See the NOTICE file distributed with this work for\nadditional information regarding copyright ownership.  The ASF licenses this\nfile to you under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License.  You may obtain a copy of\nthe License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "storage/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "storage/app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.tasks.factory.dependsOn\n\nplugins {\n    id(\"com.android.application\")\n    id(\"com.google.gms.google-services\")\n}\n\ntasks {\n    check.dependsOn(\"assembleDebugAndroidTest\")\n}\n\nandroid {\n    namespace = \"com.google.firebase.quickstart.firebasestorage\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"com.google.firebase.quickstart.firebasestorage\"\n        minSdk = 23\n        targetSdk = 36\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        viewBinding = true\n    }\n}\n\ndependencies {\n    implementation(project(\":internal:lintchecks\"))\n    implementation(project(\":internal:chooserx\"))\n\n    // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)\n    implementation(platform(\"com.google.firebase:firebase-bom:34.7.0\"))\n\n    // Cloud Storage for Firebase\n    implementation(\"com.google.firebase:firebase-storage\")\n\n    // Firebase Authentication\n    implementation(\"com.google.firebase:firebase-auth\")\n\n    implementation(\"androidx.activity:activity-ktx:1.12.1\")\n    implementation(\"androidx.appcompat:appcompat:1.7.1\")\n    implementation(\"com.google.android.material:material:1.13.0\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.7.0\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-intents:3.7.0\")\n    androidTestImplementation(\"androidx.test:rules:1.7.0\")\n    androidTestImplementation(\"androidx.test:runner:1.7.0\")\n}\n"
  },
  {
    "path": "storage/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keepattributes Signature\n-keepattributes *Annotation*\n-keepattributes EnclosingMethod\n-keepattributes InnerClasses\n"
  },
  {
    "path": "storage/app/src/androidTest/java/com/google/firebase/quickstart/firebasestorage/MainActivityTest.java",
    "content": "package com.google.firebase.quickstart.firebasestorage;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.app.Instrumentation;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Build;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.NoMatchingViewException;\nimport androidx.test.espresso.ViewInteraction;\nimport androidx.test.espresso.intent.Intents;\nimport androidx.test.rule.ActivityTestRule;\nimport androidx.test.runner.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\nimport android.util.Log;\n\nimport com.google.firebase.quickstart.firebasestorage.java.MainActivity;\nimport com.google.firebase.quickstart.firebasestorage.java.MyUploadService;\n\nimport org.hamcrest.Matcher;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport static androidx.test.InstrumentationRegistry.getInstrumentation;\nimport static androidx.test.InstrumentationRegistry.getTargetContext;\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.assertion.ViewAssertions.matches;\nimport static androidx.test.espresso.intent.Intents.intending;\nimport static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;\nimport static androidx.test.espresso.matcher.RootMatchers.isDialog;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static androidx.test.espresso.matcher.ViewMatchers.withId;\nimport static androidx.test.espresso.matcher.ViewMatchers.withText;\nimport static org.hamcrest.CoreMatchers.allOf;\nimport static org.hamcrest.CoreMatchers.startsWith;\n\n@LargeTest\n@RunWith(AndroidJUnit4.class)\npublic class MainActivityTest {\n\n    private static final String TAG = \"MainActivityTest\";\n\n    private ServiceIdlingResource mUploadIdlingResource;\n\n    @Rule\n    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);\n\n    @BeforeClass\n    public static void grantPermissions() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            String packageName = getTargetContext().getPackageName();\n            String testPackageName = packageName + \".test\";\n\n            // Grant \"WRITE_EXTERNAL_STORAGE\"\n            getInstrumentation().getUiAutomation().executeShellCommand(\n                    \"pm grant \" + packageName + Manifest.permission.WRITE_EXTERNAL_STORAGE);\n            getInstrumentation().getUiAutomation().executeShellCommand(\n                    \"pm grant \" + testPackageName + Manifest.permission.WRITE_EXTERNAL_STORAGE);\n        }\n    }\n\n    @Before\n    public void before() {\n        // Initialize intents\n        Intents.init();\n\n        // Idling resource\n        mUploadIdlingResource = new ServiceIdlingResource(mActivityTestRule.getActivity(),\n                MyUploadService.class);\n        Espresso.registerIdlingResources(mUploadIdlingResource);\n    }\n\n    @After\n    public void after() {\n        // Release intents\n        Intents.release();\n\n        // Idling resource\n        if (mUploadIdlingResource != null) {\n            Espresso.unregisterIdlingResources(mUploadIdlingResource);\n        }\n    }\n\n\n    @Test\n    public void uploadPhotoTest() throws InterruptedException {\n        // Log out to start\n        logOutIfPossible();\n\n        // Create a temp file\n        createTempFile();\n\n        // Click sign in\n        ViewInteraction signInButton = onView(\n                allOf(withId(R.id.buttonSignIn), withText(R.string.sign_in_anonymously),\n                        isDisplayed()));\n        signInButton.perform(click());\n\n        // Wait for sign in\n        Thread.sleep(5000);\n\n        // Click upload\n        ViewInteraction uploadButton = onView(\n                allOf(withId(R.id.buttonCamera), withText(R.string.camera_button_text),\n                        isDisplayed()));\n        uploadButton.perform(click());\n\n        // Confirm that download link label is displayed\n        onView(withText(R.string.label_link))\n                .check(matches(isDisplayed()));\n\n        // Confirm that there is a download link on screen\n        onView(withId(R.id.pictureDownloadUri))\n                .check(matches(withText(startsWith(\"https://firebasestorage.googleapis.com\"))));\n\n        // Click download\n        ViewInteraction downloadButton = onView(\n                allOf(withId(R.id.buttonDownload), withText(R.string.download),\n                        isDisplayed()));\n        downloadButton.perform(click());\n\n        // Wait for download\n        Thread.sleep(5000);\n\n        // Confirm that a success dialog appears\n        onView(withText(R.string.success)).inRoot(isDialog())\n                .check(matches(isDisplayed()));\n    }\n\n    /**\n     * Create a file to be selected by tests.\n     */\n    private void createTempFile() {\n        // Create fake RESULT_OK Intent\n        Intent intent = new Intent();\n        intent.putExtra(\"is-espresso-test\", true);\n\n        // Create a temporary file for the result of the intent\n        File external = mActivityTestRule.getActivity().getExternalFilesDir(null);\n        File imageFile = new File(external, \"tmp.jpg\");\n        try {\n            imageFile.createNewFile();\n        } catch (IOException e) {\n            Log.e(TAG, \"createNewFile\", e);\n        }\n        intent.setData(Uri.fromFile(imageFile));\n\n        Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(\n                Activity.RESULT_OK, intent);\n\n        // Intercept photo intent\n        Matcher<Intent> pictureIntentMatch = allOf(hasAction(Intent.ACTION_GET_CONTENT));\n        intending(pictureIntentMatch).respondWith(result);\n    }\n\n    /**\n     * Click the 'Log Out' overflow menu if it exists (which would mean we're signed in).\n     */\n    private void logOutIfPossible() {\n        try {\n            openActionBarOverflowOrOptionsMenu(getTargetContext());\n            onView(withText(R.string.log_out)).perform(click());\n        } catch (NoMatchingViewException e) {\n            // Ignore exception since we only want to do this operation if it's easy.\n        }\n\n    }\n}\n"
  },
  {
    "path": "storage/app/src/androidTest/java/com/google/firebase/quickstart/firebasestorage/ServiceIdlingResource.java",
    "content": "package com.google.firebase.quickstart.firebasestorage;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport androidx.test.espresso.IdlingResource;\n\n/**\n * Idling Resource for when service is running.\n * Adapted from: https://github.com/chiuki/espresso-samples\n */\npublic class ServiceIdlingResource implements IdlingResource {\n\n    private final Context mContext;\n    private final Class mServiceClass;\n    private ResourceCallback mResourceCallback;\n\n    public ServiceIdlingResource(Context context, Class serviceClass) {\n        this.mContext = context;\n        this.mServiceClass = serviceClass;\n    }\n\n    @Override\n    public String getName() {\n        return \"IdlingResource<\" + mServiceClass.getSimpleName() +\">\";\n    }\n\n    @Override\n    public boolean isIdleNow() {\n        boolean idle = !isIntentServiceRunning();\n        if (idle && mResourceCallback != null) {\n            mResourceCallback.onTransitionToIdle();\n        }\n        return idle;\n    }\n\n    @Override\n    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {\n        this.mResourceCallback = resourceCallback;\n    }\n\n    private boolean isIntentServiceRunning() {\n        ActivityManager manager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);\n        for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) {\n            if (mServiceClass.getName().equals(info.service.getClassName())) {\n                return true;\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!-- Used only for testing purposes, not required for Firebase Storage -->\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity android:name=\".EntryChoiceActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".java.MainActivity\"\n            android:launchMode=\"singleTask\">\n        </activity>\n\n        <activity\n            android:name=\".kotlin.MainActivity\"\n            android:launchMode=\"singleTask\">\n        </activity>\n\n        <service\n            android:name=\".java.MyDownloadService\"\n            android:exported=\"false\"/>\n\n        <service\n            android:name=\".java.MyUploadService\"\n            android:exported=\"false\" />\n\n        <service\n            android:name=\".kotlin.MyDownloadService\"\n            android:exported=\"false\"/>\n\n        <service\n            android:name=\".kotlin.MyUploadService\"\n            android:exported=\"false\" />\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/EntryChoiceActivity.kt",
    "content": "package com.google.firebase.quickstart.firebasestorage\n\nimport android.content.Intent\nimport com.firebase.example.internal.BaseEntryChoiceActivity\nimport com.firebase.example.internal.Choice\n\nclass EntryChoiceActivity : BaseEntryChoiceActivity() {\n\n    override fun getChoices(): List<Choice> {\n        return listOf(\n            Choice(\n                \"Java\",\n                \"Run the Firebase Storage quickstart written in Java.\",\n                Intent(this, com.google.firebase.quickstart.firebasestorage.java.MainActivity::class.java),\n            ),\n            Choice(\n                \"Kotlin\",\n                \"Run the Firebase In App Messaging quickstart written in Kotlin.\",\n                Intent(this, com.google.firebase.quickstart.firebasestorage.kotlin.MainActivity::class.java),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/java/MainActivity.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.firebasestorage.java;\n\nimport android.Manifest;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.content.ContextCompat;\nimport androidx.localbroadcastmanager.content.LocalBroadcastManager;\n\nimport com.google.android.gms.tasks.OnFailureListener;\nimport com.google.android.gms.tasks.OnSuccessListener;\nimport com.google.firebase.auth.AuthResult;\nimport com.google.firebase.auth.FirebaseAuth;\nimport com.google.firebase.auth.FirebaseUser;\nimport com.google.firebase.quickstart.firebasestorage.R;\nimport com.google.firebase.quickstart.firebasestorage.databinding.ActivityMainBinding;\n\nimport java.util.Locale;\n\n/**\n * Activity to upload and download photos from Firebase Storage.\n *\n * See {@link MyUploadService} for upload example.\n * See {@link MyDownloadService} for download example.\n */\npublic class MainActivity extends AppCompatActivity implements View.OnClickListener {\n\n    private static final String TAG = \"Storage#MainActivity\";\n\n    private static final String KEY_FILE_URI = \"key_file_uri\";\n    private static final String KEY_DOWNLOAD_URL = \"key_download_url\";\n\n    private BroadcastReceiver mBroadcastReceiver;\n    private FirebaseAuth mAuth;\n\n    private Uri mDownloadUrl = null;\n    private Uri mFileUri = null;\n\n    private ActivityMainBinding binding;\n\n    private ActivityResultLauncher<String[]> intentLauncher;\n    private final ActivityResultLauncher<String> requestPermissionLauncher =\n            registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {\n                if (isGranted) {\n                    Toast.makeText(this, \"Notifications permission granted\", Toast.LENGTH_SHORT)\n                            .show();\n                } else {\n                    Toast.makeText(this,\n                            \"Can't post notifications without POST_NOTIFICATIONS permission\",\n                            Toast.LENGTH_LONG).show();\n                }\n            });\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        // Initialize Firebase Auth\n        mAuth = FirebaseAuth.getInstance();\n\n        // Click listeners\n        binding.buttonCamera.setOnClickListener(this);\n        binding.buttonSignIn.setOnClickListener(this);\n        binding.buttonDownload.setOnClickListener(this);\n\n        // Restore instance state\n        if (savedInstanceState != null) {\n            mFileUri = savedInstanceState.getParcelable(KEY_FILE_URI);\n            mDownloadUrl = savedInstanceState.getParcelable(KEY_DOWNLOAD_URL);\n        }\n        onNewIntent(getIntent());\n\n        intentLauncher = registerForActivityResult(\n                new ActivityResultContracts.OpenDocument(), fileUri -> {\n                    if (fileUri != null) {\n                        uploadFromUri(fileUri);\n                    } else {\n                        Log.w(TAG, \"File URI is null\");\n                    }\n                });\n\n        // Local broadcast receiver\n        mBroadcastReceiver = new BroadcastReceiver() {\n            @Override\n            public void onReceive(Context context, Intent intent) {\n                Log.d(TAG, \"onReceive:\" + intent);\n                hideProgressBar();\n\n                switch (intent.getAction()) {\n                    case MyDownloadService.DOWNLOAD_COMPLETED:\n                        // Get number of bytes downloaded\n                        long numBytes = intent.getLongExtra(MyDownloadService.EXTRA_BYTES_DOWNLOADED, 0);\n\n                        // Alert success\n                        showMessageDialog(getString(R.string.success), String.format(Locale.getDefault(),\n                                \"%d bytes downloaded from %s\",\n                                numBytes,\n                                intent.getStringExtra(MyDownloadService.EXTRA_DOWNLOAD_PATH)));\n                        break;\n                    case MyDownloadService.DOWNLOAD_ERROR:\n                        // Alert failure\n                        showMessageDialog(\"Error\", String.format(Locale.getDefault(),\n                                \"Failed to download from %s\",\n                                intent.getStringExtra(MyDownloadService.EXTRA_DOWNLOAD_PATH)));\n                        break;\n                    case MyUploadService.UPLOAD_COMPLETED:\n                    case MyUploadService.UPLOAD_ERROR:\n                        onUploadResultIntent(intent);\n                        break;\n                }\n            }\n        };\n\n        askNotificationPermission();\n    }\n\n    @Override\n    public void onNewIntent(Intent intent) {\n        super.onNewIntent(intent);\n\n        // Check if this Activity was launched by clicking on an upload notification\n        if (intent.hasExtra(MyUploadService.EXTRA_DOWNLOAD_URL)) {\n            onUploadResultIntent(intent);\n        }\n\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        updateUI(mAuth.getCurrentUser());\n\n        // Register receiver for uploads and downloads\n        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);\n        manager.registerReceiver(mBroadcastReceiver, MyDownloadService.getIntentFilter());\n        manager.registerReceiver(mBroadcastReceiver, MyUploadService.getIntentFilter());\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n\n        // Unregister download receiver\n        LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);\n    }\n\n    @Override\n    public void onSaveInstanceState(Bundle out) {\n        super.onSaveInstanceState(out);\n        out.putParcelable(KEY_FILE_URI, mFileUri);\n        out.putParcelable(KEY_DOWNLOAD_URL, mDownloadUrl);\n    }\n\n    private void uploadFromUri(Uri fileUri) {\n        Log.d(TAG, \"uploadFromUri:src:\" + fileUri.toString());\n\n        // Save the File URI\n        mFileUri = fileUri;\n\n        // Clear the last download, if any\n        updateUI(mAuth.getCurrentUser());\n        mDownloadUrl = null;\n\n        // Start MyUploadService to upload the file, so that the file is uploaded\n        // even if this Activity is killed or put in the background\n        startService(new Intent(this, MyUploadService.class)\n                .putExtra(MyUploadService.EXTRA_FILE_URI, fileUri)\n                .setAction(MyUploadService.ACTION_UPLOAD));\n\n        // Show loading spinner\n        showProgressBar(getString(R.string.progress_uploading));\n    }\n\n    private void beginDownload() {\n        // Get path\n        String path = \"photos/\" + mFileUri.getLastPathSegment();\n\n        // Kick off MyDownloadService to download the file\n        Intent intent = new Intent(this, MyDownloadService.class)\n                .putExtra(MyDownloadService.EXTRA_DOWNLOAD_PATH, path)\n                .setAction(MyDownloadService.ACTION_DOWNLOAD);\n        startService(intent);\n\n        // Show loading spinner\n        showProgressBar(getString(R.string.progress_downloading));\n    }\n\n    private void launchCamera() {\n        Log.d(TAG, \"launchCamera\");\n\n        // Pick an image from storage\n        intentLauncher.launch(new String[]{ \"image/*\" });\n    }\n\n    private void signInAnonymously() {\n        // Sign in anonymously. Authentication is required to read or write from Firebase Storage.\n        showProgressBar(getString(R.string.progress_auth));\n        mAuth.signInAnonymously()\n                .addOnSuccessListener(this, new OnSuccessListener<AuthResult>() {\n                    @Override\n                    public void onSuccess(AuthResult authResult) {\n                        Log.d(TAG, \"signInAnonymously:SUCCESS\");\n                        hideProgressBar();\n                        updateUI(authResult.getUser());\n                    }\n                })\n                .addOnFailureListener(this, new OnFailureListener() {\n                    @Override\n                    public void onFailure(@NonNull Exception exception) {\n                        Log.e(TAG, \"signInAnonymously:FAILURE\", exception);\n                        hideProgressBar();\n                        updateUI(null);\n                    }\n                });\n    }\n\n    private void onUploadResultIntent(Intent intent) {\n        // Got a new intent from MyUploadService with a success or failure\n        mDownloadUrl = intent.getParcelableExtra(MyUploadService.EXTRA_DOWNLOAD_URL);\n        mFileUri = intent.getParcelableExtra(MyUploadService.EXTRA_FILE_URI);\n\n        updateUI(mAuth.getCurrentUser());\n    }\n\n    private void updateUI(FirebaseUser user) {\n        // Signed in or Signed out\n        if (user != null) {\n            binding.layoutSignin.setVisibility(View.GONE);\n            binding.layoutStorage.setVisibility(View.VISIBLE);\n        } else {\n            binding.layoutSignin.setVisibility(View.VISIBLE);\n            binding.layoutStorage.setVisibility(View.GONE);\n        }\n\n        // Download URL and Download button\n        if (mDownloadUrl != null) {\n            binding.pictureDownloadUri.setText(mDownloadUrl.toString());\n            binding.layoutDownload.setVisibility(View.VISIBLE);\n        } else {\n            binding.pictureDownloadUri.setText(null);\n            binding.layoutDownload.setVisibility(View.GONE);\n        }\n    }\n\n    private void showMessageDialog(String title, String message) {\n        AlertDialog ad = new AlertDialog.Builder(this)\n                .setTitle(title)\n                .setMessage(message)\n                .create();\n        ad.show();\n    }\n\n    private void showProgressBar(String caption) {\n        binding.caption.setText(caption);\n        binding.progressBar.setVisibility(View.VISIBLE);\n    }\n\n    private void hideProgressBar() {\n        binding.caption.setText(\"\");\n        binding.progressBar.setVisibility(View.INVISIBLE);\n    }\n\n    private void askNotificationPermission() {\n        // This is only necessary for API level >= 33 (TIRAMISU)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==\n                    PackageManager.PERMISSION_GRANTED) {\n                // Your app can post notifications.\n            } else{\n                // Directly ask for the permission\n                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);\n            }\n        }\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        int i = item.getItemId();\n        if (i == R.id.action_logout) {\n            FirebaseAuth.getInstance().signOut();\n            updateUI(null);\n            return true;\n        } else {\n            return super.onOptionsItemSelected(item);\n        }\n    }\n\n    @Override\n    public void onClick(View v) {\n        int i = v.getId();\n        if (i == R.id.buttonCamera) {\n            launchCamera();\n        } else if (i == R.id.buttonSignIn) {\n            signInAnonymously();\n        } else if (i == R.id.buttonDownload) {\n            beginDownload();\n        }\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/java/MyBaseTaskService.java",
    "content": "package com.google.firebase.quickstart.firebasestorage.java;\n\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport androidx.core.app.NotificationCompat;\nimport android.util.Log;\n\nimport com.google.firebase.quickstart.firebasestorage.R;\n\n/**\n * Base class for Services that keep track of the number of active jobs and self-stop when the\n * count is zero.\n */\npublic abstract class MyBaseTaskService extends Service {\n\n    private static final String CHANNEL_ID_DEFAULT = \"default\";\n\n    static final int PROGRESS_NOTIFICATION_ID = 0;\n    static final int FINISHED_NOTIFICATION_ID = 1;\n\n    private static final String TAG = \"MyBaseTaskService\";\n    private int mNumTasks = 0;\n\n    public void taskStarted() {\n        changeNumberOfTasks(1);\n    }\n\n    public void taskCompleted() {\n        changeNumberOfTasks(-1);\n    }\n\n    private synchronized void changeNumberOfTasks(int delta) {\n        Log.d(TAG, \"changeNumberOfTasks:\" + mNumTasks + \":\" + delta);\n        mNumTasks += delta;\n\n        // If there are no tasks left, stop the service\n        if (mNumTasks <= 0) {\n            Log.d(TAG, \"stopping\");\n            stopSelf();\n        }\n    }\n\n    private void createDefaultChannel() {\n        // Since android Oreo notification channel is needed.\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);\n\n            NotificationChannel channel = new NotificationChannel(CHANNEL_ID_DEFAULT,\n                    \"Default\",\n                    NotificationManager.IMPORTANCE_DEFAULT);\n            nm.createNotificationChannel(channel);\n        }\n    }\n\n    /**\n     * Show notification with a progress bar.\n     */\n    protected void showProgressNotification(String caption, long completedUnits, long totalUnits) {\n        int percentComplete = 0;\n        if (totalUnits > 0) {\n            percentComplete = (int) (100 * completedUnits / totalUnits);\n        }\n\n        createDefaultChannel();\n        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_DEFAULT)\n                .setSmallIcon(R.drawable.ic_file_upload_white_24dp)\n                .setContentTitle(getString(R.string.app_name))\n                .setContentText(caption)\n                .setProgress(100, percentComplete, false)\n                .setOngoing(true)\n                .setAutoCancel(false);\n\n        NotificationManager manager =\n                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n\n        manager.notify(PROGRESS_NOTIFICATION_ID, builder.build());\n    }\n\n    /**\n     * Show notification that the activity finished.\n     */\n    protected void showFinishedNotification(String caption, Intent intent, boolean success) {\n        // Make PendingIntent for notification\n        int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT;\n        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* requestCode */, intent,\n                flag);\n\n        int icon = success ? R.drawable.ic_check_white_24 : R.drawable.ic_error_white_24dp;\n\n        createDefaultChannel();\n        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_DEFAULT)\n                .setSmallIcon(icon)\n                .setContentTitle(getString(R.string.app_name))\n                .setContentText(caption)\n                .setAutoCancel(true)\n                .setContentIntent(pendingIntent);\n\n        NotificationManager manager =\n                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n\n        manager.notify(FINISHED_NOTIFICATION_ID, builder.build());\n    }\n\n    /**\n     * Dismiss the progress notification.\n     */\n    protected void dismissProgressNotification() {\n        NotificationManager manager =\n                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n\n        manager.cancel(PROGRESS_NOTIFICATION_ID);\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/java/MyDownloadService.java",
    "content": "/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.firebase.quickstart.firebasestorage.java;\n\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.localbroadcastmanager.content.LocalBroadcastManager;\n\nimport com.google.android.gms.tasks.OnFailureListener;\nimport com.google.android.gms.tasks.OnSuccessListener;\nimport com.google.firebase.quickstart.firebasestorage.R;\nimport com.google.firebase.storage.FirebaseStorage;\nimport com.google.firebase.storage.StorageReference;\nimport com.google.firebase.storage.StreamDownloadTask;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Service to handle downloading files from Firebase Storage.\n */\npublic class MyDownloadService extends MyBaseTaskService {\n\n    private static final String TAG = \"Storage#DownloadService\";\n\n    /** Actions **/\n    public static final String ACTION_DOWNLOAD = \"action_download\";\n    public static final String DOWNLOAD_COMPLETED = \"download_completed\";\n    public static final String DOWNLOAD_ERROR = \"download_error\";\n\n    /** Extras **/\n    public static final String EXTRA_DOWNLOAD_PATH = \"extra_download_path\";\n    public static final String EXTRA_BYTES_DOWNLOADED = \"extra_bytes_downloaded\";\n\n    private StorageReference mStorageRef;\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        // Initialize Storage\n        mStorageRef = FirebaseStorage.getInstance().getReference();\n    }\n\n    @Nullable\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        Log.d(TAG, \"onStartCommand:\" + intent + \":\" + startId);\n\n        if (ACTION_DOWNLOAD.equals(intent.getAction())) {\n            // Get the path to download from the intent\n            String downloadPath = intent.getStringExtra(EXTRA_DOWNLOAD_PATH);\n            downloadFromPath(downloadPath);\n        }\n\n        return START_REDELIVER_INTENT;\n    }\n\n    private void downloadFromPath(final String downloadPath) {\n        Log.d(TAG, \"downloadFromPath:\" + downloadPath);\n\n        // Mark task started\n        taskStarted();\n        showProgressNotification(getString(R.string.progress_downloading), 0, 0);\n\n        // Download and get total bytes\n        mStorageRef.child(downloadPath).getStream(\n                new StreamDownloadTask.StreamProcessor() {\n                    @Override\n                    public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot,\n                                               InputStream inputStream) throws IOException {\n                        long totalBytes = taskSnapshot.getTotalByteCount();\n                        long bytesDownloaded = 0;\n\n                        byte[] buffer = new byte[1024];\n                        int size;\n\n                        while ((size = inputStream.read(buffer)) != -1) {\n                            bytesDownloaded += size;\n                            showProgressNotification(getString(R.string.progress_downloading),\n                                    bytesDownloaded, totalBytes);\n                        }\n\n                        // Close the stream at the end of the Task\n                        inputStream.close();\n                    }\n                })\n                .addOnSuccessListener(new OnSuccessListener<StreamDownloadTask.TaskSnapshot>() {\n                    @Override\n                    public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) {\n                        Log.d(TAG, \"download:SUCCESS\");\n\n                        // Send success broadcast with number of bytes downloaded\n                        broadcastDownloadFinished(downloadPath, taskSnapshot.getTotalByteCount());\n                        showDownloadFinishedNotification(downloadPath, (int) taskSnapshot.getTotalByteCount());\n\n                        // Mark task completed\n                        taskCompleted();\n                    }\n                })\n                .addOnFailureListener(new OnFailureListener() {\n                    @Override\n                    public void onFailure(@NonNull Exception exception) {\n                        Log.w(TAG, \"download:FAILURE\", exception);\n\n                        // Send failure broadcast\n                        broadcastDownloadFinished(downloadPath, -1);\n                        showDownloadFinishedNotification(downloadPath, -1);\n\n                        // Mark task completed\n                        taskCompleted();\n                    }\n                });\n    }\n\n    /**\n     * Broadcast finished download (success or failure).\n     * @return true if a running receiver received the broadcast.\n     */\n    private boolean broadcastDownloadFinished(String downloadPath, long bytesDownloaded) {\n        boolean success = bytesDownloaded != -1;\n        String action = success ? DOWNLOAD_COMPLETED : DOWNLOAD_ERROR;\n\n        Intent broadcast = new Intent(action)\n                .putExtra(EXTRA_DOWNLOAD_PATH, downloadPath)\n                .putExtra(EXTRA_BYTES_DOWNLOADED, bytesDownloaded);\n        return LocalBroadcastManager.getInstance(getApplicationContext())\n                .sendBroadcast(broadcast);\n    }\n\n    /**\n     * Show a notification for a finished download.\n     */\n    private void showDownloadFinishedNotification(String downloadPath, int bytesDownloaded) {\n        // Hide the progress notification\n        dismissProgressNotification();\n\n        // Make Intent to MainActivity\n        Intent intent = new Intent(this, MainActivity.class)\n                .putExtra(EXTRA_DOWNLOAD_PATH, downloadPath)\n                .putExtra(EXTRA_BYTES_DOWNLOADED, bytesDownloaded)\n                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);\n\n        boolean success = bytesDownloaded != -1;\n        String caption = success ? getString(R.string.download_success) : getString(R.string.download_failure);\n        showFinishedNotification(caption, intent, true);\n    }\n\n\n    public static IntentFilter getIntentFilter() {\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(DOWNLOAD_COMPLETED);\n        filter.addAction(DOWNLOAD_ERROR);\n\n        return filter;\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/java/MyUploadService.java",
    "content": "package com.google.firebase.quickstart.firebasestorage.java;\n\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.localbroadcastmanager.content.LocalBroadcastManager;\n\nimport com.google.android.gms.tasks.Continuation;\nimport com.google.android.gms.tasks.OnFailureListener;\nimport com.google.android.gms.tasks.OnSuccessListener;\nimport com.google.android.gms.tasks.Task;\nimport com.google.firebase.quickstart.firebasestorage.R;\nimport com.google.firebase.storage.FirebaseStorage;\nimport com.google.firebase.storage.OnProgressListener;\nimport com.google.firebase.storage.StorageReference;\nimport com.google.firebase.storage.UploadTask;\n\n/**\n * Service to handle uploading files to Firebase Storage.\n */\npublic class MyUploadService extends MyBaseTaskService {\n\n    private static final String TAG = \"MyUploadService\";\n\n    /** Intent Actions **/\n    public static final String ACTION_UPLOAD = \"action_upload\";\n    public static final String UPLOAD_COMPLETED = \"upload_completed\";\n    public static final String UPLOAD_ERROR = \"upload_error\";\n\n    /** Intent Extras **/\n    public static final String EXTRA_FILE_URI = \"extra_file_uri\";\n    public static final String EXTRA_DOWNLOAD_URL = \"extra_download_url\";\n\n    // [START declare_ref]\n    private StorageReference mStorageRef;\n    // [END declare_ref]\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        // [START get_storage_ref]\n        mStorageRef = FirebaseStorage.getInstance().getReference();\n        // [END get_storage_ref]\n    }\n\n    @Nullable\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        Log.d(TAG, \"onStartCommand:\" + intent + \":\" + startId);\n        if (ACTION_UPLOAD.equals(intent.getAction())) {\n            Uri fileUri = intent.getParcelableExtra(EXTRA_FILE_URI);\n\n            // Make sure we have permission to read the data\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                getContentResolver().takePersistableUriPermission(\n                        fileUri,\n                        Intent.FLAG_GRANT_READ_URI_PERMISSION);\n            }\n\n            uploadFromUri(fileUri);\n        }\n\n        return START_REDELIVER_INTENT;\n    }\n\n    // [START upload_from_uri]\n    private void uploadFromUri(final Uri fileUri) {\n        Log.d(TAG, \"uploadFromUri:src:\" + fileUri.toString());\n\n        // [START_EXCLUDE]\n        taskStarted();\n        showProgressNotification(getString(R.string.progress_uploading), 0, 0);\n        // [END_EXCLUDE]\n\n        // [START get_child_ref]\n        // Get a reference to store file at photos/<FILENAME>.jpg\n        final StorageReference photoRef = mStorageRef.child(\"photos\")\n                .child(fileUri.getLastPathSegment());\n        // [END get_child_ref]\n\n        // Upload file to Firebase Storage\n        Log.d(TAG, \"uploadFromUri:dst:\" + photoRef.getPath());\n        photoRef.putFile(fileUri).\n                addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {\n                    @Override\n                    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {\n                        showProgressNotification(getString(R.string.progress_uploading),\n                                taskSnapshot.getBytesTransferred(),\n                                taskSnapshot.getTotalByteCount());\n                    }\n                })\n                .continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {\n                    @Override\n                    public Task<Uri> then(@NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {\n                        // Forward any exceptions\n                        if (!task.isSuccessful()) {\n                            throw task.getException();\n                        }\n\n                        Log.d(TAG, \"uploadFromUri: upload success\");\n\n                        // Request the public download URL\n                        return photoRef.getDownloadUrl();\n                    }\n                })\n                .addOnSuccessListener(new OnSuccessListener<Uri>() {\n                    @Override\n                    public void onSuccess(@NonNull Uri downloadUri) {\n                        // Upload succeeded\n                        Log.d(TAG, \"uploadFromUri: getDownloadUri success\");\n\n                        // [START_EXCLUDE]\n                        broadcastUploadFinished(downloadUri, fileUri);\n                        showUploadFinishedNotification(downloadUri, fileUri);\n                        taskCompleted();\n                        // [END_EXCLUDE]\n                    }\n                })\n                .addOnFailureListener(new OnFailureListener() {\n                    @Override\n                    public void onFailure(@NonNull Exception exception) {\n                        // Upload failed\n                        Log.w(TAG, \"uploadFromUri:onFailure\", exception);\n\n                        // [START_EXCLUDE]\n                        broadcastUploadFinished(null, fileUri);\n                        showUploadFinishedNotification(null, fileUri);\n                        taskCompleted();\n                        // [END_EXCLUDE]\n                    }\n                });\n    }\n    // [END upload_from_uri]\n\n    /**\n     * Broadcast finished upload (success or failure).\n     * @return true if a running receiver received the broadcast.\n     */\n    private boolean broadcastUploadFinished(@Nullable Uri downloadUrl, @Nullable Uri fileUri) {\n        boolean success = downloadUrl != null;\n\n        String action = success ? UPLOAD_COMPLETED : UPLOAD_ERROR;\n\n        Intent broadcast = new Intent(action)\n                .putExtra(EXTRA_DOWNLOAD_URL, downloadUrl)\n                .putExtra(EXTRA_FILE_URI, fileUri);\n        return LocalBroadcastManager.getInstance(getApplicationContext())\n                .sendBroadcast(broadcast);\n    }\n\n    /**\n     * Show a notification for a finished upload.\n     */\n    private void showUploadFinishedNotification(@Nullable Uri downloadUrl, @Nullable Uri fileUri) {\n        // Hide the progress notification\n        dismissProgressNotification();\n\n        // Make Intent to MainActivity\n        Intent intent = new Intent(this, MainActivity.class)\n                .putExtra(EXTRA_DOWNLOAD_URL, downloadUrl)\n                .putExtra(EXTRA_FILE_URI, fileUri)\n                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);\n\n        boolean success = downloadUrl != null;\n        String caption = success ? getString(R.string.upload_success) : getString(R.string.upload_failure);\n        showFinishedNotification(caption, intent, success);\n    }\n\n    public static IntentFilter getIntentFilter() {\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(UPLOAD_COMPLETED);\n        filter.addAction(UPLOAD_ERROR);\n\n        return filter;\n    }\n\n}\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/kotlin/MainActivity.kt",
    "content": "package com.google.firebase.quickstart.firebasestorage.kotlin\n\nimport android.Manifest\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.content.ContextCompat\nimport androidx.localbroadcastmanager.content.LocalBroadcastManager\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\nimport com.google.firebase.auth.auth\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.firebasestorage.R\nimport com.google.firebase.quickstart.firebasestorage.databinding.ActivityMainBinding\nimport java.util.Locale\n\n/**\n * Activity to upload and download photos from Firebase Storage.\n *\n * See [MyUploadService] for upload example.\n * See [MyDownloadService] for download example.\n */\nclass MainActivity : AppCompatActivity(), View.OnClickListener {\n\n    private lateinit var broadcastReceiver: BroadcastReceiver\n    private lateinit var auth: FirebaseAuth\n\n    private var downloadUrl: Uri? = null\n    private var fileUri: Uri? = null\n\n    private lateinit var binding: ActivityMainBinding\n\n    private lateinit var cameraIntent: ActivityResultLauncher<Array<String>>\n    private val requestPermissionLauncher = registerForActivityResult(\n        ActivityResultContracts.RequestPermission(),\n    ) { isGranted: Boolean ->\n        if (isGranted) {\n            Toast.makeText(this, \"Notifications permission granted\", Toast.LENGTH_SHORT)\n                .show()\n        } else {\n            Toast.makeText(\n                this,\n                \"Can't post notifications without POST_NOTIFICATIONS permission\",\n                Toast.LENGTH_LONG,\n            ).show()\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        // Initialize Firebase Auth\n        auth = Firebase.auth\n\n        // Click listeners\n        with(binding) {\n            buttonCamera.setOnClickListener(this@MainActivity)\n            buttonSignIn.setOnClickListener(this@MainActivity)\n            buttonDownload.setOnClickListener(this@MainActivity)\n        }\n\n        cameraIntent = registerForActivityResult(ActivityResultContracts.OpenDocument()) { fileUri ->\n            if (fileUri != null) {\n                uploadFromUri(fileUri)\n            } else {\n                Log.w(TAG, \"File URI is null\")\n            }\n        }\n\n        // Local broadcast receiver\n        broadcastReceiver = object : BroadcastReceiver() {\n            override fun onReceive(context: Context, intent: Intent) {\n                Log.d(TAG, \"onReceive:$intent\")\n                hideProgressBar()\n\n                when (intent.action) {\n                    MyDownloadService.DOWNLOAD_COMPLETED -> {\n                        // Get number of bytes downloaded\n                        val numBytes = intent.getLongExtra(MyDownloadService.EXTRA_BYTES_DOWNLOADED, 0)\n\n                        // Alert success\n                        showMessageDialog(\n                            getString(R.string.success),\n                            String.format(\n                                Locale.getDefault(),\n                                \"%d bytes downloaded from %s\",\n                                numBytes,\n                                intent.getStringExtra(MyDownloadService.EXTRA_DOWNLOAD_PATH),\n                            ),\n                        )\n                    }\n                    MyDownloadService.DOWNLOAD_ERROR ->\n                        // Alert failure\n                        showMessageDialog(\n                            \"Error\",\n                            String.format(\n                                Locale.getDefault(),\n                                \"Failed to download from %s\",\n                                intent.getStringExtra(MyDownloadService.EXTRA_DOWNLOAD_PATH),\n                            ),\n                        )\n                    MyUploadService.UPLOAD_COMPLETED, MyUploadService.UPLOAD_ERROR -> onUploadResultIntent(intent)\n                }\n            }\n        }\n\n        // Restore instance state\n        savedInstanceState?.let {\n            fileUri = it.getParcelable(KEY_FILE_URI)\n            downloadUrl = it.getParcelable(KEY_DOWNLOAD_URL)\n        }\n        onNewIntent(intent)\n\n        askNotificationPermission()\n    }\n\n    public override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n\n        // Check if this Activity was launched by clicking on an upload notification\n        if (intent.hasExtra(MyUploadService.EXTRA_DOWNLOAD_URL)) {\n            onUploadResultIntent(intent)\n        }\n    }\n\n    public override fun onStart() {\n        super.onStart()\n        updateUI(auth.currentUser)\n\n        // Register receiver for uploads and downloads\n        val manager = LocalBroadcastManager.getInstance(this)\n        manager.registerReceiver(broadcastReceiver, MyDownloadService.intentFilter)\n        manager.registerReceiver(broadcastReceiver, MyUploadService.intentFilter)\n    }\n\n    public override fun onStop() {\n        super.onStop()\n\n        // Unregister download receiver\n        LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)\n    }\n\n    public override fun onSaveInstanceState(out: Bundle) {\n        super.onSaveInstanceState(out)\n        out.putParcelable(KEY_FILE_URI, fileUri)\n        out.putParcelable(KEY_DOWNLOAD_URL, downloadUrl)\n    }\n\n    private fun uploadFromUri(uploadUri: Uri) {\n        Log.d(TAG, \"uploadFromUri:src: $uploadUri\")\n\n        // Save the File URI\n        fileUri = uploadUri\n\n        // Clear the last download, if any\n        updateUI(auth.currentUser)\n        downloadUrl = null\n\n        // Start MyUploadService to upload the file, so that the file is uploaded\n        // even if this Activity is killed or put in the background\n        startService(\n            Intent(this, MyUploadService::class.java)\n                .putExtra(MyUploadService.EXTRA_FILE_URI, uploadUri)\n                .setAction(MyUploadService.ACTION_UPLOAD),\n        )\n\n        // Show loading spinner\n        showProgressBar(getString(R.string.progress_uploading))\n    }\n\n    private fun beginDownload() {\n        fileUri?.let {\n            // Get path\n            val path = \"photos/\" + it.lastPathSegment\n\n            // Kick off MyDownloadService to download the file\n            val intent = Intent(this, MyDownloadService::class.java)\n                .putExtra(MyDownloadService.EXTRA_DOWNLOAD_PATH, path)\n                .setAction(MyDownloadService.ACTION_DOWNLOAD)\n            startService(intent)\n\n            // Show loading spinner\n            showProgressBar(getString(R.string.progress_downloading))\n        }\n    }\n\n    private fun launchCamera() {\n        Log.d(TAG, \"launchCamera\")\n\n        // Pick an image from storage\n        cameraIntent.launch(arrayOf(\"image/*\"))\n    }\n\n    private fun signInAnonymously() {\n        // Sign in anonymously. Authentication is required to read or write from Firebase Storage.\n        showProgressBar(getString(R.string.progress_auth))\n        auth.signInAnonymously()\n            .addOnSuccessListener(this) { authResult ->\n                Log.d(TAG, \"signInAnonymously:SUCCESS\")\n                hideProgressBar()\n                updateUI(authResult.user)\n            }\n            .addOnFailureListener(this) { exception ->\n                Log.e(TAG, \"signInAnonymously:FAILURE\", exception)\n                hideProgressBar()\n                updateUI(null)\n            }\n    }\n\n    private fun onUploadResultIntent(intent: Intent) {\n        // Got a new intent from MyUploadService with a success or failure\n        downloadUrl = intent.getParcelableExtra(MyUploadService.EXTRA_DOWNLOAD_URL)\n        fileUri = intent.getParcelableExtra(MyUploadService.EXTRA_FILE_URI)\n\n        updateUI(auth.currentUser)\n    }\n\n    private fun updateUI(user: FirebaseUser?) {\n        with(binding) {\n            // Signed in or Signed out\n            if (user != null) {\n                layoutSignin.visibility = View.GONE\n                layoutStorage.visibility = View.VISIBLE\n            } else {\n                layoutSignin.visibility = View.VISIBLE\n                layoutStorage.visibility = View.GONE\n            }\n\n            // Download URL and Download button\n            if (downloadUrl != null) {\n                pictureDownloadUri.text = downloadUrl.toString()\n                layoutDownload.visibility = View.VISIBLE\n            } else {\n                pictureDownloadUri.text = null\n                layoutDownload.visibility = View.GONE\n            }\n        }\n    }\n\n    private fun showMessageDialog(title: String, message: String) {\n        val ad = AlertDialog.Builder(this)\n            .setTitle(title)\n            .setMessage(message)\n            .create()\n        ad.show()\n    }\n\n    private fun showProgressBar(progressCaption: String) {\n        with(binding) {\n            caption.text = progressCaption\n            progressBar.visibility = View.VISIBLE\n        }\n    }\n\n    private fun hideProgressBar() {\n        with(binding) {\n            caption.text = \"\"\n            progressBar.visibility = View.INVISIBLE\n        }\n    }\n\n    private fun askNotificationPermission() {\n        // This is only necessary for API Level > 33 (TIRAMISU)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==\n                PackageManager.PERMISSION_GRANTED\n            ) {\n                // Your app can post notifications.\n            } else {\n                // Directly ask for the permission\n                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)\n            }\n        }\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu_main, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        val i = item.itemId\n        return if (i == R.id.action_logout) {\n            FirebaseAuth.getInstance().signOut()\n            updateUI(null)\n            true\n        } else {\n            super.onOptionsItemSelected(item)\n        }\n    }\n\n    override fun onClick(v: View) {\n        when (v.id) {\n            R.id.buttonCamera -> launchCamera()\n            R.id.buttonSignIn -> signInAnonymously()\n            R.id.buttonDownload -> beginDownload()\n        }\n    }\n\n    companion object {\n\n        private const val TAG = \"Storage#MainActivity\"\n\n        private const val KEY_FILE_URI = \"key_file_uri\"\n        private const val KEY_DOWNLOAD_URL = \"key_download_url\"\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/kotlin/MyBaseTaskService.kt",
    "content": "package com.google.firebase.quickstart.firebasestorage.kotlin\n\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.app.PendingIntent\nimport android.app.Service\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Build\nimport android.util.Log\nimport androidx.core.app.NotificationCompat\nimport com.google.firebase.quickstart.firebasestorage.R\n\n/**\n * Base class for Services that keep track of the number of active jobs and self-stop when the\n * count is zero.\n */\nabstract class MyBaseTaskService : Service() {\n\n    private var numTasks = 0\n\n    private val manager by lazy {\n        getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager\n    }\n\n    fun taskStarted() {\n        changeNumberOfTasks(1)\n    }\n\n    fun taskCompleted() {\n        changeNumberOfTasks(-1)\n    }\n\n    @Synchronized\n    private fun changeNumberOfTasks(delta: Int) {\n        Log.d(TAG, \"changeNumberOfTasks:$numTasks:$delta\")\n        numTasks += delta\n\n        // If there are no tasks left, stop the service\n        if (numTasks <= 0) {\n            Log.d(TAG, \"stopping\")\n            stopSelf()\n        }\n    }\n\n    private fun createDefaultChannel() {\n        // Since android Oreo notification channel is needed.\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            val channel = NotificationChannel(\n                CHANNEL_ID_DEFAULT,\n                \"Default\",\n                NotificationManager.IMPORTANCE_DEFAULT,\n            )\n            manager.createNotificationChannel(channel)\n        }\n    }\n\n    /**\n     * Show notification with a progress bar.\n     */\n    protected fun showProgressNotification(caption: String, completedUnits: Long, totalUnits: Long) {\n        var percentComplete = 0\n        if (totalUnits > 0) {\n            percentComplete = (100 * completedUnits / totalUnits).toInt()\n        }\n\n        createDefaultChannel()\n        val builder = NotificationCompat.Builder(this, CHANNEL_ID_DEFAULT)\n            .setSmallIcon(R.drawable.ic_file_upload_white_24dp)\n            .setContentTitle(getString(R.string.app_name))\n            .setContentText(caption)\n            .setProgress(100, percentComplete, false)\n            .setOngoing(true)\n            .setAutoCancel(false)\n\n        manager.notify(PROGRESS_NOTIFICATION_ID, builder.build())\n    }\n\n    /**\n     * Show notification that the activity finished.\n     */\n    protected fun showFinishedNotification(caption: String, intent: Intent, success: Boolean) {\n        // Make PendingIntent for notification\n\n        // Make PendingIntent for notification\n        val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            PendingIntent.FLAG_IMMUTABLE\n        } else PendingIntent.FLAG_UPDATE_CURRENT\n        val requestCode = 0\n        val pendingIntent = PendingIntent.getActivity(\n            this,\n            requestCode,\n            intent,\n            flag,\n        )\n\n        val icon = if (success) R.drawable.ic_check_white_24 else R.drawable.ic_error_white_24dp\n\n        createDefaultChannel()\n        val builder = NotificationCompat.Builder(this, CHANNEL_ID_DEFAULT)\n            .setSmallIcon(icon)\n            .setContentTitle(getString(R.string.app_name))\n            .setContentText(caption)\n            .setAutoCancel(true)\n            .setContentIntent(pendingIntent)\n\n        manager.notify(FINISHED_NOTIFICATION_ID, builder.build())\n    }\n\n    /**\n     * Dismiss the progress notification.\n     */\n    protected fun dismissProgressNotification() {\n        manager.cancel(PROGRESS_NOTIFICATION_ID)\n    }\n\n    companion object {\n\n        private const val CHANNEL_ID_DEFAULT = \"default\"\n\n        internal const val PROGRESS_NOTIFICATION_ID = 0\n        internal const val FINISHED_NOTIFICATION_ID = 1\n\n        private const val TAG = \"MyBaseTaskService\"\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/kotlin/MyDownloadService.kt",
    "content": "package com.google.firebase.quickstart.firebasestorage.kotlin\n\nimport android.app.Service\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.IBinder\nimport android.util.Log\nimport androidx.localbroadcastmanager.content.LocalBroadcastManager\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.firebasestorage.R\nimport com.google.firebase.storage.StorageReference\nimport com.google.firebase.storage.component1\nimport com.google.firebase.storage.component2\nimport com.google.firebase.storage.storage\n\nclass MyDownloadService : MyBaseTaskService() {\n\n    private lateinit var storageRef: StorageReference\n\n    override fun onCreate() {\n        super.onCreate()\n\n        // Initialize Storage\n        storageRef = Firebase.storage.reference\n    }\n\n    override fun onBind(intent: Intent): IBinder? {\n        return null\n    }\n\n    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {\n        Log.d(TAG, \"onStartCommand:$intent:$startId\")\n\n        if (ACTION_DOWNLOAD == intent.action) {\n            // Get the path to download from the intent\n            val downloadPath = intent.getStringExtra(EXTRA_DOWNLOAD_PATH)!!\n            downloadFromPath(downloadPath)\n        }\n\n        return Service.START_REDELIVER_INTENT\n    }\n\n    private fun downloadFromPath(downloadPath: String) {\n        Log.d(TAG, \"downloadFromPath:$downloadPath\")\n\n        // Mark task started\n        taskStarted()\n        showProgressNotification(getString(R.string.progress_downloading), 0, 0)\n\n        // Download and get total bytes\n        storageRef.child(downloadPath).getStream { (_, totalBytes), inputStream ->\n            var bytesDownloaded: Long = 0\n\n            val buffer = ByteArray(1024)\n            var size: Int = inputStream.read(buffer)\n\n            while (size != -1) {\n                bytesDownloaded += size.toLong()\n                showProgressNotification(\n                    getString(R.string.progress_downloading),\n                    bytesDownloaded,\n                    totalBytes,\n                )\n\n                size = inputStream.read(buffer)\n            }\n\n            // Close the stream at the end of the Task\n            inputStream.close()\n        }.addOnSuccessListener { (_, totalBytes) ->\n            Log.d(TAG, \"download:SUCCESS\")\n\n            // Send success broadcast with number of bytes downloaded\n            broadcastDownloadFinished(downloadPath, totalBytes)\n            showDownloadFinishedNotification(downloadPath, totalBytes.toInt())\n\n            // Mark task completed\n            taskCompleted()\n        }.addOnFailureListener { exception ->\n            Log.w(TAG, \"download:FAILURE\", exception)\n\n            // Send failure broadcast\n            broadcastDownloadFinished(downloadPath, -1)\n            showDownloadFinishedNotification(downloadPath, -1)\n\n            // Mark task completed\n            taskCompleted()\n        }\n    }\n\n    /**\n     * Broadcast finished download (success or failure).\n     * @return true if a running receiver received the broadcast.\n     */\n    private fun broadcastDownloadFinished(downloadPath: String, bytesDownloaded: Long): Boolean {\n        val success = bytesDownloaded != -1L\n        val action = if (success) DOWNLOAD_COMPLETED else DOWNLOAD_ERROR\n\n        val broadcast = Intent(action)\n            .putExtra(EXTRA_DOWNLOAD_PATH, downloadPath)\n            .putExtra(EXTRA_BYTES_DOWNLOADED, bytesDownloaded)\n        return LocalBroadcastManager.getInstance(applicationContext)\n            .sendBroadcast(broadcast)\n    }\n\n    /**\n     * Show a notification for a finished download.\n     */\n    private fun showDownloadFinishedNotification(downloadPath: String, bytesDownloaded: Int) {\n        // Hide the progress notification\n        dismissProgressNotification()\n\n        // Make Intent to MainActivity\n        val intent = Intent(this, MainActivity::class.java)\n            .putExtra(EXTRA_DOWNLOAD_PATH, downloadPath)\n            .putExtra(EXTRA_BYTES_DOWNLOADED, bytesDownloaded)\n            .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)\n\n        val success = bytesDownloaded != -1\n        val caption = if (success) {\n            getString(R.string.download_success)\n        } else {\n            getString(R.string.download_failure)\n        }\n\n        showFinishedNotification(caption, intent, true)\n    }\n\n    companion object {\n\n        private const val TAG = \"Storage#DownloadService\"\n\n        /** Actions  */\n        const val ACTION_DOWNLOAD = \"action_download\"\n        const val DOWNLOAD_COMPLETED = \"download_completed\"\n        const val DOWNLOAD_ERROR = \"download_error\"\n\n        /** Extras  */\n        const val EXTRA_DOWNLOAD_PATH = \"extra_download_path\"\n        const val EXTRA_BYTES_DOWNLOADED = \"extra_bytes_downloaded\"\n\n        val intentFilter: IntentFilter\n            get() {\n                val filter = IntentFilter()\n                filter.addAction(DOWNLOAD_COMPLETED)\n                filter.addAction(DOWNLOAD_ERROR)\n\n                return filter\n            }\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/java/com/google/firebase/quickstart/firebasestorage/kotlin/MyUploadService.kt",
    "content": "package com.google.firebase.quickstart.firebasestorage.kotlin\n\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.net.Uri\nimport android.os.Build\nimport android.os.IBinder\nimport android.util.Log\nimport androidx.localbroadcastmanager.content.LocalBroadcastManager\nimport com.google.firebase.Firebase\nimport com.google.firebase.quickstart.firebasestorage.R\nimport com.google.firebase.storage.StorageReference\nimport com.google.firebase.storage.component1\nimport com.google.firebase.storage.component2\nimport com.google.firebase.storage.storage\n\n/**\n * Service to handle uploading files to Firebase Storage.\n */\nclass MyUploadService : MyBaseTaskService() {\n\n    // [START declare_ref]\n    private lateinit var storageRef: StorageReference\n    // [END declare_ref]\n\n    override fun onCreate() {\n        super.onCreate()\n\n        // [START get_storage_ref]\n        storageRef = Firebase.storage.reference\n        // [END get_storage_ref]\n    }\n\n    override fun onBind(intent: Intent): IBinder? {\n        return null\n    }\n\n    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {\n        Log.d(TAG, \"onStartCommand:$intent:$startId\")\n        if (ACTION_UPLOAD == intent.action) {\n            val fileUri = intent.getParcelableExtra<Uri>(EXTRA_FILE_URI)!!\n\n            // Make sure we have permission to read the data\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                contentResolver.takePersistableUriPermission(\n                    fileUri,\n                    Intent.FLAG_GRANT_READ_URI_PERMISSION,\n                )\n            }\n\n            uploadFromUri(fileUri)\n        }\n\n        return START_REDELIVER_INTENT\n    }\n\n    // [START upload_from_uri]\n    private fun uploadFromUri(fileUri: Uri) {\n        Log.d(TAG, \"uploadFromUri:src:$fileUri\")\n\n        // [START_EXCLUDE]\n        taskStarted()\n        showProgressNotification(getString(R.string.progress_uploading), 0, 0)\n        // [END_EXCLUDE]\n\n        // [START get_child_ref]\n        // Get a reference to store file at photos/<FILENAME>.jpg\n        fileUri.lastPathSegment?.let {\n            val photoRef = storageRef.child(\"photos\")\n                .child(it)\n            // [END get_child_ref]\n\n            // Upload file to Firebase Storage\n            Log.d(TAG, \"uploadFromUri:dst:\" + photoRef.path)\n            photoRef.putFile(fileUri).addOnProgressListener { (bytesTransferred, totalByteCount) ->\n                showProgressNotification(\n                    getString(R.string.progress_uploading),\n                    bytesTransferred,\n                    totalByteCount,\n                )\n            }.continueWithTask { task ->\n                // Forward any exceptions\n                if (!task.isSuccessful) {\n                    throw task.exception!!\n                }\n\n                Log.d(TAG, \"uploadFromUri: upload success\")\n\n                // Request the public download URL\n                photoRef.downloadUrl\n            }.addOnSuccessListener { downloadUri ->\n                // Upload succeeded\n                Log.d(TAG, \"uploadFromUri: getDownloadUri success\")\n\n                // [START_EXCLUDE]\n                broadcastUploadFinished(downloadUri, fileUri)\n                showUploadFinishedNotification(downloadUri, fileUri)\n                taskCompleted()\n                // [END_EXCLUDE]\n            }.addOnFailureListener { exception ->\n                // Upload failed\n                Log.w(TAG, \"uploadFromUri:onFailure\", exception)\n\n                // [START_EXCLUDE]\n                broadcastUploadFinished(null, fileUri)\n                showUploadFinishedNotification(null, fileUri)\n                taskCompleted()\n                // [END_EXCLUDE]\n            }\n        }\n    }\n    // [END upload_from_uri]\n\n    /**\n     * Broadcast finished upload (success or failure).\n     * @return true if a running receiver received the broadcast.\n     */\n    private fun broadcastUploadFinished(downloadUrl: Uri?, fileUri: Uri?): Boolean {\n        val success = downloadUrl != null\n\n        val action = if (success) UPLOAD_COMPLETED else UPLOAD_ERROR\n\n        val broadcast = Intent(action)\n            .putExtra(EXTRA_DOWNLOAD_URL, downloadUrl)\n            .putExtra(EXTRA_FILE_URI, fileUri)\n        return LocalBroadcastManager.getInstance(applicationContext)\n            .sendBroadcast(broadcast)\n    }\n\n    /**\n     * Show a notification for a finished upload.\n     */\n    private fun showUploadFinishedNotification(downloadUrl: Uri?, fileUri: Uri?) {\n        // Hide the progress notification\n        dismissProgressNotification()\n\n        // Make Intent to MainActivity\n        val intent = Intent(this, MainActivity::class.java)\n            .putExtra(EXTRA_DOWNLOAD_URL, downloadUrl)\n            .putExtra(EXTRA_FILE_URI, fileUri)\n            .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)\n\n        val success = downloadUrl != null\n        val caption = if (success) getString(R.string.upload_success) else getString(R.string.upload_failure)\n        showFinishedNotification(caption, intent, success)\n    }\n\n    companion object {\n\n        private const val TAG = \"MyUploadService\"\n\n        /** Intent Actions  */\n        const val ACTION_UPLOAD = \"action_upload\"\n        const val UPLOAD_COMPLETED = \"upload_completed\"\n        const val UPLOAD_ERROR = \"upload_error\"\n\n        /** Intent Extras  */\n        const val EXTRA_FILE_URI = \"extra_file_uri\"\n        const val EXTRA_DOWNLOAD_URL = \"extra_download_url\"\n\n        val intentFilter: IntentFilter\n            get() {\n                val filter = IntentFilter()\n                filter.addAction(UPLOAD_COMPLETED)\n                filter.addAction(UPLOAD_ERROR)\n\n                return filter\n            }\n    }\n}\n"
  },
  {
    "path": "storage/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:fillViewport=\"true\"\n    android:scrollbars=\"vertical\"\n    tools:context=\"com.google.firebase.quickstart.firebasestorage.java.MainActivity\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingBottom=\"@dimen/activity_vertical_margin\"\n        android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n        android:paddingRight=\"@dimen/activity_horizontal_margin\"\n        android:paddingTop=\"@dimen/activity_vertical_margin\"\n        android:orientation=\"vertical\">\n\n        <ProgressBar\n            android:id=\"@+id/progressBar\"\n            android:indeterminate=\"true\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"invisible\"\n            style=\"?android:attr/progressBarStyleHorizontal\"/>\n\n        <TextView\n            android:id=\"@+id/caption\"\n            android:textAlignment=\"center\"\n            android:textColor=\"@color/colorAccent\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\" />\n\n        <ImageView\n            android:id=\"@+id/firebaseLogo\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginBottom=\"@dimen/margin_2\"\n            android:src=\"@drawable/firebase_lockup_400\" />\n\n        <LinearLayout\n            android:id=\"@+id/layoutSignin\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            tools:visibility=\"gone\">\n\n            <TextView\n                android:id=\"@+id/statusSignIn\"\n                style=\"@style/TextAppearance.AppCompat.Medium\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"@dimen/margin_1\"\n                android:text=\"@string/sign_in_prompt\" />\n\n            <Button\n                android:id=\"@+id/buttonSignIn\"\n                android:layout_width=\"@dimen/standard_field_width\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/sign_in_anonymously\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/layoutStorage\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\">\n\n            <TextView\n                style=\"@style/TextAppearance.AppCompat.Medium\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"@dimen/margin_1\"\n                android:text=\"@string/take_photo_prompt\" />\n\n            <Button\n                android:id=\"@+id/buttonCamera\"\n                android:layout_width=\"@dimen/standard_field_width\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/camera_button_text\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/layoutDownload\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\">\n\n            <TextView\n                style=\"@style/TextAppearance.AppCompat.Medium\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"@dimen/margin_1\"\n                android:layout_marginTop=\"@dimen/margin_2\"\n                android:text=\"@string/label_link\" />\n\n            <TextView\n                android:id=\"@+id/pictureDownloadUri\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:autoLink=\"web\"\n                tools:text=\"http://www.example.com/?id=UAOJNVKBMQUGPYZKCQZRZKJEXRCRXMRSMFBZBMBODWUSVTDXJCPJMYOKQQBODSGPYHPZUR\" />\n\n            <TextView\n                style=\"@style/TextAppearance.AppCompat.Medium\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"@dimen/margin_1\"\n                android:layout_marginTop=\"@dimen/margin_2\"\n                android:text=\"@string/label_download\" />\n\n            <Button\n                android:id=\"@+id/buttonDownload\"\n                android:layout_width=\"@dimen/standard_field_width\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/download\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n</ScrollView>\n"
  },
  {
    "path": "storage/app/src/main/res/menu/menu_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item android:id=\"@+id/action_logout\"\n        android:title=\"@string/log_out\"\n        app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "storage/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#039BE5</color>\n    <color name=\"colorPrimaryDark\">#0288D1</color>\n    <color name=\"colorAccent\">#FFA000</color>\n</resources>\n"
  },
  {
    "path": "storage/app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n\n    <dimen name=\"standard_field_width\">160dp</dimen>\n    <dimen name=\"margin_1\">8dp</dimen>\n    <dimen name=\"margin_2\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "storage/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Cloud Storage for Firebase</string>\n    <string name=\"camera_button_text\">Upload</string>\n    <string name=\"sign_in_prompt\">Sign In to begin</string>\n    <string name=\"take_photo_prompt\">Upload a Photo</string>\n    <string name=\"download\">Download</string>\n    <string name=\"sign_in_anonymously\">Sign In</string>\n    <string name=\"rationale_storage\">This sample reads images from your camera to demonstrate uploading.</string>\n    <string name=\"label_link\">Link to Uploaded File</string>\n    <string name=\"label_download\">Download File</string>\n    <string name=\"log_out\">Log out</string>\n    <string name=\"success\">Success</string>\n\n    <string name=\"progress_downloading\">Downloading…</string>\n    <string name=\"progress_uploading\">Uploading…</string>\n    <string name=\"progress_auth\">Signing In…</string>\n\n    <string name=\"upload_success\">Upload finished</string>\n    <string name=\"upload_failure\">Upload failed</string>\\\n\n    <string name=\"download_success\">Download finished</string>\n    <string name=\"download_failure\">Download failure</string>\n</resources>\n"
  },
  {
    "path": "storage/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "storage/app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "storage/app/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <external-path name=\"external\" path=\"photos/\" />\n</paths>\n"
  },
  {
    "path": "storage/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.google.services) apply false\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n    }\n}\n\ntasks {\n    register(\"clean\", Delete::class) {\n        delete(rootProject.layout.buildDirectory)\n    }\n}\n"
  },
  {
    "path": "storage/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "storage/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "storage/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "storage/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "storage/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ninclude(\":app\")\n\n// Required so that gradle can resolve these dependencies even when\n// building only a single project.\ninclude(\":internal:lintchecks\")\nproject(\":internal:lintchecks\").projectDir = file(\"../internal/lintchecks\")\ninclude(\":internal:lint\")\nproject(\":internal:lint\").projectDir = file(\"../internal/lint\")\ninclude(\":internal:chooserx\")\nproject(\":internal:chooserx\").projectDir = file(\"../internal/chooserx\")"
  },
  {
    "path": "vertexai/README.md",
    "content": "# [DEPRECATED] Firebase Vertex AI Sample for Android (Kotlin)\n\n> [!CAUTION] \n> On May 20, 2025, we released the Firebase AI SDK (`firebase-ai`). This SDK replaces the previous Vertex AI in Firebase SDK (`firebase-vertexai`) to accommodate the evolving set of supported features and services.\n> * The new Firebase AI SDK provides Preview support for the Gemini Developer API, including its free tier offering.\n> * Using the Firebase AI SDK with the Vertex AI Gemini API is still generally available (GA).\n>\n> If you're using the old `firebase-vertexai`, we recommend [migrating to firebase-ai](https://firebase.google.com/docs/ai-logic/migrate-to-latest-sdk) because all new development and features will be in this new SDK.\n>\n> The quickstart sample app for `firebase-ai` can be found in the [/firebase-ai directory](../firebase-ai/).\n"
  }
]