[
  {
    "path": ".gemini/config.yaml",
    "content": "# Copyright 2026 Google LLC\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# Gemini Code Assist Configuration\n# See: https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github\n\n# Feature settings\nhave_fun: false\n\ncode_review:\n  disable: false\n  comment_severity_threshold: MEDIUM\n  max_review_comments: -1\n\npull_request_opened:\n  summary: true\n  code_review: true\n  include_drafts: true\n\n# Files to ignore in Gemini analysis\nignore_patterns:\n  - \"**/*.bin\"\n  - \"**/*.exe\"\n  - \"**/build/**\"\n  - \"**/.gradle/**\"\n  - \"**/secrets.properties\"\n"
  },
  {
    "path": ".gemini/skills/places-android/SKILL.md",
    "content": "---\nname: places-android\ndescription: Guide for integrating the Places SDK for Android into an application. Use when users ask to add Places, autocomplete, place details, or search for places.\n---\n\n# Places SDK for Android Integration\n\nYou are an expert Android developer specializing in modern Android architecture. Before generating any code, ask the user the following questions to tailor the solution:\n\n### 📋 Design & Architectural Questions to Ask the User\n\n*   **UI Framework**: Are you using Jetpack Compose or standard UI Views?\n*   **Widget vs Custom UI**: Do you want to use the pre-built Google Autocomplete Widget (Dialog/Overlay) or build a completely custom programmatic search bar?\n*   **Compact vs Full Details**: Do you want a compact half-sheet overlay (`PlaceDetailsCompactFragment`) or a full-page details viewer (`PlaceDetailsFragment`)?\n*   **Cost & Field Scoping**: What exact fields do you need (e.g., `DISPLAY_NAME`, `FORMATTED_ADDRESS`, `PHOTO_METADATAS`)? Limiting fields saves costs!\n*   **Theming Options**: Are you using Material 3 themes so we can bridge default styling automatically?\n\n---\n\n## 1. Setup Dependencies\n\nAdd the necessary dependencies to your module-level `build.gradle.kts` file. It is recommended to use the Versions Catalog if available:\n\n```toml\n[versions]\nplaces = \"5.1.1\" # x-release-please-version\n\n[libraries]\nplaces = { group = \"com.google.android.libraries.places\", name = \"places\", version.ref = \"places\" }\n```\n\nThen in `build.gradle.kts`:\n\n```kotlin\ndependencies {\n    implementation(libs.places)\n}\n```\n\n## 2. Setup the Secrets Gradle Plugin\n\nUse the Secrets Gradle Plugin for Android to inject the API key securely into your project (e.g., via `BuildConfig`), so you can access it programmatically during initialization.\n\nEnsure you have the plugin applied in your app-level `build.gradle.kts`:\n\n```kotlin\nplugins {\n    alias(libs.plugins.secrets.gradle.plugin)\n}\n\nsecrets {\n    propertiesFileName = \"secrets.properties\"\n    defaultPropertiesFileName = \"local.defaults.properties\"\n}\n```\n\nAdd your API Key to `secrets.properties`:\n\n```properties\nPLACES_API_KEY=YOUR_API_KEY\n```\n\n## 3. Initialize the Places SDK\n\nIn your `Application` or `Activity` (before accessing any Places APIs, usually inside `onCreate`), initialize the Places SDK.\n\n### Kotlin\n\n```kotlin\nimport com.google.android.libraries.places.api.Places\n\nclass DemoApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        \n        val apiKey = BuildConfig.PLACES_API_KEY\n        if (apiKey.isNotEmpty()) {\n            Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)\n        }\n    }\n}\n```\n\n### Java\n\n```java\nimport com.google.android.libraries.places.api.Places;\n\npublic class DemoApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        String apiKey = BuildConfig.PLACES_API_KEY;\n        if (!apiKey.isEmpty()) {\n            Places.initializeWithNewPlacesApiEnabled(getApplicationContext(), apiKey);\n        }\n    }\n}\n```\n\n## 4. Best Practices & Guidelines\n\n*   **Prefer Places UI Kit**: For displaying place details (photos, reviews, addresses), prefer using the **Places UI Kit** over manual programmatic retrieval. It provides pre-built, beautifully designed, and automatically maintained UI components!\n*   **Null Safety & Validation**: Handle nulls defensively for optional parameters (e.g. Place fields).\n*   **Scoped Fields**: Always specify *only* parameters that are needed (e.g. `Place.Field.ID`, `Place.Field.DISPLAY_NAME`) to avoid over-billing.\n*   **Coroutine Extensions**: Use Kotlin Coroutines extensions (`places-ktx` if available) to make code cleaner.\n*   **Location Permission**: Location permissions are optional but helpful. `ACCESS_COARSE_LOCATION` is sufficient for biasing prediction calls (like searching search results) to general cities. `ACCESS_FINE_LOCATION` is necessary only for exact current position tracking. Declare them in your `AndroidManifest.xml`:\n\n    ```xml\n    <manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" ...>\n        <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n        <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    </manifest>\n    ```\n\n## 5. Compose Interop with Places UI Kit\n\nThe Places UI Kit (`PlaceDetailsCompactFragment` and `PlaceDetailsFragment`) are View-based. To use them in Jetpack Compose, use `AndroidView` to host a `FragmentContainerView`.\n\n### Key Pattern: Fragment Container in Compose\n\n*   **Access FragmentManager**: Use standard `LocalActivity.current as FragmentActivity` to access the support FragmentManager. Avoid casting `LocalContext.current` directly to Activity.\n*   **Deferred Updates**: Inside the `AndroidView` `update` block, always wrap calls (like `.loadWithPlaceId()`) in `view.post { ... }` to ensure updates run *after* the layout is inflated and bindings are stable.\n\n```kotlin\n@Composable\nfun PlaceDetailsCompactView(\n    placeId: String,\n    onDismiss: () -> Unit\n) {\n    val fragmentContainerId = remember { View.generateViewId() }\n    val fragmentManager = (LocalActivity.current as FragmentActivity).supportFragmentManager\n\n    val fragment = remember {\n        PlaceDetailsCompactFragment.newInstance(\n            PlaceDetailsCompactFragment.ALL_CONTENT,\n            Orientation.VERTICAL\n        )\n    }\n\n    Box(modifier = Modifier.fillMaxWidth()) {\n        AndroidView(\n            modifier = Modifier.fillMaxWidth(),\n            factory = { context ->\n                FragmentContainerView(context).apply {\n                    id = fragmentContainerId\n                    if (fragmentManager.findFragmentById(fragmentContainerId) == null) {\n                        fragmentManager.beginTransaction()\n                            .add(fragmentContainerId, fragment)\n                            .commit()\n                    }\n                }\n            },\n            update = { view ->\n                // Ensures updates run after view hierarchy is ready\n                view.post {\n                    fragment.loadWithPlaceId(placeId)\n                }\n            }\n        )\n    }\n}\n```\n\n## 📏 6. Advanced Compose Viewports & BottomSheetScaffold\n\nWhen hosting UI Kit fragments inside navigation drawers or overlays, follow these architectural bounds to avoid viewport clipping snags:\n\n*   **System Viewport Edge Overlap**: If using `enableEdgeToEdge()` and your container loses standard `Scaffold` body padding context, manually append `Modifier.statusBarsPadding()` to avoid overlapping with system status bar text:\n    ```kotlin\n    Column(modifier = modifier.statusBarsPadding()) { \n        // Beautiful search results sit safely under the status bar\n    }\n    ```\n*   **Unified Sheet Content State**: To achieve clean mutual exclusivity between \"Compact\" and \"Full\" details click toggles, hoist the viewport state to a high enum variable level:\n    ```kotlin\n    enum class DetailsUiType { COMPACT, FULL }\n    ```\n    Track `currentUiType` at the Activity level and pass it to a single shared `BottomSheetScaffold`. Users can swipe to dismiss natively without custom button overrides!\n\n## 7. Autocomplete with Compose (Widget)\n\nTo implement autocomplete in Compose, use `ActivityResultContracts.StartActivityForResult` with an Intent from `Autocomplete.IntentBuilder`. This is the recommended way to use the pre-built widget, as it handles session tokens and debouncing automatically.\n\n```kotlin\n@Composable\nfun AutocompleteSearchButton() {\n    val context = LocalContext.current\n    \n    val fields = listOf(Place.Field.ID, Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS)\n    val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)\n        .build(context)\n\n    val launcher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) { result ->\n        if (result.resultCode == Activity.RESULT_OK) {\n            val place = Autocomplete.getPlaceFromIntent(result.data!!)\n            Log.d(\"Autocomplete\", \"Place selected: ${place.name}\")\n        }\n    }\n\n    Button(onClick = { launcher.launch(intent) }) {\n        Text(\"Search Places\")\n    }\n}\n```\n\n## 8. Execution Steps\n\n1. Add the Places SDK dependencies to `build.gradle.kts`.\n2. Set up the Secrets Gradle Plugin in `build.gradle.kts`.\n3. Implement initialization (e.g., in a subclass of `Application`).\n4. Provide a summary of how to use it (retrieve place details, display autocomplete).\n"
  },
  {
    "path": ".gemini/styleguide.md",
    "content": "# Gemini Code Assist Style Guide: android-places-demos\n\nThis guide defines the custom code review and generation rules for the `android-places-demos` project.\n\n## Jetpack Compose Guidelines\n- **API Guidelines**: Strictly follow the [Jetpack Compose API guidelines](https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md).\n- **Naming**: Composable functions must be PascalCase.\n- **Modifiers**: The first optional parameter of any Composable should be `modifier: Modifier = Modifier`.\n\n## Kotlin & Java Style\n- **Naming**: Use camelCase for variables and functions.\n- **Documentation**: Provide KDoc for all public classes, properties, and functions. Explain the \"why\" in comments, not just the \"what\".\n- **Safety**: Use null-safe operators and avoid `!!` in Kotlin. In Java, use standard null checks or `@NonNull`/`@Nullable` annotations if available.\n- **Imports vs FQCNs**: Avoid using Fully Qualified Class Names (FQCNs) in code if a standard `import` statement would suffice. Keep code readable.\n\n## Places SDK Specifics\n- **Secrets**: Never commit API keys. Ensure they are read from `secrets.properties` (or `local.properties`) via `BuildConfig` or injected into `AndroidManifest.xml` by the Secrets Gradle Plugin.\n- **Initialization**:\n  - Strongly recommend `Places.initializeWithNewPlacesApiEnabled` over the legacy `Places.initialize` for modern demos.\n- **Literate Programming**: Write clear, well-documented code that functions as an example for developers. Explain architectural decisions.\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# Copyright 2022 Google LLC\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# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners\n\n.github/ @googlemaps/admin\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: 'type: bug, triage me'\nassignees: ''\n\n---\n\nThanks for stopping by to let us know something could be better!\n\n---\n**PLEASE READ** \n\nIf you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. \n\nDiscover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). \n\nIf your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker).\n\nCheck for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag.\n\n---\n\nPlease be sure to include as much information as possible:\n\n#### Environment details\n\n1. Specify the API at the beginning of the title (for example, \"Places: ...\")\n2. OS type and version\n3. Library version and other environment information\n\n#### Steps to reproduce\n\n  1. ?\n\n#### Code example\n\n```\n# example\n```\n\n#### Stack trace\n```\n# example\n```\n\nFollowing these steps will guarantee the quickest resolution possible.\n\nThanks!\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this library\ntitle: ''\nlabels: 'type: feature request, triage me'\nassignees: ''\n\n---\n\nThanks for stopping by to let us know something could be better!\n\n---\n**PLEASE READ** \n\nIf you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. \n\nDiscover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). \n\nIf your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker).\n\nCheck for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag.\n\n---\n\n **Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n **Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n **Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n **Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/support_request.md",
    "content": "---\nname: Support request\nabout: If you have a support contract with Google, please create an issue in the Google\n  Cloud Support console.\ntitle: ''\nlabels: 'triage me, type: question'\nassignees: ''\n\n---\n\n**PLEASE READ** \n\nIf you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/). This will ensure a timely response. \n\nDiscover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform [support resources page](https://developers.google.com/maps/support/). \n\nIf your bug or feature request is not related to this particular library, please visit the Google Maps Platform [issue trackers](https://developers.google.com/maps/support/#issue_tracker).\n\nCheck for answers on StackOverflow with the [google-maps](http://stackoverflow.com/questions/tagged/google-maps) tag.\n\n---\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE/pull_request_template.md",
    "content": "---\nname: Pull request\nabout: Create a pull request\nlabel: 'triage me'\n---\nThank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:\n- [ ] Make sure to open a GitHub issue as a bug/feature request before writing your code!  That way we can discuss the change, evaluate designs, and agree on the general idea\n- [ ] Ensure the tests and linter pass\n- [ ] Code coverage does not decrease (if any source code was changed)\n- [ ] Appropriate docs were updated (if necessary)\n\nFixes #<issue_number_goes_here> 🦕\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# Copyright 2025 Google LLC\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\nversion: 2\nupdates:\n- package-ecosystem: gradle\n  directory: \"/\"\n  schedule:\n    interval: \"weekly\"\n  open-pull-requests-limit: 10\n  commit-message:\n    prefix: chore(deps)\n"
  },
  {
    "path": ".github/header-checker-lint.yml",
    "content": "# Copyright 2025 Google LLC\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# Presubmit test that ensures that source files contain valid license headers\n# https://github.com/googleapis/repo-automation-bots/tree/main/packages/header-checker-lint\n# Install: https://github.com/apps/license-header-lint-gcf\n\nallowedCopyrightHolders:\n  - 'Google LLC'\nallowedLicenses:\n  - 'Apache-2.0'\nsourceFileExtensions:\n  - 'yaml'\n  - 'yml'\n  - 'sh'\n  - 'ts'\n  - 'js'\n  - 'java'\n  - 'html'\n  - 'txt'\n  - 'kt'\n  - 'kts'\n  - 'xml'\n  - 'gradle'\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "Thank you for opening a Pull Request!\n\n---\n\nBefore submitting your PR, there are a few things you can do to make sure it goes smoothly:\n- [ ] Make sure to open a GitHub issue as a bug/feature request before writing your code!  That way we can discuss the change, evaluate designs, and agree on the general idea\n- [ ] Ensure the tests and linter pass\n- [ ] Code coverage does not decrease (if any source code was changed)\n- [ ] Appropriate docs were updated (if necessary)\n\nFixes #<issue_number_goes_here> 🦕\n"
  },
  {
    "path": ".github/snippet-bot.yml",
    "content": "# Copyright 2025 Google LLC\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"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Copyright 2020 Google LLC\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# Configuration for probot-stale - https://github.com/probot/stale\n\n# Number of days of inactivity before an Issue or Pull Request becomes stale\ndaysUntilStale: 120\n\n# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.\n# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.\ndaysUntilClose: 180\n\n# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)\nonlyLabels: []\n\n# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable\nexemptLabels:\n  - pinned\n  - \"type: bug\"\n\n# Set to true to ignore issues in a project (defaults to false)\nexemptProjects: false\n\n# Set to true to ignore issues in a milestone (defaults to false)\nexemptMilestones: false\n\n# Set to true to ignore issues with an assignee (defaults to false)\nexemptAssignees: false\n\n# Label to use when marking as stale\nstaleLabel: \"stale\"\n\n# Comment to post when marking as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. Please comment here if it is still valid so that we can\n  reprioritize. Thank you!\n\n# Comment to post when removing the stale label.\n# unmarkComment: >\n#   Your comment here.\n\n# Comment to post when closing a stale Issue or Pull Request.\ncloseComment: >\n  Closing this. Please reopen if you believe it should be addressed. Thank you for your contribution.\n\n# Limit the number of actions per hour, from 1-30. Default is 30\nlimitPerRun: 10\n\n# Limit to only `issues` or `pulls`\nonly: issues\n\n# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':\n# pulls:\n#   daysUntilStale: 30\n#   markComment: >\n#     This pull request has been automatically marked as stale because it has not had\n#     recent activity. It will be closed if no further activity occurs. Thank you\n#     for your contributions.\n\n# issues:\n#   exemptLabels:\n#     - confirmed\n"
  },
  {
    "path": ".github/sync-repo-settings.yaml",
    "content": "# Copyright 2022 Google LLC\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# https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings\n\nrebaseMergeAllowed: true\nsquashMergeAllowed: true\nmergeCommitAllowed: false\ndeleteBranchOnMerge: true\nbranchProtectionRules:\n- pattern: main\n  isAdminEnforced: false\n  requiresStrictStatusChecks: false\n  requiredStatusCheckContexts:\n    - 'cla/google'\n    - 'test'\n    - 'snippet-bot check'\n    - 'header-check'\n  requiredApprovingReviewCount: 1\n  requiresCodeOwnerReviews: true\n- pattern: master\n  isAdminEnforced: false\n  requiresStrictStatusChecks: false\n  requiredStatusCheckContexts:\n    - 'cla/google'\n    - 'test'\n    - 'snippet-bot check'\n    - 'header-check'\n  requiredApprovingReviewCount: 1\n  requiresCodeOwnerReviews: true\npermissionRules:\n  - team: admin\n    permission: admin\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "# Copyright 2020 Google LLC\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\nname: Build demos\n\n# Controls when the action will run. Triggers the workflow on push or pull request\n# events but only for the main branch\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  repository_dispatch:\n    types: [ build ]\n  schedule:\n    - cron: '0 0 * * 1'\n  workflow_dispatch:\n\njobs:\n  build-all:\n    runs-on: ubuntu-22.04\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: set up JDK 17\n        uses: actions/setup-java@v4.2.1\n        with:\n          java-version: '17'\n          distribution: 'adopt'\n\n      - name: Install NDK\n        run: |\n          sudo ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install \"ndk;20.0.5594570\"\n\n      - name: Create local.defaults.properties\n        run: |\n          echo \"MAPS3D_API_KEY=YOUR_API_KEY\" >> local.defaults.properties\n\n      - name: Build and verify all modules\n        run: ./gradlew assembleDebug lint --continue\n\n      - name: Run tests\n        run: ./gradlew test --continue\n\n      - name: Upload build reports\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: build-reports\n          path: \"**/build/reports\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# Build output\nbuild/\n.gradle/\n.kotlin/\n\n# IDE files\n.idea/\n*.iml\n\n# Local machine-specific configs\n**/local.properties\n**/secrets.properties\n\n# OS junk\n.DS_Store\n.java-version\n\n# This covers new IDEs, like Antigravity\n.vscode/\n**/bin/"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Google Open Source Community Guidelines\n\nAt Google, we recognize and celebrate the creativity and collaboration of open\nsource contributors and the diversity of skills, experiences, cultures, and\nopinions they bring to the projects and communities they participate in.\n\nEvery one of Google's open source projects and communities are inclusive\nenvironments, based on treating all individuals respectfully, regardless of\ngender identity and expression, sexual orientation, disabilities,\nneurodiversity, physical appearance, body size, ethnicity, nationality, race,\nage, religion, or similar personal characteristic.\n\nWe value diverse opinions, but we value respectful behavior more.\n\nRespectful behavior includes:\n\n* Being considerate, kind, constructive, and helpful.\n* Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or\n  physically threatening behavior, speech, and imagery.\n* Not engaging in unwanted physical contact.\n\nSome Google open source projects [may adopt][] an explicit project code of\nconduct, which may have additional detailed expectations for participants. Most\nof those projects will use our [modified Contributor Covenant][].\n\n[may adopt]: https://opensource.google/docs/releasing/preparing/#conduct\n[modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/\n\n## Resolve peacefully\n\nWe do not believe that all conflict is necessarily bad; healthy debate and\ndisagreement often yields positive results. However, it is never okay to be\ndisrespectful.\n\nIf you see someone behaving disrespectfully, you are encouraged to address the\nbehavior directly with those involved. Many issues can be resolved quickly and\neasily, and this gives people more control over the outcome of their dispute.\nIf you are unable to resolve the matter for any reason, or if the behavior is\nthreatening or harassing, report it. We are dedicated to providing an\nenvironment where participants feel welcome and safe.\n\n## Reporting problems\n\nSome Google open source projects may adopt a project-specific code of conduct.\nIn those cases, a Google employee will be identified as the Project Steward,\nwho will receive and handle reports of code of conduct violations. In the event\nthat a project hasn’t identified a Project Steward, you can report problems by\nemailing opensource@google.com.\n\nWe will investigate every complaint, but you may not receive a direct response.\nWe will use our discretion in determining when and how to follow up on reported\nincidents, which may range from not taking action to permanent expulsion from\nthe project and project-sponsored spaces. We will notify the accused of the\nreport and provide them an opportunity to discuss it before any action is\ntaken. The identity of the reporter will be omitted from the details of the\nreport supplied to the accused. In potentially harmful situations, such as\nongoing harassment or threats to anyone's safety, we may take action without\nnotice.\n\n*This document was adapted from the [IndieWeb Code of Conduct][] and can also\nbe found at <https://opensource.google/conduct/>.*\n\n[IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to become a contributor and submit your own code\n\n## Contributor License Agreements\n\nWe'd love to accept your sample apps and patches! Before we can take them, we \nhave to jump a couple of legal hurdles.\n\nPlease fill out either the individual or corporate Contributor License Agreement\n(CLA).\n\n  * If you are an individual writing original source code and you're sure you\n    own the intellectual property, then you'll need to sign an [individual CLA]\n    (http://code.google.com/legal/individual-cla-v1.0.html).\n  * If you work for a company that wants to allow you to contribute your work,\n    then you'll need to sign a [corporate CLA]\n    (http://code.google.com/legal/corporate-cla-v1.0.html).\n\nFollow either of the two links above to access the appropriate CLA and\ninstructions for how to sign and return it. Once we receive it, we'll be able to\naccept your pull requests.\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 2016 Google\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"
  },
  {
    "path": "PlaceDetailsCompose/.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": "PlaceDetailsCompose/ARCHITECTURE.md",
    "content": "# Architecture\n\nThis sample application demonstrates a modern **MVVM (Model-View-ViewModel)** architecture integrated with **Jetpack Compose** and **Google Maps Platform**.\n\n## Overview\n\nThe app is a single-screen application (`MapScreen`) that allows users to view a map, track their location, and select places to view detailed information using the Google Places UI Kit.\n\n### Key Layers\n\n1.  **UI Layer (Compose)**:\n    -   **`MapScreen`**: The main entry point. Handles the Scaffold, Map rendering (via Maps Compose), and UI controls.\n    -   **`PlaceDetailsView`**: Bridging components (`PlaceDetailsCompactView`, `PlaceDetailsFullView`) that wrap the View-based Places fragments using `AndroidView`.\n    -   **`MapViewModel`**: Holds the UI state (`selectedPlace`, `isFullView`, `deviceLocation`) and handles business logic.\n\n2.  **Data Layer (Repository)**:\n    -   **`LocationRepository`**: A light wrapper around `FusedLocationProviderClient`. It exposes location updates as a Kotlin `Flow`, handling permission checks gracefully.\n\n## Key Patterns\n\n### 1. Compose Interoperability (`AndroidView`)\nThe Google Places UI Kit components (`PlaceDetailsCompactFragment`, `PlaceDetailsFragment`) are standard Android Fragments. To use them in a pure Compose app, we use the `AndroidView` composable to host a `FragmentContainerView`.\n\n-   **Unique IDs**: We generate unique View IDs using `View.generateViewId()` so the `FragmentManager` can correctly identify each container.\n-   **Lifecycle Handling**: Fragments are added/removed via transactions inside the `AndroidView` factory.\n-   **Updates**: Data updates (like changing the Place ID) are posted to the view queue (`view.post`) to ensure the Fragment is fully attached before loading data.\n\n### 2. State Management with Flows\nThe `MapViewModel` uses `StateFlow` to expose reactive state to the UI.\n-   **`flatMapLatest`**: Used for location updates to automatically switch between \"no location\" and \"location updates\" based on permission state.\n-   **`combine` / `map`**: derived state is computed reactively.\n\n## Common Integration Challenges\n\nWhen integrating the Places UI Kit (View-based) into a Jetpack Compose app, there are a few specific implementation details to be aware of:\n\n1.  **\"Context must be a FragmentActivity\" Crash**:\n    -   *Why it happens*: The Places UI Kit fragments (`PlaceDetailsFragment`) rely on the legacy Android `FragmentManager`. This manager is only available in a `FragmentActivity` (or `AppCompatActivity`).\n    -   *The Fix*: We assume the Composable is hosted in an Activity that extends `AppCompatActivity` and cast the `Context` to it.\n\n2.  **`view.post { ... }`**:\n    -   *Why we do it*: Fragment transactions are asynchronous. If we try to call `fragment.loadWithPlaceId` immediately after adding the fragment, the fragment's view might not be created yet, leading to a crash.\n    -   *The Fix*: `view.post` schedules the action to run *after* the current message queue is processed, ensuring the view hierarchy is ready.\n\n3.  **Unique View IDs (`View.generateViewId()`)**:\n    -   *Why*: If you have multiple `AndroidView`s hosting fragments (even if one is hidden), they need distinct IDs so the `FragmentManager` doesn't get confused about which container holds which fragment.\n\n"
  },
  {
    "path": "PlaceDetailsCompose/README.md",
    "content": "# Place Details Compose Sample\n\nThis sample demonstrates how to integrate the **Places UI Kit** (specifically `PlaceDetailsCompactFragment` and `PlaceDetailsFragment`) into a **Jetpack Compose** application.\n\nIt showcases how to wrap these View-based fragments using `AndroidView` to create seamless Composable wrappers: `PlaceDetailsCompactView` and `PlaceDetailsFullView`.\n\n## Features\n\n- **Jetpack Compose Integration**: Demonstrates the `AndroidView` pattern for embedding Places UI Kit fragments.\n- **Compact & Full Views**: Supports both the Compact (bottom sheet style) and Full (fullscreen style) variants of the UI Kit.\n- **Dynamic Toggling**: Users can switch between Compact and Full views at runtime using a toggle switch.\n- **Google Maps Integration**: Uses the Maps Compose library to display an interactive map.\n- **MVVM Architecture**: Manages state (selected place, view mode) using a `MapViewModel`.\n- **Secrets Management**: Securely handles API keys using the Secrets Gradle Plugin.\n\n## Getting Started\n\n1.  **Clone the repository:**\n    ```bash\n    git clone https://github.com/googlemaps-samples/android-places-demos.git\n    ```\n2.  **Open in Android Studio:** Open the `PlaceDetailsCompose` directory.\n3.  **Add API Key:**\n    -   Create `secrets.properties` in the project root.\n    -   Add your key: `PLACES_API_KEY=\"YOUR_API_KEY\"` (ensure Places API and Maps SDK are enabled).\n4.  **Run:** Build and run on a device/emulator.\n\n## Code Highlights\n\n### 1. Wrapping Fragments in Compose (`PlaceDetailsView.kt`)\n\nThe core of this integration is wrapping the `PlaceDetailsCompactFragment` and `PlaceDetailsFragment` in a Composable. We use `AndroidView` to host a `FragmentContainerView`.\n\n**Key Steps:**\n-   **Unique ID**: Generate a unique view ID (`View.generateViewId()`) for the container so `FragmentManager` can identify it.\n-   **Fragment Management**: In the `update` block, check if the fragment exists. If not, create and add it.\n-   **Safe Loading**: Use `view.post { ... }` to call `loadWithPlaceId`. This ensures the fragment's view is fully attached before data loading begins, preventing crashes.\n\n```kotlin\n@Composable\nfun PlaceDetailsCompactView(place: PointOfInterest, ...) {\n    val fragmentContainerId = remember { View.generateViewId() }\n    \n    AndroidView(\n        factory = { context ->\n            FragmentContainerView(context).apply { id = fragmentContainerId }\n        },\n        update = { view ->\n            // ... Fragment transaction logic ...\n            view.post { fragment.loadWithPlaceId(place.placeId) }\n        }\n    )\n}\n```\n\n### 2. Switching Views (`MapScreen.kt`)\n\nThe app demonstrates how to dynamically switch between the Compact and Full views while maintaining the selected place context.\n\n```kotlin\nvar isFullView by remember { mutableStateOf(false) }\n\nif (isFullView) {\n    PlaceDetailsFullView(place = place, ...)\n} else {\n    PlaceDetailsCompactView(place = place, ...)\n}\n```\n\n### 3. Handling Events\n\nWe use `PlaceLoadListener` attached to the fragment to listen for success/failure events. These are propagated back to the Compose layer via callbacks (e.g., `onDismiss`).\n\n## License\n\n```\nCopyright 2025 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the 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,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n"
  },
  {
    "path": "PlaceDetailsCompose/build.gradle.kts",
    "content": "/*\n * Copyright 2025 Google LLC\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// The `plugins` block is where we apply Gradle plugins to this module.\n// Plugins add new tasks and configurations to our build process.\nplugins {\n    id(\"places-demo.android.application\")\n    // This plugin enables Kotlin support in the Android project, allowing us to write code in Kotlin.\n    alias(libs.plugins.kotlin.android)\n    // This plugin from Google helps manage API keys and other secrets by reading them from a `secrets.properties`\n    // file (which should be in .gitignore) and exposing them in the `BuildConfig` file at compile time.\n    // This is crucial for keeping sensitive data out of version control.\n    id(\"places-demo.secrets\")\n    // This plugin provides the necessary integration for using Jetpack Compose with the Kotlin compiler.\n    alias(libs.plugins.kotlin.compose)\n}\n\n// The `android` block is where we configure all the Android-specific build options.\nandroid {\n    // The `namespace` is a unique identifier for the app's generated R class. It's also used\n    // as the default `applicationId` if not specified in `defaultConfig`.\n    namespace = \"com.example.placedetailscompose\"\n\n    defaultConfig {\n        // `applicationId` is the unique identifier for the app on the Google Play Store and on the device.\n        applicationId = \"com.example.placedetailscompose\"\n        // `minSdk` is the minimum API level required to run the app. Devices below this level cannot install it.\n        minSdk = 27\n        versionCode = 1\n        versionName = \"1.0\"\n\n        // Specifies the instrumentation runner for running Android tests.\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        // The `release` block configures settings for the release build of the app.\n        release {\n            // `isMinifyEnabled` enables code shrinking with R8 to reduce the app's size.\n            // It's disabled here for simplicity in a sample app, but highly recommended for production.\n            isMinifyEnabled = false\n            // `proguardFiles` specifies the files that define the R8 shrinking and obfuscation rules.\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    kotlin {\n        // Configures Kotlin-specific compiler options.\n        compilerOptions {\n            freeCompilerArgs.addAll(\n                \"-opt-in=kotlin.RequiresOptIn\",\n                \"-Xannotation-default-target=param-property\"\n            )\n        }\n    }\n    buildFeatures {\n        // `compose` enables Jetpack Compose for the project.\n        compose = true\n        // `buildConfig` generates a `BuildConfig` class that contains constants from the build configuration,\n        // such as the API key from the secrets plugin.\n        buildConfig = true\n        // `viewBinding` generates a binding class for each XML layout file.\n        viewBinding = true\n    }\n    composeOptions {\n        // Sets the version of the Kotlin compiler extension for Compose. This version must be\n        // compatible with the Kotlin version used in the project.\n        kotlinCompilerExtensionVersion = \"1.5.1\"\n    }\n    packaging {\n        resources {\n            excludes += \"/META-INF/{AL2.0,LGPL2.1}\"\n        }\n    }\n}\n\n// The `dependencies` block is where we declare all the external libraries the app needs.\n// These are fetched from repositories like Maven Central and Google's Maven repository.\ndependencies {\n    // --- Core AndroidX & UI Libraries ---\n    // These are foundational libraries for building modern Android apps.\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.activity.compose)\n    implementation(libs.androidx.lifecycle.viewmodel.compose)\n    implementation(libs.androidx.fragment.ktx)\n    implementation(libs.androidx.compose.material.icons.core)\n\n    // --- Google Play Services ---\n    // These are the essential libraries for this sample, providing Maps and Places functionality.\n    implementation(libs.google.maps.services) // The core SDK for embedding Google Maps.\n    implementation(libs.places) // The SDK for the Places UI Kit (PlaceDetails fragments).\n    implementation(libs.play.services.location) // Needed for the FusedLocationProviderClient to get the device's location.\n    implementation(libs.maps.compose)\n    implementation(libs.maps.compose.widgets)\n    implementation(libs.maps.utils.ktx)\n    implementation(libs.material) // For Material Design components (used in XML layouts).\n\n    // --- Jetpack Compose ---\n    // These libraries are for building UIs with Jetpack Compose.\n    implementation(libs.androidx.material3) // The latest Material Design components for Compose.\n    implementation(platform(libs.androidx.compose.bom)) // The Compose Bill of Materials (BOM) ensures all Compose libraries use compatible versions.\n    implementation(libs.androidx.ui.tooling.preview) // For displaying @Preview composables in Android Studio.\n    implementation(libs.androidx.ui.viewbinding)\n    implementation(libs.androidx.material.icons.extended)\n    debugImplementation(libs.androidx.ui.tooling) // Provides tools for inspecting Compose UIs.\n\n    // --- Testing Libraries ---\n    // These libraries are for writing and running tests.\n    // `testImplementation` is for local unit tests (running on the JVM).\n    testImplementation(libs.junit)\n    // `androidTestImplementation` is for instrumented tests (running on an Android device or emulator).\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core) // For UI testing with the View system.\n    // AndroidX libraries for creating test rules and running tests.\n    androidTestImplementation(libs.androidx.test.rules)\n    androidTestImplementation(libs.androidx.test.runner)\n\n    // --- Compose Testing ---\n    // These are specific to testing Jetpack Compose UIs.\n    androidTestImplementation(platform(libs.androidx.compose.bom)) // BOM for testing libraries.\n    androidTestImplementation(libs.androidx.ui.test.junit4) // The main library for Compose UI tests.\n    debugImplementation(libs.androidx.ui.test.manifest) // Provides a manifest for UI tests.\n}\n"
  },
  {
    "path": "PlaceDetailsCompose/local.defaults.properties",
    "content": "PLACES_API_KEY=YOUR_API_KEY\nMAPS_API_KEY=YOUR_API_KEY\nMAP_ID=YOUR_MAP_ID\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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,\nWITHOUT 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<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.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n\n    <application\n        android:name=\".PlaceDetailsComposeApplication\"\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.PlaceDetailsCompose\"\n        tools:targetApi=\"36\">\n\n        <meta-data\n            android:name=\"com.google.android.geo.API_KEY\"\n            android:value=\"${MAPS_API_KEY}\"/>\n\n        <activity\n            android:name=\".MainActivity\"\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    </application>\n\n</manifest>"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/MainActivity.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose\n\nimport android.Manifest\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.util.Log\nimport android.widget.Toast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.SideEffect\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.core.content.ContextCompat\nimport androidx.core.view.WindowCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.WindowInsetsControllerCompat\nimport com.example.placedetailscompose.ui.map.MapScreen\nimport com.example.placedetailscompose.ui.theme.PlaceDetailsComposeTheme\nimport com.google.android.libraries.places.api.Places\n\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        // Retrieve the API key from the local.properties file.\n        // See https://github.com/googlemaps/android-places-demos#installation for more details.\n        val apiKey = BuildConfig.PLACES_API_KEY\n        if (apiKey.isEmpty() || apiKey == \"YOUR_API_KEY\") {\n            Log.e(\"PlacesCompose\", \"No api key\")\n            Toast.makeText(\n                this,\n                \"Add your own API_KEY in local.properties\",\n                Toast.LENGTH_LONG\n            ).show()\n            finish()\n            return\n        }\n\n        // Initialize the Places SDK. This must be done before calling any other Places API methods.\n        // The 'newPlacesApiEnabled' flag indicates that the new Places API should be used.\n        // This can happen in an Activity or the Application.\n        Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)\n\n        enableEdgeToEdge()\n        setContent {\n            val window = this.window\n            val insetsController = WindowCompat.getInsetsController(window, window.decorView)\n\n            // Educational Note: We are handling permissions directly within the Compose scope\n            // here to keep this Place Details sample self-contained and easy to follow.\n            // In a production app, you might prefer to hoist this logic to a ViewModel\n            // or a dedicated permission handler class.\n\n            // Check if we already have the permission.\n            // Using ContextCompat.checkSelfPermission ensures we respect the state if the \n            // user granted it previously via system settings.\n            var hasPermission by remember {\n                mutableStateOf(\n                    ContextCompat.checkSelfPermission(\n                        this,\n                        Manifest.permission.ACCESS_FINE_LOCATION\n                    ) == PackageManager.PERMISSION_GRANTED\n                )\n            }\n\n            // The standard, modern Compose way to register for Activity Results (like Permissions)\n            // within a Composable scope.\n            val launcher = rememberLauncherForActivityResult(\n                contract = ActivityResultContracts.RequestPermission(),\n                onResult = { granted ->\n                    if (granted) {\n                        hasPermission = true\n                    } else {\n                        Toast.makeText(this, \"Location permission is required to use this app.\", Toast.LENGTH_LONG).show()\n                    }\n                }\n            )\n\n            // Trigger the permission request when this Composable first enters the composition.\n            // The 'Unit' key ensures this side-effect only runs once on mount.\n            LaunchedEffect(Unit) {\n                if (!hasPermission) {\n                    launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION)\n                }\n            }\n\n            SideEffect {\n                insetsController.hide(WindowInsetsCompat.Type.systemBars())\n                insetsController.systemBarsBehavior =\n                    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE\n            }\n\n            PlaceDetailsComposeTheme {\n                if (hasPermission) {\n                    MapScreen()\n                } else {\n                    Box(\n                        modifier = Modifier.fillMaxSize(),\n                        contentAlignment = Alignment.Center\n                    ) {\n                        Button(onClick = { launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION) }) {\n                            Text(\"Grant Location Permission\")\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/PlaceDetailsComposeApplication.kt",
    "content": "/*\n* Copyright 2025 Google LLC\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.example.placedetailscompose\n\nimport android.app.Application\nimport android.content.pm.PackageManager\nimport android.util.Log\nimport android.widget.Toast\nimport java.util.Objects\nimport kotlin.text.isBlank\n\n/**\n * `PlaceDetailsComposeApplication` is a custom Application class.\n *\n * This class is responsible for application-wide initialization and setup,\n * such as checking for the presence and validity of the API key during the\n * application's startup.\n *\n * It extends the [Application] class and overrides the [.onCreate]\n * method to perform these initialization tasks.\n */\nclass PlaceDetailsComposeApplication : Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n        checkApiKey()\n    }\n\n    /**\n     * Checks if the API key for Google Maps is properly configured in the application's metadata.\n     *\n     * This method retrieves the API key from the application's metadata, specifically looking for\n     * a string value associated with the key \"com.google.android.geo.API_KEY\".\n     * The key must be present, not blank, and not set to the placeholder value \"DEFAULT_API_KEY\".\n     *\n     * If any of these checks fail, a Toast message is displayed indicating that the API key is missing or\n     * incorrectly configured, and a RuntimeException is thrown.\n     */\n    private fun checkApiKey() {\n        try {\n            val appInfo =\n                packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)\n            val bundle = Objects.requireNonNull(appInfo.metaData)\n\n            val apiKey =\n                bundle.getString(\"com.google.android.geo.API_KEY\") // Key name is important!\n\n            if (apiKey == null || apiKey.isBlank() || apiKey == \"DEFAULT_API_KEY\") {\n                Toast.makeText(\n                    this,\n                    getString(R.string.error_api_key_missing),\n                    Toast.LENGTH_LONG\n                ).show()\n                throw RuntimeException(getString(R.string.error_api_key_missing))\n            }\n        } catch (e: PackageManager.NameNotFoundException) {\n            Log.e(TAG, \"Package name not found.\", e)\n            throw RuntimeException(\"Error getting package info.\", e)\n        } catch (e: NullPointerException) {\n            Log.e(TAG, \"Error accessing meta-data.\", e) // Handle the case where meta-data is completely missing.\n            throw RuntimeException(\"Error accessing meta-data in manifest\", e)\n        }\n    }\n\n    /**\n     * Retrieves the map ID from the BuildConfig or string resource.\n     *\n     * @return The valid map ID or null if no valid map ID is found.\n     */\n    val mapId: String? by lazy {\n        if (BuildConfig.MAP_ID != \"YOUR_MAP_ID\") {\n            BuildConfig.MAP_ID\n        } else if (getString(R.string.map_id) != \"YOUR_MAP_ID\") {\n            getString(R.string.map_id)\n        } else {\n            Log.w(TAG, \"Map ID is not set. See README for instructions.\")\n            Toast.makeText(this, getString(R.string.error_map_id_missing), Toast.LENGTH_LONG)\n                .show()\n            null\n        }\n    }\n\n    companion object {\n        private const val TAG = \"ApiDemoApplication\"\n    }\n}"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/repository/LocationRepository.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose.repository\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.location.Location\nimport android.os.Looper\nimport com.google.android.gms.location.FusedLocationProviderClient\nimport com.google.android.gms.location.LocationCallback\nimport com.google.android.gms.location.LocationRequest\nimport com.google.android.gms.location.LocationResult\nimport com.google.android.gms.location.LocationServices\nimport com.google.android.gms.location.Priority\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\n\nclass LocationRepository(context: Context) {\n\n    private val fusedLocationClient: FusedLocationProviderClient =\n        LocationServices.getFusedLocationProviderClient(context)\n\n    @SuppressLint(\"MissingPermission\")\n    fun getDeviceLocation(): Flow<Location> = callbackFlow {\n        val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000L)\n            .setWaitForAccurateLocation(false)\n            .setMinUpdateIntervalMillis(5000L)\n            .build()\n\n        val locationCallback = object : LocationCallback() {\n            override fun onLocationResult(locationResult: LocationResult) {\n                locationResult.lastLocation?.let { trySend(it) }\n            }\n        }\n\n        try {\n            fusedLocationClient.requestLocationUpdates(\n                locationRequest,\n                locationCallback,\n                Looper.getMainLooper()\n            )\n        } catch (e: SecurityException) {\n            // Permissions were likely denied.\n            // In a real app, we might want to emit an error state or log this.\n            // For now, we just close the flow to avoid a crash.\n            close(e)\n        }\n\n        awaitClose {\n            fusedLocationClient.removeLocationUpdates(locationCallback)\n        }\n    }\n}\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/ui/map/MapScreen.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose.ui.map\n\nimport android.Manifest\nimport android.content.pm.PackageManager\nimport android.widget.Toast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.expandHorizontally\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.shrinkHorizontally\nimport androidx.compose.ui.draw.scale\nimport androidx.compose.foundation.background\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.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.ChevronLeft\nimport androidx.compose.material.icons.filled.Settings\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\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.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.core.app.ActivityCompat\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.example.placedetailscompose.R\nimport com.example.placedetailscompose.viewmodels.MapViewModel\nimport com.google.android.gms.maps.CameraUpdateFactory\nimport com.google.android.gms.maps.model.CameraPosition\nimport com.google.maps.android.compose.Circle\nimport com.google.maps.android.compose.ComposeMapColorScheme\nimport com.google.maps.android.compose.GoogleMap\nimport com.google.maps.android.compose.MapProperties\nimport com.google.maps.android.compose.MapType\nimport com.google.maps.android.compose.MapUiSettings\nimport com.google.maps.android.compose.rememberCameraPositionState\nimport com.google.maps.android.ktx.utils.sphericalDistance\nimport com.google.maps.android.ktx.utils.withSphericalOffset\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\n/**\n * The main screen of the app. This screen shows a map and allows the user to select a\n * point of interest to see details about it.\n */\n@Composable\nfun MapScreen(\n    viewModel: MapViewModel = viewModel()\n) {\n    val context = LocalContext.current\n    val deviceLocation by viewModel.deviceLocation.collectAsState()\n    val selectedPlace by viewModel.selectedPlace.collectAsState()\n    val isMapFollowingUser by viewModel.isMapFollowingUser.collectAsState()\n    val hasAnimatedToPlace by viewModel.hasAnimatedToPlace.collectAsState()\n    \n    // **View Mode State**\n    var isFullView by rememberSaveable { mutableStateOf(false) }\n\n    // **Coordinate Mode State**\n    val isCoordinateMode by viewModel.isCoordinateMode.collectAsState()\n\n    val cameraPositionState = rememberCameraPositionState {\n        position = CameraPosition.fromLatLngZoom(viewModel.sydney, 13f)\n    }\n\n    val permissionDeniedString = stringResource(R.string.location_permission_denied)\n    val locationPermissionLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.RequestMultiplePermissions()\n    ) { permissions ->\n        if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true ||\n            permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true\n        ) {\n            viewModel.onPermissionGranted()\n        } else {\n            Toast.makeText(context, permissionDeniedString, Toast.LENGTH_SHORT).show()\n        }\n    }\n\n    LaunchedEffect(Unit) {\n        if (ActivityCompat.checkSelfPermission(\n                context,\n                Manifest.permission.ACCESS_FINE_LOCATION\n            ) == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(\n                context,\n                Manifest.permission.ACCESS_COARSE_LOCATION\n            ) == PackageManager.PERMISSION_GRANTED\n        ) {\n            viewModel.onPermissionGranted()\n        } else {\n            locationPermissionLauncher.launch(\n                arrayOf(\n                    Manifest.permission.ACCESS_FINE_LOCATION,\n                    Manifest.permission.ACCESS_COARSE_LOCATION\n                )\n            )\n        }\n    }\n\n    LaunchedEffect(selectedPlace) {\n        selectedPlace?.let { place ->\n            val latLng = place.location\n            if (latLng != null) {\n                val focalPoint = latLng.withSphericalOffset(300.0, 180.0)\n                val placeCameraPosition = CameraPosition.builder()\n                    .target(focalPoint)\n                    .zoom(15f)\n                    .build()\n                if (hasAnimatedToPlace) {\n                    cameraPositionState.move(CameraUpdateFactory.newCameraPosition(placeCameraPosition))\n                } else {\n                    cameraPositionState.animate(CameraUpdateFactory.newCameraPosition(placeCameraPosition), 1000)\n                    viewModel.onAnimateToPlaceFinish()\n                }\n            }\n        }\n    }\n\n    LaunchedEffect(deviceLocation, isMapFollowingUser) {\n        deviceLocation?.let { location ->\n            if (isMapFollowingUser) {\n                val currentPosition = cameraPositionState.position.target\n                val distance = currentPosition.sphericalDistance(location)\n                if (distance > 100) {\n                    cameraPositionState.animate(CameraUpdateFactory.newLatLngZoom(location, 15f))\n                }\n            }\n        }\n    }\n\n    val selectedCompactContent by viewModel.selectedCompactContent.collectAsState()\n    val selectedFullContent by viewModel.selectedFullContent.collectAsState()\n    var showContentSelectionDialog by rememberSaveable { mutableStateOf(false) }\n    val coroutineScope = rememberCoroutineScope()\n\n    var showSettingsButton by remember { mutableStateOf(true) }\n    LaunchedEffect(showSettingsButton) {\n        if (showSettingsButton) {\n            delay(5000)\n            showSettingsButton = false\n        }\n    }\n\n    if (cameraPositionState.isMoving) {\n        // Reset the timer whenever the user is dragging the map.\n        showSettingsButton = true\n        viewModel.onMapDragged()\n    }\n\n    Box(modifier = Modifier.fillMaxSize()) {\n        GoogleMap(\n            modifier = Modifier.fillMaxSize(),\n            cameraPositionState = cameraPositionState,\n            properties = MapProperties(\n                isMyLocationEnabled = true,\n                mapType = MapType.NORMAL\n            ),\n            uiSettings = MapUiSettings(\n                myLocationButtonEnabled = true,\n                zoomControlsEnabled = false\n            ),\n            onMapLoaded = {\n                showSettingsButton = true\n            },\n            onPOIClick = { poi ->\n                showSettingsButton = true\n                if (!isCoordinateMode) {\n                    coroutineScope.launch {\n                        val cameraPosition = CameraPosition.builder()\n                            .target(poi.latLng)\n                            .zoom(15f)\n                            .build()\n                        cameraPositionState.animate(CameraUpdateFactory.newCameraPosition(cameraPosition), 2000)\n                    }\n                    viewModel.onPoiClicked(poi)\n                }\n            },\n            onMapClick = { latLng ->\n                showSettingsButton = true\n                viewModel.onMapClicked(latLng)\n            },\n            mapColorScheme = ComposeMapColorScheme.FOLLOW_SYSTEM\n        ) {\n            selectedPlace?.location?.let {\n                Circle(\n                    center = it,\n                    radius = 75.0,\n                    fillColor = Color(0x880088FF),\n                    strokeWidth = 2f,\n                    strokeColor = Color(0xAA000000)\n                )\n            }\n        }\n\n        var isControlsExpanded by rememberSaveable { mutableStateOf(false) }\n\n        AnimatedVisibility(\n            visible = showSettingsButton,\n            enter = fadeIn(),\n            exit = fadeOut()\n        ) {\n            Column(\n                modifier = Modifier\n                    .padding(top = 48.dp, start = 16.dp)\n                    .align(Alignment.TopStart),\n                horizontalAlignment = Alignment.Start\n            ) {\n                FloatingActionButton(\n                    onClick = {\n                        isControlsExpanded = !isControlsExpanded\n                        // Keep the button visible while the controls are expanded\n                        showSettingsButton = true\n                    },\n                    modifier = Modifier.padding(bottom = 8.dp),\n                    containerColor = MaterialTheme.colorScheme.surface,\n                    contentColor = MaterialTheme.colorScheme.onSurface\n                ) {\n                    Icon(\n                        imageVector = if (isControlsExpanded) Icons.Default.ChevronLeft else Icons.Default.Settings,\n                        contentDescription = if (isControlsExpanded) stringResource(R.string.collapse_settings) else stringResource(R.string.expand_settings)\n                    )\n                }\n\n                AnimatedVisibility(\n                    visible = isControlsExpanded,\n                    enter = expandHorizontally(expandFrom = Alignment.Start) + fadeIn(),\n                    exit = shrinkHorizontally(shrinkTowards = Alignment.Start) + fadeOut()\n                ) {\n                    androidx.compose.material3.ElevatedCard(\n                        modifier = Modifier\n                            .padding(8.dp)\n                            .background(MaterialTheme.colorScheme.surface, RoundedCornerShape(12.dp))\n                    ) {\n                        Column(\n                            modifier = Modifier.padding(16.dp),\n                            horizontalAlignment = Alignment.CenterHorizontally\n                        ) {\n                            Row(\n                                modifier = Modifier.padding(bottom = 8.dp),\n                                horizontalArrangement = Arrangement.spacedBy(24.dp),\n                                verticalAlignment = Alignment.CenterVertically\n                            ) {\n                                Column(\n                                    horizontalAlignment = Alignment.CenterHorizontally,\n                                    modifier = Modifier.padding(end = 16.dp)\n                                ) {\n                                    Text(\n                                        text = if (isFullView) stringResource(R.string.full_view) else stringResource(R.string.compact_view),\n                                        style = MaterialTheme.typography.labelSmall,\n                                        modifier = Modifier.padding(bottom = 4.dp)\n                                    )\n                                    Switch(\n                                        checked = isFullView,\n                                        onCheckedChange = { isFullView = it },\n                                        modifier = Modifier.scale(0.8f)\n                                    )\n                                }\n                                Column(\n                                    horizontalAlignment = Alignment.CenterHorizontally\n                                ) {\n                                    Text(\n                                        text = if (isCoordinateMode) stringResource(R.string.coords_mode) else stringResource(R.string.poi_mode),\n                                        style = MaterialTheme.typography.labelSmall,\n                                        modifier = Modifier.padding(bottom = 4.dp)\n                                    )\n                                    Switch(\n                                        checked = isCoordinateMode,\n                                        onCheckedChange = { viewModel.onToggleCoordinateMode(it) },\n                                        modifier = Modifier.scale(0.8f)\n                                    )\n                                }\n                            }\n                            androidx.compose.material3.HorizontalDivider(modifier = Modifier.padding(vertical = 12.dp))\n                            androidx.compose.material3.FilledTonalButton(\n                                onClick = { showContentSelectionDialog = true },\n                                modifier = Modifier.fillMaxWidth()\n                            ) {\n                                Text(stringResource(R.string.select_fields))\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        selectedPlace?.let { place ->\n            if (isFullView) {\n                PlaceDetailsFullView(\n                    place = place,\n                    onDismiss = { viewModel.onDismissPlace() },\n                    content = selectedFullContent,\n                    modifier = Modifier\n                        .align(Alignment.BottomCenter)\n                        .fillMaxWidth()\n                        .fillMaxHeight(0.9f)\n                )\n            } else {\n                PlaceDetailsCompactView(\n                    place = place,\n                    onDismiss = { viewModel.onDismissPlace() },\n                    content = selectedCompactContent,\n                    modifier = Modifier\n                        .align(Alignment.BottomCenter)\n                        .fillMaxWidth()\n                )\n            }\n        }\n\n        if (showContentSelectionDialog) {\n            if (isFullView) {\n                PlaceContentSelectionDialog(\n                    title = stringResource(R.string.select_full_view_fields),\n                    allContent = com.google.android.libraries.places.widget.PlaceDetailsFragment.Content.values().toList(),\n                    selectedContent = selectedFullContent,\n                    onSelectionChanged = { viewModel.updateFullContent(it) },\n                    onDismissRequest = { showContentSelectionDialog = false },\n                    nameProvider = { it.name }\n                )\n            } else {\n                PlaceContentSelectionDialog(\n                    title = stringResource(R.string.select_compact_view_fields),\n                    allContent = com.google.android.libraries.places.widget.PlaceDetailsCompactFragment.Content.values().toList(),\n                    selectedContent = selectedCompactContent,\n                    onSelectionChanged = { viewModel.updateCompactContent(it) },\n                    onDismissRequest = { showContentSelectionDialog = false },\n                    nameProvider = { it.name }\n                )\n            }\n        }\n    }\n}"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/ui/map/PlaceContentSelectionDialog.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose.ui.map\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\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.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.Checkbox\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.res.stringResource\n\n@Composable\nfun <T> PlaceContentSelectionDialog(\n    title: String,\n    allContent: List<T>,\n    selectedContent: List<T>,\n    onSelectionChanged: (List<T>) -> Unit,\n    onDismissRequest: () -> Unit,\n    nameProvider: (T) -> String\n) {\n    AlertDialog(\n        onDismissRequest = onDismissRequest,\n        title = { Text(text = title) },\n        text = {\n            LazyColumn {\n                items(allContent) { item ->\n                    val isSelected = selectedContent.contains(item)\n                    Row(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .clickable {\n                                val newSelection = if (isSelected) {\n                                    selectedContent - item\n                                } else {\n                                    selectedContent + item\n                                }\n                                onSelectionChanged(newSelection)\n                            }\n                            .padding(vertical = 8.dp),\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Checkbox(\n                            checked = isSelected,\n                            onCheckedChange = null // Handled by Row click\n                        )\n                        Text(\n                            text = nameProvider(item),\n                            modifier = Modifier.padding(start = 8.dp),\n                            style = MaterialTheme.typography.bodyMedium\n                        )\n                    }\n                }\n            }\n        },\n        confirmButton = {\n            TextButton(onClick = onDismissRequest) {\n                Text(stringResource(com.example.placedetailscompose.R.string.done))\n            }\n        }\n    )\n}\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/ui/map/PlaceDetailsView.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose.ui.map\n\nimport android.content.res.Configuration\nimport android.util.Log\nimport android.view.View\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalConfiguration\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.fragment.app.FragmentContainerView\nimport androidx.compose.ui.res.stringResource\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.widget.PlaceDetailsCompactFragment\nimport com.google.android.libraries.places.widget.PlaceLoadListener\nimport com.google.android.libraries.places.widget.PlaceDetailsFragment\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Close\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.shape.CircleShape\nimport com.google.android.libraries.places.widget.model.Orientation\n\n/**\n * This composable displays the **Compact** version of the Place Details UI.\n *\n * **Why use `AndroidView`?**\n * The Places UI Kit components (`PlaceDetailsCompactFragment` and `PlaceDetailsFragment`) are currently\n * implemented as Android Fragments, not native Composables. To use them in a Jetpack Compose app,\n * we need to bridge the gap using `AndroidView`. This allows us to host a legacy View (in this case,\n * a `FragmentContainerView`) inside our Compose layout.\n *\n * @param place The point of interest to display details for.\n * @param onDismiss A callback to be invoked when the place details fragment is dismissed.\n */\n@Composable\nfun PlaceDetailsCompactView(\n    place: Place,\n    onDismiss: () -> Unit,\n    modifier: Modifier = Modifier,\n    content: List<PlaceDetailsCompactFragment.Content> = PlaceDetailsCompactFragment.ALL_CONTENT,\n) {\n    // We need to know the device orientation to tell the Fragment how to lay itself out.\n    // Although Compose handles layout differently, the underlying Fragment still relies on this signal.\n    val orientation =\n        if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) {\n            Orientation.HORIZONTAL\n        } else {\n            Orientation.VERTICAL\n        }\n\n    val context = LocalContext.current\n\n    // **Why generate a View ID?**\n    // The FragmentManager needs a unique ID to identify the container where the fragment will be placed.\n    // `View.generateViewId()` gives us a safe, unique integer that won't collide with other views.\n    // We use `remember` so this ID persists across recompositions.\n    val fragmentContainerId = remember { View.generateViewId() }\n\n    // We need the FragmentManager to perform Fragment transactions (adding/removing the fragment).\n    // We cast the Context to AppCompatActivity assuming this Composable is hosted within one.\n    // In a production app, you might want a more robust way to provide the FragmentManager.\n    val fragmentManager = remember(context) {\n        (context as? AppCompatActivity)?.supportFragmentManager\n            ?: throw IllegalStateException(\"Context must be a FragmentActivity\")\n    }\n\n    val fragment = remember(fragmentManager, fragmentContainerId, orientation, content) {\n        fragmentManager.findFragmentById(fragmentContainerId) as? PlaceDetailsCompactFragment\n            ?: PlaceDetailsCompactFragment.newInstance(\n                content,\n                orientation,\n            ).also { fragment ->\n                // **Listening for Load Events**\n                fragment.setPlaceLoadListener(object : PlaceLoadListener {\n                    override fun onSuccess(place: Place) {\n                        Log.d(\"PlaceDetails\", \"Loading details for: ${place.id} at ${place.location}\")\n                    }\n\n                    override fun onFailure(e: Exception) {\n                        Log.d(\"PlaceDetailsView\", \"Place failed to load place: ${e.message}\")\n                        onDismiss()\n                    }\n                })\n            }\n    }\n\n    Box(modifier = modifier.fillMaxWidth()) {\n        AndroidView(\n            modifier = Modifier.fillMaxWidth(),\n            factory = { context ->\n                // **The Factory Block**\n                // This runs only once when the AndroidView is first created.\n                // We create the container view that will hold our Fragment.\n                FragmentContainerView(context).apply {\n                    id = fragmentContainerId\n                    // Ensure the fragment is added.\n                    // We use commit() (async) to allow the view to be attached before the transaction runs.\n                    if (fragmentManager.findFragmentById(fragmentContainerId) == null) {\n                        fragmentManager.beginTransaction()\n                            .add(fragmentContainerId, fragment)\n                            .commit()\n                    }\n                }\n            },\n            update = { view ->\n                // **The Update Block**\n                // This runs whenever the Composable recomposes (e.g., when `place` changes).\n\n                // We post the update to ensure it runs after the fragment transaction has completed\n                // and the fragment's view hierarchy is fully initialized.\n                view.post {\n                    // Load the place data\n                    if (place.id != null) {\n                        fragment.loadWithPlaceId(place.id!!)\n                    } else if (place.location != null) {\n                         fragment.loadWithCoordinates(place.location!!)\n                    } else {\n                        Log.e(\"PlaceDetailsView\", \"Place has no ID and no location: $place\")\n                    }\n                }\n            }\n        )\n\n        // Close Button\n        IconButton(\n            onClick = onDismiss,\n            modifier = Modifier\n                .align(Alignment.TopEnd)\n                .padding(16.dp)\n                .background(MaterialTheme.colorScheme.surface.copy(alpha = 0.7f), shape = CircleShape)\n        ) {\n            Icon(\n                imageVector = Icons.Default.Close,\n                contentDescription = stringResource(com.example.placedetailscompose.R.string.close),\n                tint = MaterialTheme.colorScheme.onSurface\n            )\n        }\n    }\n}\n\n/**\n * This composable displays the **Full** version of the Place Details UI.\n *\n * It follows the same pattern as [PlaceDetailsCompactView], but wraps the [PlaceDetailsFragment]\n * instead. This fragment takes up more screen space and shows more detailed information.\n */\n@Composable\nfun PlaceDetailsFullView(\n    place: Place,\n    onDismiss: () -> Unit,\n    modifier: Modifier = Modifier,\n    content: List<PlaceDetailsFragment.Content> = PlaceDetailsFragment.STANDARD_CONTENT,\n) {\n    val orientation =\n        if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) {\n            Orientation.HORIZONTAL\n        } else {\n            Orientation.VERTICAL\n        }\n\n    val context = LocalContext.current\n    val fragmentManager = remember(context) {\n        (context as? AppCompatActivity)?.supportFragmentManager\n            ?: throw IllegalStateException(\"Context must be a FragmentActivity\")\n    }\n\n    val fragmentContainerId = remember { View.generateViewId() }\n\n    val fragment = remember(fragmentManager, fragmentContainerId, orientation, content) {\n        fragmentManager.findFragmentById(fragmentContainerId) as? PlaceDetailsFragment\n            ?: PlaceDetailsFragment.newInstance(\n                content,\n                orientation,\n            ).also { fragment ->\n                fragment.setPlaceLoadListener(object : PlaceLoadListener {\n                    override fun onSuccess(place: Place) {\n                        Log.d(\"PlaceDetailsFullView\", \"Place loaded: $place\")\n                    }\n\n                    override fun onFailure(e: Exception) {\n                        Log.d(\"PlaceDetailsFullView\", \"Place failed to load place: ${e.message}\")\n                        onDismiss()\n                    }\n                })\n            }\n    }\n\n    Box(modifier = modifier.fillMaxSize()) {\n        // Container for the bottom sheet content\n        Box(\n            modifier = Modifier\n                .fillMaxWidth()\n                .align(Alignment.BottomCenter)\n        ) {\n            AndroidView(\n                modifier = Modifier.fillMaxWidth(),\n                factory = { context ->\n                    FragmentContainerView(context).apply {\n                        id = fragmentContainerId\n                        if (fragmentManager.findFragmentById(fragmentContainerId) == null) {\n                            fragmentManager.beginTransaction()\n                                .add(fragmentContainerId, fragment)\n                                .commit()\n                        }\n                    }\n                },\n                update = { view ->\n                    view.post {\n                        if (place.id != null) {\n                            fragment.loadWithPlaceId(place.id!!)\n                        } else if (place.location != null) {\n                            fragment.loadWithCoordinates(place.location!!)\n                        } else {\n                             Log.e(\"PlaceDetailsFullView\", \"Place has no ID and no location: $place\")\n                        }\n                    }\n                }\n            )\n\n            // Close Button\n            IconButton(\n                onClick = onDismiss,\n                modifier = Modifier\n                    .align(Alignment.TopEnd)\n                    .padding(16.dp)\n                    .background(MaterialTheme.colorScheme.surface.copy(alpha = 0.7f), shape = CircleShape)\n            ) {\n                Icon(\n                    imageVector = Icons.Default.Close,\n                    contentDescription = stringResource(com.example.placedetailscompose.R.string.close),\n                    tint = MaterialTheme.colorScheme.onSurface\n                )\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/ui/theme/Color.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose.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": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/ui/theme/Theme.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose.ui.theme\n\nimport android.app.Activity\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalView\nimport androidx.core.view.WindowCompat\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\n@Composable\nfun PlaceDetailsComposeTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    content: @Composable () -> Unit\n) {\n    val colorScheme = when {\n        darkTheme -> DarkColorScheme\n        else -> LightColorScheme\n    }\n    val view = LocalView.current\n    if (!view.isInEditMode) {\n        SideEffect {\n            val activity = view.context as Activity\n            activity.window.statusBarColor = android.graphics.Color.TRANSPARENT\n            WindowCompat.getInsetsController(activity.window, view).isAppearanceLightStatusBars = !darkTheme\n        }\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/ui/theme/Typography.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose.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)\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/ui/theme/Utils.kt",
    "content": "/*\n * Copyright 2025 Google LLC\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 */\npackage com.example.placedetailscompose.ui.theme\n\nimport android.app.Activity\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.WindowCompat\n\n/**\n * Sets the status bar color for the given Activity, handling different API levels and modern\n * best practices to avoid the deprecation warning.\n *\n * @param activity The target Activity.\n * @param color The color to set (e.g., Color.Red.toArgb()).\n * @param isLight True if the status bar content (icons/text) should be dark (for light backgrounds).\n */\nfun setStatusBarColor(activity: Activity, color: Int, isLight: Boolean) {\n    // Set the color directly on the Window\n    // This property is used across many API levels, and while the deprecated method\n    // is often the one that causes the linter warning, accessing the property directly\n    // is the way to set it in a modern way for pre-API 35 devices.\n    @Suppress(\"DEPRECATION\")\n    activity.window.statusBarColor = color\n\n    // --- Modern System Insets & Light Status Bar Handling ---\n\n    // 1. Get the WindowInsetsControllerCompat (backward-compatible controller)\n    val controller = WindowCompat.getInsetsController(activity.window, activity.findViewById<View>(android.R.id.content))\n\n    // 2. Set the appearance (dark icons/text for light status bar background)\n    controller.isAppearanceLightStatusBars = isLight\n\n    // Optional: Ensure the window content is drawn *behind* the status bar\n    // This is the core of modern edge-to-edge handling and what the deprecation message suggests.\n    WindowCompat.setDecorFitsSystemWindows(activity.window, false)\n}"
  },
  {
    "path": "PlaceDetailsCompose/src/main/java/com/example/placedetailscompose/viewmodels/MapViewModel.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailscompose.viewmodels\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.placedetailscompose.repository.LocationRepository\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.PointOfInterest\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\n\nprivate const val TAG = \"MapViewModel\"\n\nclass MapViewModel(application: Application) : AndroidViewModel(application) {\n    private val locationRepository = LocationRepository(application)\n\n    val sydney = LatLng(40.01833081193422, -105.27805050328878)\n\n    // **Permission Handling**\n    // We use a StateFlow to track whether location permissions have been granted.\n    // This is crucial because we don't want to start collecting location updates\n    // until we know we have the necessary permissions.\n    private val _permissionGranted = MutableStateFlow(false)\n\n    @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)\n    val deviceLocation: StateFlow<LatLng?> = _permissionGranted\n        .flatMapLatest { hasPermission ->\n            // **Lazy Location Collection**\n            // We use `flatMapLatest` to switch between flows based on the permission state.\n            // If permission is granted, we start collecting from the repository.\n            // If not, we emit `null` (or keep the previous state).\n            // This prevents `SecurityException` crashes and ensures we only ask for location\n            // when it's safe to do so.\n            if (hasPermission) {\n                locationRepository.getDeviceLocation()\n            } else {\n                flowOf(null)\n            }\n        }\n        .map { it?.let { loc -> LatLng(loc.latitude, loc.longitude) } }\n        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)\n\n    /**\n     * Called when the UI has confirmed that location permissions are granted.\n     * This triggers the [deviceLocation] flow to start fetching updates.\n     */\n    fun onPermissionGranted() {\n        _permissionGranted.value = true\n    }\n\n    private val _selectedPlace = MutableStateFlow<com.google.android.libraries.places.api.model.Place?>(null)\n    val selectedPlace = _selectedPlace.asStateFlow()\n\n    // **User Tracking State**\n    // This state determines if the map camera should automatically follow the user's device location.\n    // It starts as `true` (following) but can be disabled by user interaction (dragging).\n    private val _isMapFollowingUser = MutableStateFlow(true)\n    val isMapFollowingUser: StateFlow<Boolean> = _isMapFollowingUser.asStateFlow()\n\n    // **Coordinate Mode State**\n    // This state determines whether clicking the map triggers Place Details for the clicked coordinates.\n    private val _isCoordinateMode = MutableStateFlow(false)\n    val isCoordinateMode: StateFlow<Boolean> = _isCoordinateMode.asStateFlow()\n\n    private val _hasAnimatedToPlace = MutableStateFlow(false)\n    val hasAnimatedToPlace: StateFlow<Boolean> = _hasAnimatedToPlace.asStateFlow()\n\n    fun onAnimateToPlaceFinish() {\n        _hasAnimatedToPlace.value = true\n    }\n\n    /**\n     * Called when the user manually drags the map.\n     * We disable user tracking so the map doesn't jump back to the user's location while they are exploring.\n     */\n    fun onMapDragged() {\n        _isMapFollowingUser.value = false\n    }\n\n    /**\n     * Called when the \"My Location\" button is clicked.\n     * We re-enable user tracking to snap the camera back to the user's location.\n     */\n    fun onMyLocationClicked() {\n        _isMapFollowingUser.value = true\n    }\n\n    fun onPoiClicked(poi: PointOfInterest) {\n        // When a POI is clicked, we create a Place object with the ID and LatLng.\n        // This allows us to load details using the Place ID.\n        val place = com.google.android.libraries.places.api.model.Place.builder()\n            .setId(poi.placeId)\n            .setLocation(poi.latLng)\n            .setDisplayName(poi.name)\n            .build()\n        _selectedPlace.value = place\n    }\n\n    fun onMapClicked(latLng: LatLng) {\n        if (_isCoordinateMode.value) {\n            // In Coordinate Mode, we create a Place object with just the LatLng.\n            // The Place Details UI will load details for this location.\n            val place = com.google.android.libraries.places.api.model.Place.builder()\n                .setLocation(latLng)\n                .build()\n            _selectedPlace.value = place\n        }\n    }\n\n    fun onToggleCoordinateMode(enabled: Boolean) {\n        _isCoordinateMode.value = enabled\n        // Clear selection when switching modes to avoid confusion\n        _selectedPlace.value = null\n        _hasAnimatedToPlace.value = false\n    }\n\n    // **Content Selection State**\n    private val _selectedCompactContent = MutableStateFlow(com.google.android.libraries.places.widget.PlaceDetailsCompactFragment.ALL_CONTENT)\n    val selectedCompactContent: StateFlow<List<com.google.android.libraries.places.widget.PlaceDetailsCompactFragment.Content>> = _selectedCompactContent.asStateFlow()\n\n    private val _selectedFullContent = MutableStateFlow(com.google.android.libraries.places.widget.PlaceDetailsFragment.STANDARD_CONTENT)\n    val selectedFullContent: StateFlow<List<com.google.android.libraries.places.widget.PlaceDetailsFragment.Content>> = _selectedFullContent.asStateFlow()\n\n    fun updateCompactContent(content: List<com.google.android.libraries.places.widget.PlaceDetailsCompactFragment.Content>) {\n        _selectedCompactContent.value = content\n    }\n\n    fun updateFullContent(content: List<com.google.android.libraries.places.widget.PlaceDetailsFragment.Content>) {\n        _selectedFullContent.value = content\n    }\n\n    fun onDismissPlace() {\n        _selectedPlace.value = null\n        _hasAnimatedToPlace.value = false\n    }\n}"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/drawable/close_button_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <solid android:color=\"#80000000\" /> <!-- Semi-transparent black -->\n</shape>\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/drawable/ic_close.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\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</vector>\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlaceDetailsCompose/src/main/res/drawable/ic_launcher_foreground.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<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=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\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=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/drawable/outline_my_location_24.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:viewportHeight=\"960\" android:viewportWidth=\"960\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M440,918L440,838Q315,824 225.5,734.5Q136,645 122,520L42,520L42,440L122,440Q136,315 225.5,225.5Q315,136 440,122L440,42L520,42L520,122Q645,136 734.5,225.5Q824,315 838,440L918,440L918,520L838,520Q824,645 734.5,734.5Q645,824 520,838L520,918L440,918ZM480,760Q596,760 678,678Q760,596 760,480Q760,364 678,282Q596,200 480,200Q364,200 282,282Q200,364 200,480Q200,596 282,678Q364,760 480,760ZM480,640Q414,640 367,593Q320,546 320,480Q320,414 367,367Q414,320 480,320Q546,320 593,367Q640,414 640,480Q640,546 593,593Q546,640 480,640ZM480,560Q513,560 536.5,536.5Q560,513 560,480Q560,447 536.5,423.5Q513,400 480,400Q447,400 423.5,423.5Q400,447 400,480Q400,513 423.5,536.5Q447,560 480,560ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z\"/>\n    \n</vector>\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/drawable/outline_settings_24.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:viewportHeight=\"960\" android:viewportWidth=\"960\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z\"/>\n    \n</vector>\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/layout/place_details_fragment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<androidx.fragment.app.FragmentContainerView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fragment_container_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    />\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/mipmap-anydpi/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlaceDetailsCompose/src/main/res/mipmap-anydpi/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlaceDetailsCompose/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<resources>\n    <item name=\"fragment_container_view\" type=\"id\" />\n</resources>\n"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/values/strings.xml",
    "content": "<!--\n     Copyright 2025 Google LLC\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<resources>\n    <string name=\"app_name\">Place Details Compose</string>\n    <string name=\"map_id\">YOUR_MAP_ID</string>\n    <string name=\"location_permission_denied\">Location permission denied</string>\n    <string name=\"full_view\">Full View</string>\n    <string name=\"compact_view\">Compact View</string>\n    <string name=\"coords_mode\">Coords Mode</string>\n    <string name=\"poi_mode\">POI Mode</string>\n    <string name=\"select_fields\">Select Fields</string>\n    <string name=\"select_full_view_fields\">Select Full View Fields</string>\n    <string name=\"select_compact_view_fields\">Select Compact View Fields</string>\n    <string name=\"collapse_settings\">Collapse Settings</string>\n    <string name=\"expand_settings\">Expand Settings</string>\n    <string name=\"close\">Close</string>\n    <string name=\"done\">Done</string>\n    <string name=\"error_api_key_missing\">API Key was not set in secrets.properties</string>\n    <string name=\"error_map_id_missing\">Map ID is not set. Some features may not work. See README for instructions.</string>\n</resources>"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<resources>\n    <style name=\"Theme.PlaceDetailsCompose\" parent=\"Theme.AppCompat.Light.NoActionBar\" />\n\n    <style name=\"CustomizedPlaceDetailsTheme\" parent=\"PlacesMaterialTheme\">\n        <item name=\"android:windowIsFloating\">false</item>\n        <item name=\"android:background\">@android:color/holo_blue_light</item>\n    </style>\n</resources>"
  },
  {
    "path": "PlaceDetailsCompose/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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   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": "PlaceDetailsCompose/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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   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": "PlaceDetailsUIKit/.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": "PlaceDetailsUIKit/README.md",
    "content": "# **Place Details UI Kit Samples for Android**\n\nThis Android application provides two distinct demonstrations of the **Places** UI Kit for **Android\n**, showcasing how to integrate and customize the PlaceDetailsCompactFragment.\n\n1. **Simple Integration (MainActivity)**: A straightforward example of how to add the Place Details\n   widget to an app. It focuses on handling map interactions, displaying the widget with a default\n   set of content, and persisting its state across screen rotations using a ViewModel.\n2. **Configurable Integration (ConfigurablePlaceDetailsActivity)**: A more advanced example that\n   demonstrates how to dynamically configure the content sections displayed within the widget. It\n   features a settings dialog, built with Jetpack Compose, that allows the user to select which\n   place data fields (e.g., Photos, Rating, Website) they want to see.\n\nBoth samples demonstrate best practices for handling runtime permissions, the Android Activity\nlifecycle (including configuration changes), and lifecycle-aware data loading to prevent common\ncrashes.\n\n## **Features**\n\n* **Google Map Integration**: Displays an interactive Google Map centered on the user's location.\n* **POI Click Handling**: Detects clicks on POIs and retrieves their unique Place ID.\n* **Place Details UI Kit**: Uses the modern PlaceDetailsCompactFragment to display rich details\n  about a selected place.\n* **Dynamic Orientation**: The PlaceDetailsCompactFragment automatically adjusts its layout between\n  VERTICAL and HORIZONTAL based on the device's orientation.\n* **Robust State Management**: Uses a ViewModel to retain the selected place and/or configuration\n  across configuration changes (e.g., screen rotation), ensuring a seamless user experience.\n* **Advanced Customization**: Features a custom \"Synthwave\" theme for the\n  PlaceDetailsCompactFragment to demonstrate how easily the widget's appearance can be modified.\n* **Dynamic Content Configuration**: The ConfigurablePlaceDetailsActivity shows how to let users\n  choose which Place.Field sections are displayed in the widget at runtime.\n* **Jetpack Compose Integration**: The content selection dialog is built using Jetpack\n  Compose, showcasing its use within a View-based project.\n* **Lifecycle-Aware Implementation**: Includes a robust solution to prevent common lifecycle-related\n  crashes when loading the fragment.\n\n## **Getting Started**\n\nTo build and run this sample application, you will need an API key from the Google Cloud Console.\n\n### **Set Up Your API Key**\n\n1. Go to the [Google Cloud Console](https://console.cloud.google.com/).\n2. Create a new project or select an existing one.\n3. Enable the **Maps SDK for Android** and the **Places API**.\n4. Create an API key. For security, it's highly recommended to restrict your API key to your Android\n   app's package name and SHA-1 certificate fingerprint.\n5. In the root directory of this project, create a file named secrets.properties. This file is\n   already listed in .gitignore to prevent it from being checked into version control.\n6. Add your API key to the secrets.properties file. The key should be assigned to both\n   MAPS_API_KEY and PLACES_API_KEY:\n\n```properties\n   MAPS_API_KEY=\"YOUR_API_KEY_HERE\"\n   PLACES_API_KEY=\"YOUR_API_KEY_HERE\"\n```\n\n### **Build and Run**\n\n1. Open the project in Android Studio.\n2. Let Gradle sync the project dependencies.\n3. Run the app on an Android emulator or a physical device.\n\nThe app has two launcher activities. You can choose which one to run using the \"Run/Debug\nConfigurations\" dropdown in Android Studio.\n\n* **MainActivity**: Launches the simple, non-configurable demo.\n* **ConfigurablePlaceDetailsActivity**: Launches the advanced demo with content selection.\n\nThe app will request location permissions. Once granted, it will zoom to your current location.\nTapping on any POI on the map (e.g., a restaurant, park, or shop) will display the\nPlaceDetailsCompactFragment at the bottom of the screen. In the configurable demo, a settings icon\nallows you to customize the widget's content.\n\n## **Code Highlights**\n\n### **MainActivity.kt**\n\n* **MainViewModel**: A simple ViewModel class defined at the top of the file. Its sole purpose is to\n  store the selectedPlaceId so that it survives configuration changes.\n* **onCreate()**:\n    * Initializes the ActivityResultLauncher for handling location permission requests.\n    * Initializes the Places SDK and the FusedLocationProviderClient.\n    * Crucially, it checks if viewModel.selectedPlaceId is not null. If it has a value (meaning the\n      app was rotated while a place was selected), it calls showPlaceDetailsFragment() to restore\n      the view.\n* **onPoiClick(poi: PointOfInterest)**:\n    * This is the callback for when a user taps a POI on the map.\n    * It saves the poi.placeId to the viewModel.\n    * It then calls showPlaceDetailsFragment() to display the widget.\n* **showPlaceDetailsFragment(placeId: String)**:\n    * This is the core function for displaying the widget.\n    * It dynamically determines the orientation (HORIZONTAL or VERTICAL) based on the device's\n      current configuration.\n    * It creates a new instance of PlaceDetailsCompactFragment, passing it the content to display,\n      the orientation, and a custom theme (R.style.CustomizedPlaceDetailsTheme).\n    * It sets a PlaceLoadListener to handle onSuccess and onFailure events. The UI (loading\n      indicator and fragment visibility) is updated in these callbacks.\n    * It adds the fragment to the FragmentContainerView using the FragmentManager.\n    * **Important**: The call to fragment.loadWithPlaceId(placeId) is wrapped in\n      binding.root.post { ... }. This is a key fix that prevents a\n      kotlin.UninitializedPropertyAccessException crash by ensuring the fragment's view is fully\n      created and attached before its data is loaded.\n\n### **ConfigurablePlaceDetailsActivity.kt**\n\nThis activity demonstrates a more advanced use case where the content of the widget is\nuser-configurable.\n\n* **ContentSelectionViewModel.kt**: This ViewModel is more complex. It holds both the\n  selectedPlaceId and the state of the content configuration. It uses StateFlow to expose lists of\n  selected and unselected content items, which the UI observes.\n* **Content Configuration Dialog**:\n    * The configure\\_button FAB opens an AlertDialog.\n    * The dialog's view (content\\_selector\\_dialog.xml) contains a ComposeView.\n    * The UI of the dialog is built declaratively with Jetpack Compose in the DialogContent\n      composable function. It displays two lists with sticky headers for \"Selected\" and \"Unselected\"\n      content.\n    * Clicking an item calls viewModel.toggleSelection(), which atomically updates the state flows,\n      causing the Compose UI to automatically re-render.\n* **showPlaceDetailsFragment(placeId: String)**:\n    * This function is similar to the one in MainActivity, but with one key difference.\n    * When creating the PlaceDetailsCompactFragment, it gets the list of content directly from the\n      ViewModel: PlaceDetailsCompactFragment.newInstance(viewModel.selectedContent.value.map {\n      it.content }, ...)\n    * This ensures that whatever content the user has selected in the dialog is what the fragment\n      will request and display.\n\n### **Customization**\n\nThe custom \"Synthwave\" theme is defined in [`themes.xml`](app/src/main/res/values/themes.xml) and\n[`colors.xml`](app/src/main/res/values/colors.xml). By overriding attributes like placesColorSurface,\nplacesColorPrimary, and placesTextAppearanceBodyMedium, you can completely change the look and feel\nof the widget to match your app's branding.\n\n```xml\n<!-- In themes.xml -->  \n<style name=\"CustomizedPlaceDetailsTheme\" parent=\"PlacesMaterialTheme\">  \n<!-- Core Colors -->  \n<item name=\"placesColorSurface\">@color/synthwave_surface</item>  \n<item name=\"placesColorPrimary\">@color/synthwave_primary</item>  \n...  \n<!-- Typography -->  \n<item name=\"placesTextAppearanceBodyMedium\">@style/app_text_appearence_mono</item>  \n</style>\n```\n\nThis sample provides a complete and robust foundation for integrating the Places UI Kit into your\nown applications."
  },
  {
    "path": "PlaceDetailsUIKit/build.gradle.kts",
    "content": "/*\n * Copyright 2025 Google LLC\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// The `plugins` block is where we apply Gradle plugins to this module.\n// Plugins add new tasks and configurations to our build process.\nplugins {\n    id(\"places-demo.android.application\")\n    // This plugin enables Kotlin support in the Android project, allowing us to write code in Kotlin.\n    alias(libs.plugins.kotlin.android)\n    // This plugin from Google helps manage API keys and other secrets by reading them from a `secrets.properties`\n    // file (which should be in .gitignore) and exposing them in the `BuildConfig` file at compile time.\n    // This is crucial for keeping sensitive data out of version control.\n    id(\"places-demo.secrets\")\n    // This plugin provides the necessary integration for using Jetpack Compose with the Kotlin compiler.\n    alias(libs.plugins.kotlin.compose)\n}\n\n// The `android` block is where we configure all the Android-specific build options.\nandroid {\n    // The `namespace` is a unique identifier for the app's generated R class. It's also used\n    // as the default `applicationId` if not specified in `defaultConfig`.\n    namespace = \"com.example.placedetailsuikit\"\n\n    defaultConfig {\n        // `applicationId` is the unique identifier for the app on the Google Play Store and on the device.\n        applicationId = \"com.example.placedetailsuikit\"\n        // `minSdk` is the minimum API level required to run the app. Devices below this level cannot install it.\n        minSdk = 27\n        versionCode = 1\n        versionName = \"1.0\"\n\n        // Specifies the instrumentation runner for running Android tests.\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        // The `release` block configures settings for the release build of the app.\n        release {\n            // `isMinifyEnabled` enables code shrinking with R8 to reduce the app's size.\n            // It's disabled here for simplicity in a sample app, but highly recommended for production.\n            isMinifyEnabled = false\n            // `proguardFiles` specifies the files that define the R8 shrinking and obfuscation rules.\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    kotlin {\n        // Configures Kotlin-specific compiler options.\n        compilerOptions {\n            freeCompilerArgs.addAll(\n                \"-opt-in=kotlin.RequiresOptIn\",\n                \"-Xannotation-default-target=param-property\"\n            )\n        }\n    }\n    buildFeatures {\n        // `viewBinding` generates a binding class for each XML layout file, providing a type-safe\n        // way to access views without `findViewById`. This is used in the XML-based activities.\n        viewBinding = true\n        // `compose` enables Jetpack Compose for the project.\n        compose = true\n        // `buildConfig` generates a `BuildConfig` class that contains constants from the build configuration,\n        // such as the API key from the secrets plugin.\n        buildConfig = true\n    }\n    composeOptions {\n        // Sets the version of the Kotlin compiler extension for Compose. This version must be\n        // compatible with the Kotlin version used in the project.\n        kotlinCompilerExtensionVersion = \"1.5.1\"\n    }\n}\n\n// The `dependencies` block is where we declare all the external libraries the app needs.\n// These are fetched from repositories like Maven Central and Google's Maven repository.\ndependencies {\n    // --- Core AndroidX & UI Libraries ---\n    // These are foundational libraries for building modern Android apps.\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.appcompat)\n    implementation(libs.material) // For Material Design components (used in XML layouts).\n    implementation(libs.androidx.activity)\n    implementation(libs.androidx.constraintlayout)\n    implementation(libs.androidx.fragment.ktx)\n    implementation(libs.androidx.lifecycle.viewmodel.ktx) // For the ViewModel architecture component.\n\n    // --- Google Play Services ---\n    // These are the essential libraries for this sample, providing Maps and Places functionality.\n    implementation(libs.google.maps.services) // The core SDK for embedding Google Maps.\n    implementation(libs.places) // The SDK for the Places UI Kit (PlaceDetails fragments).\n    implementation(libs.play.services.location) // Needed for the FusedLocationProviderClient to get the device's location.\n\n    // --- Jetpack Compose ---\n    // These libraries are for building UIs with Jetpack Compose.\n    implementation(libs.androidx.material3) // The latest Material Design components for Compose.\n    implementation(libs.androidx.activity.compose) // Integration between Activity and Compose.\n    implementation(platform(libs.androidx.compose.bom)) // The Compose Bill of Materials (BOM) ensures all Compose libraries use compatible versions.\n    implementation(libs.androidx.ui.tooling.preview) // For displaying @Preview composables in Android Studio.\n    debugImplementation(libs.androidx.ui.tooling) // Provides tools for inspecting Compose UIs.\n\n    // --- Testing Libraries ---\n    // These libraries are for writing and running tests.\n    // `testImplementation` is for local unit tests (running on the JVM).\n    testImplementation(libs.junit)\n    // `androidTestImplementation` is for instrumented tests (running on an Android device or emulator).\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core) // For UI testing with the View system.\n    // AndroidX libraries for creating test rules and running tests.\n    androidTestImplementation(libs.androidx.test.rules)\n    androidTestImplementation(libs.androidx.test.runner)\n\n    // --- Compose Testing ---\n    // These are specific to testing Jetpack Compose UIs.\n    androidTestImplementation(platform(libs.androidx.compose.bom)) // BOM for testing libraries.\n    androidTestImplementation(libs.androidx.ui.test.junit4) // The main library for Compose UI tests.\n    debugImplementation(libs.androidx.ui.test.manifest) // Provides a manifest for UI tests.\n}\n\n\ndemoApp {\n    mainActivity.set(\".LauncherActivity\")\n}\n"
  },
  {
    "path": "PlaceDetailsUIKit/lint.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<lint>\n    <issue id=\"NotificationPermission\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "PlaceDetailsUIKit/local.defaults.properties",
    "content": "PLACES_API_KEY=\"YOUR_API_KEY\"\nMAPS_API_KEY=\"YOUR_API_KEY\""
  },
  {
    "path": "PlaceDetailsUIKit/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": "PlaceDetailsUIKit/src/androidTest/java/com/example/placedetailsuikit/MainActivityInstrumentedTest.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placedetailsuikit\n\nimport android.Manifest\nimport androidx.test.espresso.Espresso.onView\nimport androidx.test.espresso.action.ViewActions.click\nimport androidx.test.espresso.assertion.ViewAssertions.matches\nimport androidx.test.espresso.matcher.ViewMatchers.isDisplayed\nimport androidx.test.espresso.matcher.ViewMatchers.withId\nimport androidx.test.espresso.matcher.ViewMatchers.withParent\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.rule.GrantPermissionRule\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.PointOfInterest\nimport org.hamcrest.CoreMatchers.allOf\nimport org.hamcrest.CoreMatchers.not\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented tests for [MainActivity] to verify UI behavior and state management.\n * These tests run on an Android device or emulator.\n */\n@RunWith(AndroidJUnit4::class)\nclass MainActivityInstrumentedTest {\n\n    // A Rule to launch MainActivity before each test and clean it up afterward.\n    @get:Rule\n    val activityRule = ActivityScenarioRule(MainActivity::class.java)\n\n    // A Rule to grant location permissions before each test. This prevents the permission dialog\n    // from interrupting the test flow.\n    @get:Rule\n    val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(\n        Manifest.permission.ACCESS_FINE_LOCATION,\n        Manifest.permission.ACCESS_COARSE_LOCATION\n    )\n\n    /**\n     * Test to verify that the map container is displayed when the activity starts.\n     */\n    @Test\n    fun test_mapIsDisplayedOnLaunch() {\n        onView(withId(R.id.map_fragment)).check(matches(isDisplayed()))\n    }\n\n    /**\n     * Test the full user flow:\n     * 1. Simulate a POI click on the map.\n     * 2. Verify the Place Details UI appears (with a loader first, then the content).\n     * 3. Click the dismiss button.\n     * 4. Verify the Place Details UI is hidden.\n     */\n    @Test\n    fun test_poiClickAndDismissFlow() {\n        // --- 1. Simulate a POI Click ---\n        // We get the activity scenario and trigger onPoiClick directly on the UI thread.\n        // This is a reliable way to test the UI logic without actually tapping the map.\n        activityRule.scenario.onActivity { activity ->\n            // A mock POI for \"Google Sydney\"\n            val poi = PointOfInterest(\n                LatLng(-33.865072, 151.1961474),\n                \"ChIJP3Sa8ziYEmsRUKgyFmh9AQM\",\n                \"Google Sydney\"\n            )\n            activity.onPoiClick(poi)\n        }\n\n        // --- 2. Verify UI After Click ---\n        // The wrapper view containing the fragment should now be visible.\n        onView(withId(R.id.place_details_wrapper)).check(matches(isDisplayed()))\n\n        // Check for the loading indicator that is a child of our wrapper.\n        // This avoids ambiguity with the loader inside the PlaceDetailsCompactFragment.\n        onView(\n            allOf(\n                withId(R.id.loading_indicator_main),\n                withParent(withId(R.id.place_details_wrapper))\n            )\n        ).check(matches(isDisplayed()))\n\n\n        // The Place Details fragment loads data asynchronously. For a simple sample,\n        // a short sleep is a straightforward way to wait for the UI to update.\n        // For a production app, using Espresso Idling Resources is the recommended approach.\n        Thread.sleep(3000) // Wait for 3 seconds for the network call to complete.\n\n        // Check that our specific loader is now gone.\n        onView(\n            allOf(\n                withId(R.id.loading_indicator_main),\n                withParent(withId(R.id.place_details_wrapper))\n            )\n        ).check(matches(not(isDisplayed())))\n\n        onView(withId(R.id.place_details_container)).check(matches(isDisplayed()))\n        onView(withId(R.id.dismiss_button)).check(matches(isDisplayed()))\n\n        // --- 3. Click the Dismiss Button ---\n        onView(withId(R.id.dismiss_button)).perform(click())\n\n        // --- 4. Verify UI After Dismiss ---\n        // The wrapper view should now be hidden.\n        onView(withId(R.id.place_details_wrapper)).check(matches(not(isDisplayed())))\n    }\n\n    /**\n     * Test that the Place Details view's state is correctly restored after a configuration change\n     * (e.g., screen rotation), thanks to the ViewModel.\n     */\n    @Test\n    fun test_stateRestoresOnConfigurationChange() {\n        // --- 1. Show the Place Details Fragment ---\n        activityRule.scenario.onActivity { activity ->\n            val poi = PointOfInterest(\n                LatLng(-33.865072, 151.1961474),\n                \"ChIJP3Sa8ziYEmsRUKgyFmh9AQM\",\n                \"Google Sydney\"\n            )\n            activity.onPoiClick(poi)\n        }\n\n        // Wait for it to load.\n        Thread.sleep(3000)\n        onView(withId(R.id.place_details_wrapper)).check(matches(isDisplayed()))\n\n        // --- 2. Recreate the Activity (Simulates Rotation) ---\n        activityRule.scenario.recreate()\n\n        // --- 3. Verify the UI is still visible ---\n        // Add another wait after recreation for the fragment to reload and become visible.\n        Thread.sleep(3000)\n\n        // The wrapper should still be visible without needing another click because the\n        // selected place ID was restored from the ViewModel.\n        onView(withId(R.id.place_details_wrapper)).check(matches(isDisplayed()))\n        onView(withId(R.id.place_details_container)).check(matches(isDisplayed()))\n        onView(withId(R.id.dismiss_button)).check(matches(isDisplayed()))\n    }\n}\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/androidTest/java/com/example/placedetailsuikit/compact/ConfigurablePlaceDetailsActivityInstrumentedTest.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placedetailsuikit.compact\n\nimport android.Manifest\nimport androidx.compose.ui.test.assertIsDisplayed\nimport androidx.compose.ui.test.junit4.createAndroidComposeRule\nimport androidx.compose.ui.test.onNodeWithText\nimport androidx.compose.ui.test.performClick\nimport androidx.test.espresso.Espresso\nimport androidx.test.espresso.action.ViewActions\nimport androidx.test.espresso.assertion.ViewAssertions\nimport androidx.test.espresso.matcher.ViewMatchers\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.rule.GrantPermissionRule\nimport com.example.placedetailsuikit.R\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.PointOfInterest\nimport org.hamcrest.CoreMatchers\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented tests for [ConfigurablePlaceDetailsActivity].\n * These tests verify the UI behavior related to the configuration dialog and state restoration.\n */\n@RunWith(AndroidJUnit4::class)\nclass ConfigurablePlaceDetailsActivityInstrumentedTest {\n\n    /**\n     * A rule to launch [ConfigurablePlaceDetailsActivity] and interact with its Compose content.\n     */\n    @get:Rule\n    val composeTestRule = createAndroidComposeRule<ConfigurablePlaceDetailsActivity>()\n\n    /**\n     * A rule to grant location permissions before each test, preventing system dialogs\n     * from interfering with the tests.\n     */\n    @get:Rule\n    val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(\n        Manifest.permission.ACCESS_FINE_LOCATION,\n        Manifest.permission.ACCESS_COARSE_LOCATION\n    )\n\n    /**\n     * Tests that the configuration dialog opens, displays content correctly,\n     * and can be dismissed.\n     */\n    @Test\n    fun test_configureDialogOpensAndDismisses() {\n        // 1. Click the \"Configure\" FAB to open the dialog\n        Espresso.onView(ViewMatchers.withId(R.id.configure_button)).perform(ViewActions.click())\n\n        // 2. Verify that the Compose-based dialog content is displayed\n        // We check for the sticky headers to confirm the LazyColumn is there.\n        composeTestRule.onNodeWithText(\"Selected Content\").assertIsDisplayed()\n        composeTestRule.onNodeWithText(\"Unselected Content\").assertIsDisplayed()\n\n        // Check for a specific item in the list\n        composeTestRule.onNodeWithText(\"Rating\").assertIsDisplayed()\n\n        // 3. Click the \"Close\" button on the AlertDialog\n        Espresso.onView(ViewMatchers.withText(\"Close\")).perform(ViewActions.click())\n\n        // 4. Verify the dialog is gone by checking that its content is no longer visible\n        composeTestRule.onNodeWithText(\"Selected Content\").assertDoesNotExist()\n    }\n\n    /**\n     * Tests that toggling an item in the dialog and then rotating the screen\n     * preserves the selection state.\n     */\n    @Test\n    fun test_selectionStatePersistsOnConfigurationChange() {\n        // 1. Open the configuration dialog\n        Espresso.onView(ViewMatchers.withId(R.id.configure_button)).perform(ViewActions.click())\n\n        // 2. Toggle an item (e.g., move \"Rating\" from selected to unselected)\n        composeTestRule.onNodeWithText(\"Rating\").performClick()\n\n        // After the click, \"Rating\" should now be under the \"Unselected Content\" header.\n        // We can verify this by checking its new position relative to the headers.\n        composeTestRule.onNodeWithText(\"Rating\").assertIsDisplayed()\n\n\n        // 3. Close the dialog\n        Espresso.onView(ViewMatchers.withText(\"Close\")).perform(ViewActions.click())\n\n        // 4. Recreate the activity to simulate a screen rotation\n        composeTestRule.activityRule.scenario.recreate()\n\n        // 5. Re-open the dialog and verify the state was restored\n        Espresso.onView(ViewMatchers.withId(R.id.configure_button)).perform(ViewActions.click())\n\n        // Check that \"Rating\" is still in the unselected list.\n        composeTestRule.onNodeWithText(\"Unselected Content\").assertIsDisplayed()\n        composeTestRule.onNodeWithText(\"Rating\").assertIsDisplayed()\n    }\n\n    /**\n     * Test the full user flow: click POI, check that the Place Details card is displayed,\n     * and dismiss it. This confirms the activity's core functionality.\n     */\n    @Test\n    fun test_poiClickAndDismissFlow() {\n        // 1. Simulate a POI Click\n        composeTestRule.activityRule.scenario.onActivity { activity ->\n            val poi = PointOfInterest(\n                LatLng(-33.865072, 151.1961474),\n                \"ChIJP3Sa8ziYEmsRUKgyFmh9AQM\",\n                \"Google Sydney\"\n            )\n            activity.onPoiClick(poi)\n        }\n\n        // 2. Verify UI After Click\n        // The wrapper view should be visible, and the loader should be showing initially.\n        Espresso.onView(ViewMatchers.withId(R.id.place_details_wrapper))\n            .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))\n        Espresso.onView(ViewMatchers.withId(R.id.loading_indicator_configurable))\n            .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))\n\n        // Wait for the place to load. For a real app, use Espresso Idling Resources.\n        Thread.sleep(3000)\n\n        // The loader should be gone, and the fragment container should be visible.\n        Espresso.onView(ViewMatchers.withId(R.id.loading_indicator_configurable))\n            .check(ViewAssertions.matches(CoreMatchers.not(ViewMatchers.isDisplayed())))\n        Espresso.onView(ViewMatchers.withId(R.id.place_details_container))\n            .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))\n        Espresso.onView(ViewMatchers.withId(R.id.dismiss_button))\n            .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))\n\n        // 3. Click the Dismiss Button\n        Espresso.onView(ViewMatchers.withId(R.id.dismiss_button)).perform(ViewActions.click())\n\n        // 4. Verify UI After Dismiss\n        Espresso.onView(ViewMatchers.withId(R.id.place_details_wrapper))\n            .check(ViewAssertions.matches(CoreMatchers.not(ViewMatchers.isDisplayed())))\n    }\n}"
  },
  {
    "path": "PlaceDetailsUIKit/src/androidTest/java/com/example/placedetailsuikit/full/FullConfigurablePlaceDetailsActivityInstrumentedTest.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit.full\n\nimport android.Manifest\nimport androidx.compose.ui.test.assertIsDisplayed\nimport androidx.compose.ui.test.junit4.createAndroidComposeRule\nimport androidx.compose.ui.test.onNodeWithText\nimport androidx.compose.ui.test.performClick\nimport androidx.test.espresso.Espresso.onView\nimport androidx.test.espresso.action.ViewActions.click\nimport androidx.test.espresso.assertion.ViewAssertions.matches\nimport androidx.test.espresso.matcher.ViewMatchers.isDisplayed\nimport androidx.test.espresso.matcher.ViewMatchers.withId\nimport androidx.test.espresso.matcher.ViewMatchers.withText\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.rule.GrantPermissionRule\nimport com.example.placedetailsuikit.R\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.PointOfInterest\nimport org.hamcrest.CoreMatchers.not\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented tests for [FullConfigurablePlaceDetailsActivity].\n * These tests verify the UI behavior related to the configuration dialog and state restoration\n * for the full Place Details Fragment.\n */\n@RunWith(AndroidJUnit4::class)\nclass FullConfigurablePlaceDetailsActivityInstrumentedTest {\n\n    /**\n     * A rule to launch [FullConfigurablePlaceDetailsActivity] and interact with its Compose content.\n     */\n    @get:Rule\n    val composeTestRule = createAndroidComposeRule<FullConfigurablePlaceDetailsActivity>()\n\n    /**\n     * A rule to grant location permissions before each test, preventing system dialogs\n     * from interfering with the tests.\n     */\n    @get:Rule\n    val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(\n        Manifest.permission.ACCESS_FINE_LOCATION,\n        Manifest.permission.ACCESS_COARSE_LOCATION\n    )\n\n    /**\n     * Tests that the configuration dialog opens, displays content correctly,\n     * and can be dismissed.\n     */\n    @Test\n    fun test_configureDialogOpensAndDismisses() {\n        // 1. Click the \"Configure\" FAB to open the dialog\n        onView(withId(R.id.configure_button)).perform(click())\n\n        // 2. Verify that the Compose-based dialog content is displayed\n        // We check for the sticky headers to confirm the LazyColumn is there.\n        composeTestRule.onNodeWithText(\"Selected Content\").assertIsDisplayed()\n        composeTestRule.onNodeWithText(\"Unselected Content\").assertIsDisplayed()\n\n        // Check for a specific item in the list\n        composeTestRule.onNodeWithText(\"Rating\").assertIsDisplayed()\n\n        // 3. Click the \"Close\" button on the AlertDialog\n        onView(withText(\"Close\")).perform(click())\n\n        // 4. Verify the dialog is gone by checking that its content is no longer visible\n        composeTestRule.onNodeWithText(\"Selected Content\").assertDoesNotExist()\n    }\n\n    /**\n     * Tests that toggling an item in the dialog and then rotating the screen\n     * preserves the selection state.\n     */\n    @Test\n    fun test_selectionStatePersistsOnConfigurationChange() {\n        // 1. Open the configuration dialog\n        onView(withId(R.id.configure_button)).perform(click())\n\n        // 2. Toggle an item (e.g., move \"Rating\" from selected to unselected)\n        composeTestRule.onNodeWithText(\"Rating\").performClick()\n\n        // After the click, \"Rating\" should now be under the \"Unselected Content\" header.\n        // We can verify this by checking its new position relative to the headers.\n        // The item should still be displayed, but its \"location\" in the list has changed conceptually.\n        // The check that follows is more specific to its new list.\n\n        // 3. Close the dialog\n        onView(withText(\"Close\")).perform(click())\n\n        // 4. Recreate the activity to simulate a screen rotation\n        composeTestRule.activityRule.scenario.recreate()\n\n        // 5. Re-open the dialog and verify the state was restored\n        onView(withId(R.id.configure_button)).perform(click())\n\n        // Check that \"Rating\" is still in the unselected list.\n        composeTestRule.onNodeWithText(\"Unselected Content\").assertIsDisplayed()\n        composeTestRule.onNodeWithText(\"Rating\").assertIsDisplayed()\n    }\n\n    /**\n     * Test the full user flow: click POI, check that the Place Details card is displayed,\n     * and dismiss it. This confirms the activity's core functionality.\n     */\n    @Test\n    fun test_poiClickAndDismissFlow() {\n        // 1. Simulate a POI Click\n        composeTestRule.activityRule.scenario.onActivity { activity ->\n            val poi = PointOfInterest(\n                LatLng(-33.865072, 151.1961474),\n                \"ChIJP3Sa8ziYEmsRUKgyFmh9AQM\",\n                \"Google Sydney\"\n            )\n            activity.onPoiClick(poi)\n        }\n\n        // 2. Verify UI After Click\n        // The wrapper view should be visible, and the loader should be showing initially.\n        onView(withId(R.id.place_details_wrapper)).check(matches(isDisplayed()))\n        onView(withId(R.id.loading_indicator_configurable)).check(matches(isDisplayed()))\n\n        // Wait for the place to load. For a real app, use Espresso Idling Resources.\n        Thread.sleep(3000)\n\n        // The loader should be gone, and the fragment container should be visible.\n        onView(withId(R.id.loading_indicator_configurable)).check(matches(not(isDisplayed())))\n        onView(withId(R.id.place_details_container)).check(matches(isDisplayed()))\n        onView(withId(R.id.dismiss_button)).check(matches(isDisplayed()))\n\n        // 3. Click the Dismiss Button\n        onView(withId(R.id.dismiss_button)).perform(click())\n\n        // 4. Verify UI After Dismiss\n        onView(withId(R.id.place_details_wrapper)).check(matches(not(isDisplayed())))\n    }\n}\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\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.AppCompat.Light.NoActionBar\"\n        tools:targetApi=\"36\">\n\n        <meta-data\n            android:name=\"com.google.android.geo.API_KEY\"\n            android:value=\"${MAPS_API_KEY}\"/>\n\n        <activity\n            android:name=\".LauncherActivity\"\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\n            android:name=\".MainActivity\"\n            android:exported=\"true\"/>\n\n        <activity\n            android:name=\".compact.ConfigurablePlaceDetailsActivity\"\n            android:theme=\"@style/Theme.MaterialComponents.Light.NoActionBar\"\n            android:exported=\"true\"/>\n\n        <activity\n            android:name=\".full.FullConfigurablePlaceDetailsActivity\"\n            android:theme=\"@style/Theme.MaterialComponents.Light.NoActionBar\"\n            android:exported=\"true\"/>\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/LauncherActivity.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.tooling.preview.Preview\nimport com.example.placedetailsuikit.compact.ConfigurablePlaceDetailsActivity\nimport com.example.placedetailsuikit.full.FullConfigurablePlaceDetailsActivity\nimport com.example.placedetailsuikit.ui.theme.PlaceDetailsUIKitTheme\n\nclass LauncherActivity : ComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        enableEdgeToEdge()\n        setContent {\n            PlaceDetailsUIKitTheme {\n                LauncherScreen()\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun LauncherScreen() {\n    val context = LocalContext.current\n    Scaffold(\n        topBar = {\n            TopAppBar(title = { Text(\"Place Details UIKit Demos\") })\n        }\n    ) { innerPadding ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(innerPadding),\n            verticalArrangement = Arrangement.Center,\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Button(onClick = {\n                context.startActivity(Intent(context, MainActivity::class.java))\n            }) {\n                Text(\"Main Activity\")\n            }\n            Button(onClick = {\n                context.startActivity(Intent(context, ConfigurablePlaceDetailsActivity::class.java))\n            }) {\n                Text(\"Compact Place Details\")\n            }\n            Button(onClick = {\n                context.startActivity(Intent(context, FullConfigurablePlaceDetailsActivity::class.java))\n            }) {\n                Text(\"Full Place Details\")\n            }\n        }\n    }\n}\n\n@Preview(showBackground = true)\n@Composable\nfun LauncherScreenPreview() {\n    PlaceDetailsUIKitTheme {\n        LauncherScreen()\n    }\n}"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/MainActivity.kt",
    "content": "// Copyright 2025 Google LLC\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// [START placessdkandroid_place_details_ui_kit_add_place_details_component_full]\n\npackage com.example.placedetailsuikit\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.content.res.Configuration\nimport android.location.Location\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.app.ActivityCompat\nimport androidx.lifecycle.ViewModel\nimport com.example.placedetailsuikit.databinding.ActivityMainBinding\nimport com.google.android.gms.location.FusedLocationProviderClient\nimport com.google.android.gms.location.LocationServices\nimport com.google.android.gms.maps.CameraUpdateFactory\nimport com.google.android.gms.maps.GoogleMap\nimport com.google.android.gms.maps.OnMapReadyCallback\nimport com.google.android.gms.maps.SupportMapFragment\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.PointOfInterest\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.widget.PlaceDetailsCompactFragment\nimport com.google.android.libraries.places.widget.PlaceLoadListener\nimport com.google.android.libraries.places.widget.model.Orientation\n\nprivate const val TAG = \"PlacesUiKit\"\n\n/**\n * A simple ViewModel to store UI state that needs to survive configuration changes.\n * In this case, it holds the ID of the selected place. Using a ViewModel is good practice\n * as it prevents data loss during events like screen rotation, ensuring a\n * seamless user experience.\n */\nclass MainViewModel : ViewModel() {\n    var selectedPlaceId: String? = null\n}\n\n/**\n * This activity serves as a basic example of integrating the Place Details UI Kit.\n * It demonstrates the fundamental steps required:\n * 1. Setting up a Google Map.\n * 2. Requesting location permissions to center the map.\n * 3. Handling clicks on Points of Interest (POIs) to get a Place ID.\n * 4. Using the Place ID to load and display place details in a [PlaceDetailsCompactFragment].\n */\nclass MainActivity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnPoiClickListener {\n    // ViewBinding provides type-safe access to views defined in the XML layout,\n    // eliminating the need for `findViewById` and preventing null pointer exceptions.\n    private lateinit var binding: ActivityMainBinding\n    private var googleMap: GoogleMap? = null\n\n    // The FusedLocationProviderClient is the main entry point for interacting with the\n    // fused location provider, which intelligently manages the underlying location technologies.\n    private lateinit var fusedLocationClient: FusedLocationProviderClient\n\n    // Using registerForActivityResult is the modern, recommended approach for handling\n    // permission requests. It decouples the request from the handling logic, making the\n    // code cleaner and easier to manage compared to the older `onRequestPermissionsResult` callback.\n    private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>\n\n    // The `by viewModels()` delegate provides a lazy-initialized ViewModel scoped to this Activity.\n    // This ensures that we get the same ViewModel instance across configuration changes.\n    private val viewModel: MainViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        // The ActivityResultLauncher is initialized here. The lambda defines the callback\n        // that will be executed once the user responds to the permission dialog.\n        requestPermissionLauncher =\n            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->\n                // We check if either fine or coarse location permission was granted.\n                if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true || permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {\n                    Log.d(TAG, \"Location permission granted by user.\")\n                    fetchLastLocation()\n                } else {\n                    // If permission is denied, we inform the user and default to a known location.\n                    // This ensures the app remains functional even without location access.\n                    Log.d(TAG, \"Location permission denied by user.\")\n                    Toast.makeText(\n                        this,\n                        \"Location permission denied. Showing default location.\",\n                        Toast.LENGTH_LONG\n                    ).show()\n                    moveToSydney()\n                }\n            }\n\n        // enableEdgeToEdge() allows the app to draw behind the system bars for a more immersive experience.\n        enableEdgeToEdge()\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        binding.dismissButton.setOnClickListener {\n            dismissPlaceDetails()\n        }\n\n        // --- Crucial: Initialize Places SDK ---\n        // It's essential to initialize the Places SDK before making any other Places API calls.\n        // This should ideally be done once, for example, in the Application's `onCreate`.\n        val apiKey = BuildConfig.PLACES_API_KEY\n        if (apiKey.isEmpty() || apiKey == \"YOUR_API_KEY\") {\n            // A valid API key is required for the Places SDK to function.\n            Log.e(TAG, \"No api key\")\n            Toast.makeText(\n                this,\n                \"Add your own API_KEY in local.properties\",\n                Toast.LENGTH_LONG\n            ).show()\n            finish()\n            return\n        }\n\n        // `initializeWithNewPlacesApiEnabled` is used to opt-in to the new SDK version.\n        Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)\n\n        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)\n        // ------------------------------------\n\n        // The SupportMapFragment is the container for the map. `getMapAsync` allows us to\n        // work with the GoogleMap object via a callback once it's fully initialized.\n        val mapFragment =\n            supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment?\n        mapFragment?.getMapAsync(this)\n\n        // This block handles restoration after a configuration change (e.g., screen rotation).\n        // If a place was selected before the rotation, its ID is stored in the ViewModel.\n        // We use this ID to immediately show the details fragment again.\n        if (viewModel.selectedPlaceId != null) {\n            viewModel.selectedPlaceId?.let { placeId ->\n                Log.d(TAG, \"Restoring PlaceDetailsFragment for place ID: $placeId\")\n                showPlaceDetailsFragment(placeId)\n            }\n        }\n    }\n\n    /**\n     * This callback is triggered when the GoogleMap object is ready to be used.\n     * All map setup logic should be placed here.\n     */\n    override fun onMapReady(map: GoogleMap) {\n        Log.d(TAG, \"Map is ready\")\n        googleMap = map\n        // Setting the OnPoiClickListener allows us to capture user taps on points of interest.\n        googleMap?.setOnPoiClickListener(this)\n\n        // After the map is ready, we determine the initial camera position based on location permissions.\n        if (isLocationPermissionGranted()) {\n            fetchLastLocation()\n        } else {\n            requestLocationPermissions()\n        }\n    }\n\n    /**\n     * A helper function to centralize the check for location permissions.\n     */\n    private fun isLocationPermissionGranted(): Boolean {\n        return ActivityCompat.checkSelfPermission(\n            this,\n            Manifest.permission.ACCESS_FINE_LOCATION\n        ) == PackageManager.PERMISSION_GRANTED ||\n                ActivityCompat.checkSelfPermission(\n                    this,\n                    Manifest.permission.ACCESS_COARSE_LOCATION\n                ) == PackageManager.PERMISSION_GRANTED\n    }\n\n    /**\n     * This function triggers the permission request flow. The result is handled by the\n     * ActivityResultLauncher defined in `onCreate`.\n     */\n    private fun requestLocationPermissions() {\n        Log.d(TAG, \"Requesting location permissions.\")\n        requestPermissionLauncher.launch(\n            arrayOf(\n                Manifest.permission.ACCESS_FINE_LOCATION,\n                Manifest.permission.ACCESS_COARSE_LOCATION\n            )\n        )\n    }\n\n    /**\n     * Fetches the device's last known location. This is a fast and battery-efficient way\n     * to get a location fix. It should only be called after verifying permissions.\n     */\n    @SuppressLint(\"MissingPermission\")\n    private fun fetchLastLocation() {\n        // Double-checking permissions here is a good practice, although the call sites are already guarded.\n        if (isLocationPermissionGranted()) {\n            fusedLocationClient.lastLocation\n                .addOnSuccessListener { location: Location? ->\n                    if (location != null) {\n                        val userLocation = LatLng(location.latitude, location.longitude)\n                        googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(userLocation, 13f))\n                        Log.d(TAG, \"Moved to user's last known location.\")\n                    } else {\n                        // `lastLocation` can be null if the location has never been recorded.\n                        // In this case, we fall back to a default location.\n                        Log.d(TAG, \"Last known location is null. Falling back to Sydney.\")\n                        moveToSydney()\n                    }\n                }\n                .addOnFailureListener {\n                    // This listener handles errors in the location fetching process.\n                    Log.e(TAG, \"Failed to get location.\", it)\n                    moveToSydney()\n                }\n        }\n    }\n\n    /**\n     * Moves the map camera to a default, hardcoded location (Sydney).\n     * This serves as a reliable fallback.\n     */\n    private fun moveToSydney() {\n        val sydney = LatLng(-33.8688, 151.2093)\n        googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 13f))\n        Log.d(TAG, \"Moved to Sydney\")\n    }\n\n    /**\n     * This is the callback for the `OnPoiClickListener`. It's triggered when a user\n     * taps a POI on the map.\n     */\n    override fun onPoiClick(poi: PointOfInterest) {\n        val placeId = poi.placeId\n        Log.d(TAG, \"Place ID: $placeId\")\n\n        // We save the selected place ID to the ViewModel. This is critical for surviving\n        // configuration changes. If the user rotates the screen now, the `onCreate`\n        // method will be able to restore the place details view.\n        viewModel.selectedPlaceId = placeId\n        showPlaceDetailsFragment(placeId)\n    }\n\n    /**\n     * This function is the core of the integration. It creates, configures, and displays\n     * the [PlaceDetailsCompactFragment].\n     * @param placeId The unique identifier for the place to be displayed.\n     */\n    private fun showPlaceDetailsFragment(placeId: String) {\n        Log.d(TAG, \"Showing PlaceDetailsFragment for place ID: $placeId\")\n\n        // We manage the visibility of UI elements to provide feedback to the user.\n        // The wrapper is shown, and a loading indicator is displayed while the data is fetched.\n        binding.placeDetailsWrapper.visibility = View.VISIBLE\n        binding.dismissButton.visibility = View.GONE\n        binding.placeDetailsContainer.visibility = View.GONE\n        binding.loadingIndicatorMain.visibility = View.VISIBLE\n\n        // The Place Details widget can be displayed vertically or horizontally.\n        // We dynamically choose the orientation based on the device's current configuration.\n        val orientation =\n            if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {\n                Orientation.HORIZONTAL\n            } else {\n                Orientation.VERTICAL\n            }\n\n        // [START placessdkandroid_place_details_ui_kit_add_place_details_component_snippet]\n        \n        // We create a new instance of the fragment using its factory method.\n        // We can specify which content to show, the orientation, and a custom theme.\n        val fragment = PlaceDetailsCompactFragment.newInstance(\n            PlaceDetailsCompactFragment.ALL_CONTENT, // Show all available content.\n            orientation,\n            R.style.CustomizedPlaceDetailsTheme,\n        ).apply {\n            // The PlaceLoadListener provides callbacks for when the place data is successfully\n            // loaded or when an error occurs. This is where we update our UI state.\n            setPlaceLoadListener(object : PlaceLoadListener {\n                override fun onSuccess(place: Place) {\n                    Log.d(TAG, \"Place loaded: ${place.id}\")\n                    // Once the data is loaded, we hide the loading indicator and show the fragment.\n                    binding.loadingIndicatorMain.visibility = View.GONE\n                    binding.placeDetailsContainer.visibility = View.VISIBLE\n                    binding.dismissButton.visibility = View.VISIBLE\n                }\n\n                override fun onFailure(e: Exception) {\n                    Log.e(TAG, \"Place failed to load\", e)\n                    // On failure, we hide the UI and notify the user.\n                    dismissPlaceDetails()\n                    Toast.makeText(this@MainActivity, \"Failed to load place details.\", Toast.LENGTH_SHORT).show()\n                }\n            })\n        }\n\n        // We add the fragment to our layout's container view.\n        // `commitNow()` is used to ensure the fragment is immediately added and available,\n        // which is important because we need to call a method on it right after.\n        supportFragmentManager\n            .beginTransaction()\n            .replace(binding.placeDetailsContainer.id, fragment)\n            .commitNow()\n\n        // **This is the key step**: After adding the fragment, we call `loadWithPlaceId`\n        // to trigger the data loading process for the selected place.\n        // We use `post` to ensure this runs after the layout has been measured,\n        // which can prevent potential timing issues.\n        binding.root.post {\n            fragment.loadWithPlaceId(placeId)\n        }\n    }\n\n    // [END placessdkandroid_place_details_ui_kit_add_place_details_component_snippet]\n\n    /**\n     * Hides the place details view and clears the selected place ID from the ViewModel.\n     */\n    private fun dismissPlaceDetails() {\n        binding.placeDetailsWrapper.visibility = View.GONE\n        // Clearing the ID in the ViewModel is important so that if the user rotates the\n        // screen after dismissing, the details view doesn't reappear.\n        viewModel.selectedPlaceId = null\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        // It's a good practice to nullify references to objects that have a lifecycle\n        // tied to the activity, like the GoogleMap object, to prevent potential memory leaks.\n        googleMap = null\n    }\n}\n\n// [END placessdkandroid_place_details_ui_kit_add_place_details_component_full]\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/compact/ConfigurablePlaceDetailsActivity.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit.compact\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.content.res.Configuration\nimport android.location.Location\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\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.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.ComposeView\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport androidx.core.app.ActivityCompat\nimport androidx.lifecycle.lifecycleScope\nimport com.example.placedetailsuikit.BuildConfig\nimport com.example.placedetailsuikit.R\nimport com.example.placedetailsuikit.databinding.ActivityConfigurableMapBinding\nimport com.google.android.gms.location.FusedLocationProviderClient\nimport com.google.android.gms.location.LocationServices\nimport com.google.android.gms.maps.CameraUpdateFactory\nimport com.google.android.gms.maps.GoogleMap\nimport com.google.android.gms.maps.OnMapReadyCallback\nimport com.google.android.gms.maps.SupportMapFragment\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.PointOfInterest\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.widget.PlaceDetailsCompactFragment\nimport com.google.android.libraries.places.widget.PlaceDetailsCompactFragment.Content\nimport com.google.android.libraries.places.widget.PlaceLoadListener\nimport com.google.android.libraries.places.widget.model.Orientation\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\n\nprivate const val TAG = \"ConfigurablePlaceDetailsActivity\"\n\n/**\n * This activity demonstrates a more advanced use case of the Place Details UI Kit.\n * It showcases how to dynamically configure the content displayed in the\n * [PlaceDetailsCompactFragment] at runtime.\n *\n * Key features demonstrated:\n * - Dynamic content configuration via a Jetpack Compose-based dialog.\n * - Use of a [ContentSelectionViewModel] to manage UI state (selected place and content).\n * - Reactive UI updates using Kotlin Flows (`StateFlow` and `collectLatest`).\n * - Persistence of both selected place and content configuration across orientation changes.\n */\nclass ConfigurablePlaceDetailsActivity : AppCompatActivity(), OnMapReadyCallback,\n    GoogleMap.OnPoiClickListener {\n\n    private lateinit var binding: ActivityConfigurableMapBinding\n    private var googleMap: GoogleMap? = null\n    private lateinit var fusedLocationClient: FusedLocationProviderClient\n    private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>\n\n    // The ViewModel holds all the state that needs to survive configuration changes.\n    // This includes the ID of the selected place and the user's content preferences.\n    private val viewModel: ContentSelectionViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        requestPermissionLauncher =\n            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->\n                if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true || permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {\n                    Log.d(TAG, \"Location permission granted by user.\")\n                    fetchLastLocation()\n                } else {\n                    Log.d(TAG, \"Location permission denied by user.\")\n                    Toast.makeText(\n                        this,\n                        \"Location permission denied. Showing default location.\",\n                        Toast.LENGTH_LONG\n                    ).show()\n                    moveToSydney()\n                }\n            }\n\n        enableEdgeToEdge()\n        binding = ActivityConfigurableMapBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        binding.dismissButton.setOnClickListener {\n            dismissPlaceDetails()\n        }\n\n        val apiKey = BuildConfig.PLACES_API_KEY\n        if (apiKey.isEmpty() || apiKey == \"YOUR_API_KEY\") {\n            Log.e(TAG, \"No api key\")\n            Toast.makeText(\n                this,\n                \"Add your own API_KEY in local.properties\",\n                Toast.LENGTH_LONG\n            ).show()\n            finish()\n            return\n        }\n\n        Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)\n        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)\n\n        val mapFragment =\n            supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment?\n        mapFragment?.getMapAsync(this)\n\n        // This is the core of the reactive UI. We launch a coroutine that is scoped\n        // to the activity's lifecycle. It collects the latest list of selected content\n        // from the ViewModel's StateFlow.\n        lifecycleScope.launch {\n            // `collectLatest` is used here to ensure that if the content selection changes\n            // rapidly, we only process the most recent selection, canceling any ongoing\n            // work for previous selections. This is efficient and prevents unnecessary UI updates.\n            viewModel.selectedContent.collectLatest {\n                // If a place is already selected, we immediately reload the fragment\n                // to reflect the new content configuration.\n                viewModel.selectedPlaceId?.let { placeId ->\n                    Log.d(TAG, \"Content selection changed. Reloading PlaceDetailsFragment for place ID: $placeId\")\n                    showPlaceDetailsFragment(placeId)\n                }\n            }\n        }\n\n        // Restore the fragment if a place was already selected before a configuration change.\n        if (viewModel.selectedPlaceId != null) {\n            viewModel.selectedPlaceId?.let { placeId ->\n                Log.d(TAG, \"Restoring PlaceDetailsFragment for place ID: $placeId\")\n                showPlaceDetailsFragment(placeId)\n            }\n        }\n\n        binding.configureButton.setOnClickListener {\n            showContentSelectionDialog()\n        }\n\n        binding.myLocationButton.setOnClickListener {\n            fetchLastLocation()\n        }\n    }\n\n    override fun onMapReady(map: GoogleMap) {\n        Log.d(TAG, \"Map is ready\")\n        googleMap = map\n        googleMap?.setOnPoiClickListener(this)\n\n        if (isLocationPermissionGranted()) {\n            fetchLastLocation()\n        } else {\n            requestLocationPermissions()\n        }\n    }\n\n    private fun isLocationPermissionGranted(): Boolean {\n        return ActivityCompat.checkSelfPermission(\n            this,\n            Manifest.permission.ACCESS_FINE_LOCATION\n        ) == PackageManager.PERMISSION_GRANTED ||\n                ActivityCompat.checkSelfPermission(\n                    this,\n                    Manifest.permission.ACCESS_COARSE_LOCATION\n                ) == PackageManager.PERMISSION_GRANTED\n    }\n\n    private fun requestLocationPermissions() {\n        Log.d(TAG, \"Requesting location permissions.\")\n        requestPermissionLauncher.launch(\n            arrayOf(\n                Manifest.permission.ACCESS_FINE_LOCATION,\n                Manifest.permission.ACCESS_COARSE_LOCATION\n            )\n        )\n    }\n\n    private fun handleLocationError() {\n        Log.d(TAG, \"Could not retrieve current location. Falling back to Sydney.\")\n        Toast.makeText(\n            this,\n            \"Could not retrieve current location. Showing default location.\",\n            Toast.LENGTH_LONG\n        ).show()\n        moveToSydney()\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun fetchLastLocation() {\n        if (isLocationPermissionGranted()) {\n            fusedLocationClient.lastLocation\n                .addOnSuccessListener { location: Location? ->\n                    if (location != null) {\n                        val latLng = LatLng(location.latitude, location.longitude)\n                        googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f))\n                        Log.d(TAG, \"Moved to user's last known location.\")\n                    } else {\n                        handleLocationError()\n                    }\n                }\n                .addOnFailureListener {\n                    Log.e(TAG, \"Failed to get location.\", it)\n                    handleLocationError()\n                }\n        } else {\n            requestLocationPermissions()\n        }\n    }\n\n    private fun moveToSydney() {\n        val sydney = LatLng(-33.8688, 151.2093)\n        googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 13f))\n        Log.d(TAG, \"Moved to Sydney\")\n    }\n\n    override fun onPoiClick(poi: PointOfInterest) {\n        val placeId = poi.placeId\n        Log.d(TAG, \"Place ID: $placeId\")\n        viewModel.selectedPlaceId = placeId\n        showPlaceDetailsFragment(placeId)\n    }\n\n    /**\n     * Displays the [PlaceDetailsCompactFragment] for a given place ID.\n     * This version is different from the basic example because it gets the list of\n     * content to display directly from the ViewModel's state.\n     *\n     * @param placeId The ID of the place to display.\n     */\n    private fun showPlaceDetailsFragment(placeId: String) {\n        Log.d(TAG, \"Showing PlaceDetailsFragment for place ID: $placeId\")\n        binding.placeDetailsWrapper.visibility = View.VISIBLE\n        binding.dismissButton.visibility = View.GONE\n        binding.placeDetailsContainer.visibility = View.GONE\n        binding.loadingIndicatorConfigurable.visibility = View.VISIBLE\n\n        val orientation =\n            if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {\n                Orientation.HORIZONTAL\n            } else {\n                Orientation.VERTICAL\n            }\n\n        // Here is the key difference: we get the current value of the selected content\n        // from the ViewModel's StateFlow and pass it to the fragment's factory method.\n        // This ensures the fragment is always created with the user's latest preferences.\n        val fragment = PlaceDetailsCompactFragment.newInstance(\n            viewModel.selectedContent.value.map { it.content },\n            orientation,\n            R.style.CustomizedPlaceDetailsTheme,\n        ).apply {\n            setPlaceLoadListener(object : PlaceLoadListener {\n                override fun onSuccess(place: Place) {\n                    Log.d(TAG, \"Place loaded: ${place.id}\")\n                    binding.loadingIndicatorConfigurable.visibility = View.GONE\n                    binding.placeDetailsContainer.visibility = View.VISIBLE\n                    binding.dismissButton.visibility = View.VISIBLE\n                }\n\n                override fun onFailure(e: Exception) {\n                    Log.e(TAG, \"Place failed to load\", e)\n                    dismissPlaceDetails()\n                    Toast.makeText(\n                        this@ConfigurablePlaceDetailsActivity,\n                        \"Failed to load place details.\",\n                        Toast.LENGTH_SHORT\n                    ).show()\n                }\n            })\n        }\n\n        supportFragmentManager\n            .beginTransaction()\n            .replace(binding.placeDetailsContainer.id, fragment)\n            .commitNow()\n\n        binding.root.post {\n            fragment.loadWithPlaceId(placeId)\n        }\n    }\n\n    private fun dismissPlaceDetails() {\n        binding.placeDetailsWrapper.visibility = View.GONE\n        viewModel.selectedPlaceId = null\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        googleMap = null\n    }\n\n    /**\n     * Displays a dialog that allows the user to select which content types\n     * should be visible in the [PlaceDetailsCompactFragment].\n     *\n     * This method demonstrates how to embed a Jetpack Compose UI inside a\n     * traditional View-based dialog. This is a powerful technique for gradually\n     * adopting Compose in an existing application.\n     */\n    private fun showContentSelectionDialog() {\n        // We inflate a traditional XML layout that contains a ComposeView.\n        val dialogView =\n            LayoutInflater.from(this).inflate(R.layout.content_selector_dialog, null)\n        val composeView = dialogView.findViewById<ComposeView>(R.id.compose_view)\n\n        // We then set the content of the ComposeView programmatically.\n        composeView.setContent {\n            // `collectAsState()` is a key Compose utility that observes a Flow\n            // and recomposes the UI whenever a new value is emitted.\n            val selectedContent by viewModel.selectedContent.collectAsState()\n            val unselectedContent by viewModel.unselectedContent.collectAsState()\n\n            // The dialog's UI is defined in this composable function.\n            // We pass the state from the ViewModel down and hoist the event handling up.\n            PlaceContentSelectionDialogContent(selectedContent, unselectedContent) { item ->\n                // When an item is clicked, we simply call the ViewModel's method to\n                // update the state. The StateFlows will emit new lists, and `collectAsState`\n                // will trigger a recomposition, automatically updating the UI.\n                viewModel.toggleSelection(item)\n            }\n        }\n\n        AlertDialog.Builder(this)\n            .setView(dialogView)\n            .setPositiveButton(\"Close\") { dialog, _ ->\n                dialog.dismiss()\n            }\n            .create()\n            .show()\n    }\n}\n\n/**\n * A Composable that displays two lists of content: selected and unselected.\n * It uses sticky headers to keep the section titles visible during scrolling.\n *\n * @param selectedContent The list of items that are currently selected.\n * @param unselectedContent The list of items that are available but not selected.\n * @param onItemClick A callback function invoked when any item is clicked.\n */\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun PlaceContentSelectionDialogContent(\n    selectedContent: List<PlaceDetailsCompactItem>,\n    unselectedContent: List<PlaceDetailsCompactItem>,\n    onItemClick: (PlaceDetailsCompactItem) -> Unit\n) {\n    LazyColumn {\n        stickyHeader {\n            SectionHeader(\"Selected Content\")\n        }\n\n        items(selectedContent, key = { it.content.name }) { content ->\n            ContentItem(\n                item = content,\n                onItemClick = onItemClick\n            )\n        }\n\n        stickyHeader {\n            SectionHeader(\"Unselected Content\")\n        }\n\n        items(unselectedContent, key = { it.content.name }) { content ->\n            ContentItem(\n                item = content,\n                onItemClick = onItemClick\n            )\n        }\n    }\n}\n\n/**\n * A Composable that renders a styled header for a section in the list.\n *\n * @param title The text to display in the header.\n */\n@Composable\nfun SectionHeader(title: String) {\n    Text(\n        text = title,\n        modifier = Modifier\n            .fillMaxWidth()\n            .background(MaterialTheme.colorScheme.tertiaryContainer)\n            .padding(16.dp),\n        style = MaterialTheme.typography.titleMedium,\n        color = MaterialTheme.colorScheme.onTertiaryContainer,\n        textAlign = TextAlign.Center\n    )\n}\n\n/**\n * A Composable that renders a single clickable item in the content selection list.\n *\n * @param item The content item to display.\n * @param onItemClick A callback function invoked when this item is clicked.\n */\n@Composable\nfun ContentItem(\n    item: PlaceDetailsCompactItem,\n    onItemClick: (PlaceDetailsCompactItem) -> Unit\n) {\n    Text(\n        text = item.displayName,\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable { onItemClick(item) }\n            .padding(16.dp),\n        style = MaterialTheme.typography.bodyLarge\n    )\n}\n\n// --- Previews ---\n// Previews are a powerful feature of Jetpack Compose that allow developers to see\n// their UI components in Android Studio without running the app on a device or emulator.\n// They are essential for rapid UI development and testing different states.\n\n@Preview(name = \"Both Sections\", showBackground = true, backgroundColor = 0xFFFFFFFF)\n@Composable\nfun DialogContentPreview_BothSections() {\n    MaterialTheme {\n        PlaceContentSelectionDialogContent(\n            selectedContent = standardContent.toPlaceDetailsCompactItems(),\n            unselectedContent = standardNonContent.toPlaceDetailsCompactItems(),\n            onItemClick = {}\n        )\n    }\n}\n\n@Preview(name = \"Only Selected\", showBackground = true, backgroundColor = 0xFFFFFFFF)\n@Composable\nfun DialogContentPreview_OnlySelected() {\n    MaterialTheme {\n        PlaceContentSelectionDialogContent(\n            selectedContent = Content.entries.toPlaceDetailsCompactItems(),\n            unselectedContent = emptyList(),\n            onItemClick = {}\n        )\n    }\n}\n\n@Preview(name = \"Only Unselected\", showBackground = true, backgroundColor = 0xFFFFFFFF)\n@Composable\nfun DialogContentPreview_OnlyUnselected() {\n    MaterialTheme {\n        PlaceContentSelectionDialogContent(\n            selectedContent = emptyList(),\n            unselectedContent = Content.entries.toPlaceDetailsCompactItems(),\n            onItemClick = {}\n        )\n    }\n}\n\n@Preview(name = \"Empty State\", showBackground = true, backgroundColor = 0xFFFFFFFF)\n@Composable\nfun DialogContentPreview_Empty() {\n    MaterialTheme {\n        PlaceContentSelectionDialogContent(\n            selectedContent = emptyList(),\n            unselectedContent = emptyList(),\n            onItemClick = {}\n        )\n    }\n}\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/compact/ContentSelectionViewModel.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit.compact\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.android.libraries.places.widget.PlaceDetailsCompactFragment\nimport com.google.android.libraries.places.widget.PlaceDetailsCompactFragment.Content\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\n\n/**\n * A data class that represents a single configurable item for the [PlaceDetailsCompactFragment].\n * It wraps the library's [Content] enum with additional properties needed for the UI,\n * such as a user-friendly display name and its current selection state.\n *\n * @param content The actual [Content] enum value from the Place Details library.\n * @param displayName A formatted, human-readable string for the content type.\n * @param isSelected A boolean indicating whether the user has selected this content to be displayed.\n */\ndata class PlaceDetailsCompactItem(\n    val content: Content,\n    val displayName: String,\n    val isSelected: Boolean = false\n)\n\n/**\n * An extension function to convert a [Content] enum into a [PlaceDetailsCompactItem].\n * This simplifies the creation of UI models from the library's data model.\n */\nprivate fun Content.toPlaceDetailsCompactItem() =\n    PlaceDetailsCompactItem(\n        content = this,\n        displayName = this.getDisplayName(),\n        isSelected = standardContent.contains(this)\n    )\n\n/**\n * An extension function that formats a [Content] enum name into a user-friendly, readable string.\n * For example, `ACCESSIBLE_ENTRANCE_ICON` becomes \"Accessible entrance icon\".\n *\n * @return A capitalized, space-separated string representation of the enum name.\n */\nfun Content.getDisplayName(): String =\n    this.name.replace('_', ' ').lowercase().replaceFirstChar { it.uppercase() }\n\n/**\n * An extension function to convert a collection of [Content] enums into a list of\n * [PlaceDetailsCompactItem]s, ready to be used by the UI.\n */\nfun Iterable<Content>.toPlaceDetailsCompactItems(): List<PlaceDetailsCompactItem> =\n    this.map { it.toPlaceDetailsCompactItem() }\n\n/**\n * A default list of [Content] types that are commonly displayed in the Place Details view.\n * This is defined by the Places SDK.\n */\nval standardContent: List<Content> = PlaceDetailsCompactFragment.STANDARD_CONTENT\n\n/**\n * A list containing all [Content] types that are *not* in the [standardContent] list.\n * This is used to populate the \"Unselected\" section of the configuration dialog initially.\n */\nval standardNonContent: List<Content> = Content.entries.filter { !standardContent.contains(it) }\n\n/**\n * A [ViewModel] responsible for holding and managing the UI state for the\n * Place Details content selection feature. It uses Kotlin Flows to create a reactive\n * data layer that the UI can observe.\n *\n * Key responsibilities:\n * - Storing the `selectedPlaceId` across configuration changes.\n * - Maintaining the single source of truth for all available content items and their selection states.\n * - Exposing `StateFlow`s that the UI can collect to automatically update when the state changes.\n * - Providing a method (`toggleSelection`) to handle user interactions from the UI.\n */\nclass ContentSelectionViewModel : ViewModel() {\n    /**\n     * The ID of the place currently being displayed. This is a simple `var` because its\n     * state is managed directly by the Activity, but it's placed in the ViewModel\n     * to survive configuration changes.\n     */\n    var selectedPlaceId: String? = null\n\n    /**\n     * The private, mutable state holder for the list of all content items.\n     * This is the **single source of truth**. All other flows are derived from this one.\n     * It's initialized with all possible `Content` types, with their `isSelected`\n     * property determined by whether they are in the `standardContent` list.\n     */\n    private val _contentItems = MutableStateFlow(\n        Content.entries.map {\n            PlaceDetailsCompactItem(\n                content = it,\n                displayName = it.getDisplayName(),\n                isSelected = standardContent.contains(it)\n            )\n        }\n    )\n\n    /**\n     * A read-only `StateFlow` that exposes the list of currently **selected** content items.\n     * - It's derived from `_contentItems` using the `map` operator.\n     * - `stateIn` converts this cold Flow into a hot `StateFlow`, meaning it will always have a value.\n     * - `viewModelScope` ensures the flow is active as long as the ViewModel is alive.\n     * - `SharingStarted.Eagerly` means the flow starts immediately and keeps its last value even if there are no collectors.\n     * The UI will collect this flow to display the list of selected items.\n     */\n    val selectedContent: StateFlow<List<PlaceDetailsCompactItem>> =\n        _contentItems.map { items -> items.filter { it.isSelected } }\n            .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())\n\n    /**\n     * A read-only `StateFlow` that exposes the list of currently **unselected** content items.\n     * This is also derived from the single source of truth, `_contentItems`.\n     * The UI will collect this flow to display the list of available items that the user can add.\n     */\n    val unselectedContent: StateFlow<List<PlaceDetailsCompactItem>> =\n        _contentItems.map { items -> items.filter { !it.isSelected } }\n            .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())\n\n    /**\n     * This is the public function that the UI calls to modify the state.\n     * It handles the business logic of toggling an item's selection status.\n     *\n     * @param itemToToggle The [PlaceDetailsCompactItem] that the user clicked.\n     */\n    fun toggleSelection(itemToToggle: PlaceDetailsCompactItem) {\n        // `update` is a thread-safe way to modify the value of a MutableStateFlow.\n        _contentItems.update { currentItems ->\n            // We create a new list by mapping over the old one.\n            // This ensures that we are working with immutable state, which is a core\n            // principle of modern Android development and reactive programming.\n            currentItems.map { item ->\n                if (item.content == itemToToggle.content) {\n                    // If we find the item that was clicked, we create a new `copy` of it\n                    // with the `isSelected` property flipped.\n                    item.copy(isSelected = !item.isSelected)\n                } else {\n                    // Otherwise, we return the item unmodified.\n                    item\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/full/FullConfigurablePlaceDetailsActivity.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit.full\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.content.res.Configuration\nimport android.location.Location\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\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.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.ComposeView\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport androidx.core.app.ActivityCompat\nimport androidx.lifecycle.lifecycleScope\nimport com.example.placedetailsuikit.BuildConfig\nimport com.example.placedetailsuikit.R\nimport com.example.placedetailsuikit.databinding.ActivityFullConfigurableMapBinding\nimport com.google.android.gms.location.FusedLocationProviderClient\nimport com.google.android.gms.location.LocationServices\nimport com.google.android.gms.maps.CameraUpdateFactory\nimport com.google.android.gms.maps.GoogleMap\nimport com.google.android.gms.maps.OnMapReadyCallback\nimport com.google.android.gms.maps.SupportMapFragment\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.PointOfInterest\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.widget.PlaceDetailsFragment\nimport com.google.android.libraries.places.widget.PlaceLoadListener\nimport com.google.android.libraries.places.widget.model.Orientation\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\n\nprivate const val TAG = \"FullConfigurablePlaceDetailsActivity\"\n\n/**\n * This activity demonstrates an advanced use case of the **full-screen** Place Details UI Kit.\n * It is structurally similar to the \"compact\" example but uses the [PlaceDetailsFragment]\n * instead of the [com.google.android.libraries.places.widget.PlaceDetailsCompactFragment].\n *\n * Key features demonstrated:\n * - Dynamic content configuration for the full-screen widget.\n * - Use of a [FullContentSelectionViewModel] to manage UI state.\n * - Reactive UI updates using Kotlin Flows.\n */\nclass FullConfigurablePlaceDetailsActivity : AppCompatActivity(), OnMapReadyCallback,\n    GoogleMap.OnPoiClickListener {\n\n    private lateinit var binding: ActivityFullConfigurableMapBinding\n    private var googleMap: GoogleMap? = null\n    private lateinit var fusedLocationClient: FusedLocationProviderClient\n    private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>\n\n    // The ViewModel holds all the state that needs to survive configuration changes.\n    private val viewModel: FullContentSelectionViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        requestPermissionLauncher =\n            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->\n                if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true || permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {\n                    Log.d(TAG, \"Location permission granted by user.\")\n                    fetchLastLocation()\n                } else {\n                    Log.d(TAG, \"Location permission denied by user.\")\n                    Toast.makeText(\n                        this,\n                        \"Location permission denied. Showing default location.\",\n                        Toast.LENGTH_LONG\n                    ).show()\n                    moveToSydney()\n                }\n            }\n\n        enableEdgeToEdge()\n        binding = ActivityFullConfigurableMapBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        binding.dismissButton.setOnClickListener {\n            dismissPlaceDetails()\n        }\n\n        val apiKey = BuildConfig.PLACES_API_KEY\n        if (apiKey.isEmpty() || apiKey == \"YOUR_API_KEY\") {\n            Log.e(TAG, \"No api key\")\n            Toast.makeText(\n                this,\n                \"Add your own API_KEY in local.properties\",\n                Toast.LENGTH_LONG\n            ).show()\n            finish()\n            return\n        }\n\n        // Initialize the Places SDK.\n        Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)\n        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)\n\n        val mapFragment =\n            supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment?\n        mapFragment?.getMapAsync(this)\n\n        // This coroutine observes the content selection from the ViewModel.\n        // `collectLatest` ensures that if the user changes the selection multiple times quickly,\n        // only the latest selection is used to update the UI, preventing unnecessary work.\n        lifecycleScope.launch {\n            viewModel.selectedContent.collectLatest {\n                viewModel.selectedPlaceId?.let { placeId ->\n                    Log.d(TAG, \"Content selection changed. Reloading PlaceDetailsFragment for place ID: $placeId\")\n                    showPlaceDetailsFragment(placeId)\n                }\n            }\n        }\n\n        // Restore the fragment if a place was already selected before a configuration change.\n        if (viewModel.selectedPlaceId != null) {\n            viewModel.selectedPlaceId?.let { placeId ->\n                Log.d(TAG, \"Restoring PlaceDetailsFragment for place ID: $placeId\")\n                showPlaceDetailsFragment(placeId)\n            }\n        }\n\n        binding.configureButton.setOnClickListener {\n            showContentSelectionDialog()\n        }\n\n        binding.myLocationButton.setOnClickListener {\n            fetchLastLocation()\n        }\n    }\n\n    override fun onMapReady(map: GoogleMap) {\n        Log.d(TAG, \"Map is ready\")\n        googleMap = map\n        googleMap?.setOnPoiClickListener(this)\n\n        if (isLocationPermissionGranted()) {\n            fetchLastLocation()\n        } else {\n            requestLocationPermissions()\n        }\n    }\n\n    private fun isLocationPermissionGranted(): Boolean {\n        return ActivityCompat.checkSelfPermission(\n            this,\n            Manifest.permission.ACCESS_FINE_LOCATION\n        ) == PackageManager.PERMISSION_GRANTED ||\n                ActivityCompat.checkSelfPermission(\n                    this,\n                    Manifest.permission.ACCESS_COARSE_LOCATION\n                ) == PackageManager.PERMISSION_GRANTED\n    }\n\n    private fun requestLocationPermissions() {\n        Log.d(TAG, \"Requesting location permissions.\")\n        requestPermissionLauncher.launch(\n            arrayOf(\n                Manifest.permission.ACCESS_FINE_LOCATION,\n                Manifest.permission.ACCESS_COARSE_LOCATION\n            )\n        )\n    }\n\n    private fun handleLocationError() {\n        Log.d(TAG, \"Could not retrieve current location. Falling back to Sydney.\")\n        Toast.makeText(\n            this,\n            \"Could not retrieve current location. Showing default location.\",\n            Toast.LENGTH_LONG\n        ).show()\n        moveToSydney()\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun fetchLastLocation() {\n        if (isLocationPermissionGranted()) {\n            fusedLocationClient.lastLocation\n                .addOnSuccessListener { location: Location? ->\n                    if (location != null) {\n                        val latLng = LatLng(location.latitude, location.longitude)\n                        googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f))\n                        Log.d(TAG, \"Moved to user's last known location.\")\n                    } else {\n                        handleLocationError()\n                    }\n                }\n                .addOnFailureListener {\n                    Log.e(TAG, \"Failed to get location.\", it)\n                    handleLocationError()\n                }\n        } else {\n            requestLocationPermissions()\n        }\n    }\n\n    private fun moveToSydney() {\n        val sydney = LatLng(-33.8688, 151.2093)\n        googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 13f))\n        Log.d(TAG, \"Moved to Sydney\")\n    }\n\n    override fun onPoiClick(poi: PointOfInterest) {\n        val placeId = poi.placeId\n        Log.d(TAG, \"Place ID: $placeId\")\n        viewModel.selectedPlaceId = placeId\n        showPlaceDetailsFragment(placeId)\n    }\n\n    /**\n     * Displays the [PlaceDetailsFragment] for a given place ID.\n     * The content shown in the fragment is determined by the user's selection,\n     * which is retrieved from the [FullContentSelectionViewModel].\n     *\n     * @param placeId The ID of the place to display.\n     */\n    private fun showPlaceDetailsFragment(placeId: String) {\n        Log.d(TAG, \"Showing PlaceDetailsFragment for place ID: $placeId\")\n        binding.placeDetailsWrapper.visibility = View.VISIBLE\n        binding.dismissButton.visibility = View.GONE\n        binding.placeDetailsContainer.visibility = View.GONE\n        binding.loadingIndicatorConfigurable.visibility = View.VISIBLE\n\n        val orientation =\n            if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {\n                Orientation.HORIZONTAL\n            } else {\n                Orientation.VERTICAL\n            }\n\n        // The key step: Create a new instance of the fragment, passing the list of\n        // selected content from the ViewModel. This ensures the fragment respects the user's configuration.\n        val fragment = PlaceDetailsFragment.newInstance(\n            viewModel.selectedContent.value.map { it.content },\n            orientation,\n            R.style.CustomizedPlaceDetailsTheme,\n        ).apply {\n            setPlaceLoadListener(object : PlaceLoadListener {\n                override fun onSuccess(place: Place) {\n                    Log.d(TAG, \"Place loaded: ${place.id}\")\n                    binding.loadingIndicatorConfigurable.visibility = View.GONE\n                    binding.placeDetailsContainer.visibility = View.VISIBLE\n                    binding.dismissButton.visibility = View.VISIBLE\n                }\n\n                override fun onFailure(e: Exception) {\n                    Log.e(TAG, \"Place failed to load\", e)\n                    dismissPlaceDetails()\n                    Toast.makeText(\n                        this@FullConfigurablePlaceDetailsActivity,\n                        \"Failed to load place details.\",\n                        Toast.LENGTH_SHORT\n                    ).show()\n                }\n            })\n        }\n\n        supportFragmentManager\n            .beginTransaction()\n            .replace(binding.placeDetailsContainer.id, fragment)\n            .commitNow()\n\n        binding.root.post {\n            fragment.loadWithPlaceId(placeId)\n        }\n    }\n\n    private fun dismissPlaceDetails() {\n        binding.placeDetailsWrapper.visibility = View.GONE\n        viewModel.selectedPlaceId = null\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        googleMap = null\n    }\n\n    /**\n     * Displays a dialog that allows the user to select which content types\n     * should be visible in the [PlaceDetailsFragment].\n     * This demonstrates embedding a Jetpack Compose UI inside a View-based dialog.\n     */\n    private fun showContentSelectionDialog() {\n        val dialogView =\n            LayoutInflater.from(this).inflate(R.layout.content_selector_dialog, null)\n        val composeView = dialogView.findViewById<ComposeView>(R.id.compose_view)\n\n        composeView.setContent {\n            // `collectAsState` observes the ViewModel's Flows and triggers recomposition\n            // automatically when the state changes.\n            val selectedContent by viewModel.selectedContent.collectAsState()\n            val unselectedContent by viewModel.unselectedContent.collectAsState()\n\n            // We pass the state down to the Composable and hoist the events up to the ViewModel.\n            PlaceContentSelectionDialogContent(selectedContent, unselectedContent) {\n                viewModel.toggleSelection(it)\n            }\n        }\n\n        AlertDialog.Builder(this)\n            .setView(dialogView)\n            .setPositiveButton(\"Close\") { dialog, _ ->\n                dialog.dismiss()\n            }\n            .create()\n            .show()\n    }\n}\n\n/**\n * A Composable that displays the content selection UI.\n *\n * @param selectedContent The list of items that are currently selected.\n * @param unselectedContent The list of items that are available but not selected.\n * @param onItemClick A callback function invoked when any item is clicked.\n */\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun PlaceContentSelectionDialogContent(\n    selectedContent: List<PlaceDetailsFullItem>,\n    unselectedContent: List<PlaceDetailsFullItem>,\n    onItemClick: (PlaceDetailsFullItem) -> Unit\n) {\n    LazyColumn {\n        stickyHeader {\n            SectionHeader(\"Selected Content\")\n        }\n\n        items(selectedContent, key = { it.content.name }) { content ->\n            ContentItem(\n                item = content,\n                onItemClick = onItemClick\n            )\n        }\n\n        stickyHeader {\n            SectionHeader(\"Unselected Content\")\n        }\n\n        items(unselectedContent, key = { it.content.name }) { content ->\n            ContentItem(\n                item = content,\n                onItemClick = onItemClick\n            )\n        }\n    }\n}\n\n/**\n * A Composable that renders a styled header for a section in the list.\n */\n@Composable\nfun SectionHeader(title: String) {\n    Text(\n        text = title,\n        modifier = Modifier\n            .fillMaxWidth()\n            .background(MaterialTheme.colorScheme.tertiaryContainer)\n            .padding(16.dp),\n        style = MaterialTheme.typography.titleMedium,\n        color = MaterialTheme.colorScheme.onTertiaryContainer,\n        textAlign = TextAlign.Center\n    )\n}\n\n/**\n * A Composable that renders a single clickable item in the content selection list.\n */\n@Composable\nfun ContentItem(\n    item: PlaceDetailsFullItem,\n    onItemClick: (PlaceDetailsFullItem) -> Unit\n) {\n    Text(\n        text = item.displayName,\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable { onItemClick(item) }\n            .padding(16.dp),\n        style = MaterialTheme.typography.bodyLarge\n    )\n}\n\n// --- Previews ---\n// These previews allow for rapid UI development of the dialog content in various states.\n\n@Preview(name = \"Both Sections\", showBackground = true, backgroundColor = 0xFFFFFFFF)\n@Composable\nfun DialogContentPreview_BothSections() {\n    MaterialTheme {\n        PlaceContentSelectionDialogContent(\n            selectedContent = PlaceDetailsFullItem.standardContent,\n            unselectedContent = PlaceDetailsFullItem.standardNonContent,\n            onItemClick = {}\n        )\n    }\n}\n\n@Preview(name = \"Only Selected\", showBackground = true, backgroundColor = 0xFFFFFFFF)\n@Composable\nfun DialogContentPreview_OnlySelected() {\n    MaterialTheme {\n        PlaceContentSelectionDialogContent(\n            selectedContent = PlaceDetailsFragment.Content.entries.toPlaceDetailsFullItems(),\n            unselectedContent = emptyList(),\n            onItemClick = {}\n        )\n    }\n}\n\n@Preview(name = \"Only Unselected\", showBackground = true, backgroundColor = 0xFFFFFFFF)\n@Composable\nfun DialogContentPreview_OnlyUnselected() {\n    MaterialTheme {\n        PlaceContentSelectionDialogContent(\n            selectedContent = emptyList(),\n            unselectedContent = PlaceDetailsFragment.Content.entries.toPlaceDetailsFullItems(),\n            onItemClick = {}\n        )\n    }\n}\n\n@Preview(name = \"Empty State\", showBackground = true, backgroundColor = 0xFFFFFFFF)\n@Composable\nfun DialogContentPreview_Empty() {\n    MaterialTheme {\n        PlaceContentSelectionDialogContent(\n            selectedContent = emptyList(),\n            unselectedContent = emptyList(),\n            onItemClick = {}\n        )\n    }\n}\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/full/FullContentSelectionViewModel.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit.full\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.android.libraries.places.widget.PlaceDetailsFragment\nimport com.google.android.libraries.places.widget.PlaceDetailsFragment.Content\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\n\n/**\n * A data class that represents a single configurable item for the [PlaceDetailsFragment].\n * It wraps the library's [Content] enum with additional properties needed for the UI.\n *\n * @param content The actual [Content] enum value from the Place Details library.\n * @param displayName A formatted, human-readable string for the content type.\n * @param isSelected A boolean indicating whether the user has selected this content to be displayed.\n */\ndata class PlaceDetailsFullItem(\n    val content: Content,\n    val displayName: String,\n    val isSelected: Boolean = false\n) {\n    companion object {\n        /**\n         * The default list of content fields displayed by the [PlaceDetailsFragment].\n         * We use this to set the initial state of the configuration dialog.\n         */\n        val standardContent: List<PlaceDetailsFullItem> = PlaceDetailsFragment.STANDARD_CONTENT.toPlaceDetailsFullItems()\n\n        /**\n         * A list of all available content fields that are not included in the default set.\n         */\n        val standardNonContent: List<PlaceDetailsFullItem> =\n            Content.entries.filterNot { content ->\n                standardContent.any { it.content == content }\n            }.toPlaceDetailsFullItems()\n    }\n}\n\n/**\n * An extension function to convert a [Content] enum into a [PlaceDetailsFullItem].\n */\nprivate fun Content.toPlaceDetailsFullItem(): PlaceDetailsFullItem =\n    PlaceDetailsFullItem(\n        content = this,\n        displayName = this.getDisplayName(),\n        isSelected = PlaceDetailsFragment.STANDARD_CONTENT.contains(this)\n    )\n\n/**\n * An extension function that formats a [Content] enum name into a user-friendly, readable string.\n * For example, `REVIEWS` becomes \"Reviews\".\n *\n * @return A capitalized, space-separated string representation of the enum name.\n */\nfun Content.getDisplayName(): String =\n    this.name.replace('_', ' ').lowercase().replaceFirstChar { it.uppercase() }\n\n/**\n * An extension function to convert a collection of [Content] enums into a list of\n * [PlaceDetailsFullItem]s, ready to be used by the UI.\n */\nfun Iterable<Content>.toPlaceDetailsFullItems(): List<PlaceDetailsFullItem> =\n    this.map { it.toPlaceDetailsFullItem() }\n\n/**\n * A [ViewModel] for the [FullConfigurablePlaceDetailsActivity] that manages the\n * state for the content selection UI. It follows the same reactive pattern as the\n * compact version's ViewModel.\n */\nclass FullContentSelectionViewModel : ViewModel() {\n    /**\n     * The ID of the place currently being displayed. Stored in the ViewModel to\n     * survive configuration changes.\n     */\n    var selectedPlaceId: String? = null\n\n    /**\n     * The private, mutable state holder for the list of all content items. This is the\n     * single source of truth for the selection state.\n     */\n    private val _contentItems = MutableStateFlow(\n        Content.entries.map {\n            PlaceDetailsFullItem(\n                content = it,\n                displayName = it.getDisplayName(),\n                isSelected = PlaceDetailsFragment.STANDARD_CONTENT.contains(it)\n            )\n        }\n    )\n\n    /**\n     * A read-only `StateFlow` that exposes the list of currently **selected** content items.\n     * The UI collects this flow to display the list of selected items.\n     */\n    val selectedContent: StateFlow<List<PlaceDetailsFullItem>> =\n        _contentItems.map { items -> items.filter { it.isSelected } }\n            .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())\n\n    /**\n     * A read-only `StateFlow` that exposes the list of currently **unselected** content items.\n     * The UI collects this flow to display the list of available items.\n     */\n    val unselectedContent: StateFlow<List<PlaceDetailsFullItem>> =\n        _contentItems.map { items -> items.filterNot { it.isSelected } }\n            .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())\n\n    /**\n     * This function handles the logic of toggling an item's selection status. It is called\n     * from the UI when a user interacts with the selection dialog.\n     *\n     * @param itemToToggle The [PlaceDetailsFullItem] that the user clicked.\n     */\n    fun toggleSelection(itemToToggle: PlaceDetailsFullItem) {\n        _contentItems.update { currentItems ->\n            currentItems.map { item ->\n                if (item.content == itemToToggle.content) {\n                    item.copy(isSelected = !item.isSelected)\n                } else {\n                    item\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/ui/theme/Color.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit.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": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/ui/theme/Theme.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit.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.runtime.SideEffect\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.core.view.WindowCompat\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 PlaceDetailsUIKitTheme(\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    val view = LocalView.current\n    if (!view.isInEditMode) {\n        SideEffect {\n            val window = (view.context as Activity).window\n            window.statusBarColor = colorScheme.primary.toArgb()\n            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme\n        }\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/java/com/example/placedetailsuikit/ui/theme/Type.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placedetailsuikit.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": "PlaceDetailsUIKit/src/main/res/drawable/close_button_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <solid android:color=\"#80000000\" /> <!-- Semi-transparent black -->\n</shape>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/drawable/ic_close.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\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</vector>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlaceDetailsUIKit/src/main/res/drawable/ic_launcher_foreground.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<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=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\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=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/drawable/outline_my_location_24.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:viewportHeight=\"960\" android:viewportWidth=\"960\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M440,918L440,838Q315,824 225.5,734.5Q136,645 122,520L42,520L42,440L122,440Q136,315 225.5,225.5Q315,136 440,122L440,42L520,42L520,122Q645,136 734.5,225.5Q824,315 838,440L918,440L918,520L838,520Q824,645 734.5,734.5Q645,824 520,838L520,918L440,918ZM480,760Q596,760 678,678Q760,596 760,480Q760,364 678,282Q596,200 480,200Q364,200 282,282Q200,364 200,480Q200,596 282,678Q364,760 480,760ZM480,640Q414,640 367,593Q320,546 320,480Q320,414 367,367Q414,320 480,320Q546,320 593,367Q640,414 640,480Q640,546 593,593Q546,640 480,640ZM480,560Q513,560 536.5,536.5Q560,513 560,480Q560,447 536.5,423.5Q513,400 480,400Q447,400 423.5,423.5Q400,447 400,480Q400,513 423.5,536.5Q447,560 480,560ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z\"/>\n    \n</vector>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/drawable/outline_settings_24.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:viewportHeight=\"960\" android:viewportWidth=\"960\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z\"/>\n    \n</vector>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/font/custom_font.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<!--\nThis file defines a custom font family using Android's Downloadable Fonts feature.\nInstead of bundling the font in the APK, this tells the system to download\nRoboto Mono from the Google Fonts provider when needed.\n-->\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    app:fontProviderAuthority=\"com.google.android.gms.fonts\"\n    app:fontProviderPackage=\"com.google.android.gms\"\n    app:fontProviderQuery=\"name=Roboto Mono&amp;weight=400\"\n    app:fontProviderCerts=\"@array/com_google_android_gms_fonts_certs\">\n</font-family>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/layout/activity_configurable_map.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".compact.ConfigurablePlaceDetailsActivity\">\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/map_fragment\"\n        android:name=\"com.google.android.gms.maps.SupportMapFragment\"\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_toTopOf=\"parent\" />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/my_location_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:clickable=\"true\"\n        android:contentDescription=\"@string/my_location_button_content_description\"\n        app:layout_constraintBottom_toTopOf=\"@+id/configure_button\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:srcCompat=\"@drawable/outline_my_location_24\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/configure_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:clickable=\"true\"\n        android:contentDescription=\"@string/configure_button_content_description\"\n        app:layout_constraintBottom_toTopOf=\"@+id/place_details_wrapper\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:srcCompat=\"@drawable/outline_settings_24\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <!-- Wrapper to hold the fragment, the dismiss button, AND the loader -->\n    <FrameLayout\n        android:id=\"@+id/place_details_wrapper\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:elevation=\"4dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        tools:visibility=\"visible\">\n\n        <!-- The container for the fragment -->\n        <FrameLayout\n            android:id=\"@+id/place_details_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <!-- The loader, shown on top of the empty container -->\n        <ProgressBar\n            android:id=\"@+id/loading_indicator_configurable\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\" />\n\n        <!-- The dismiss button with a background. It's hidden initially. -->\n        <ImageButton\n            android:id=\"@+id/dismiss_button\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_gravity=\"top|end\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:background=\"@drawable/close_button_background\"\n            android:contentDescription=\"@string/dismiss_button_content_description\"\n            android:padding=\"12dp\"\n            android:visibility=\"gone\"\n            app:srcCompat=\"@drawable/ic_close\"\n            app:tint=\"@android:color/white\"\n            tools:visibility=\"visible\" />\n    </FrameLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/layout/activity_full_configurable_map.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".full.FullConfigurablePlaceDetailsActivity\"> <!-- Updated context -->\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/map_fragment\"\n        android:name=\"com.google.android.gms.maps.SupportMapFragment\"\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_toTopOf=\"parent\" />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/my_location_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:clickable=\"true\"\n        android:contentDescription=\"@string/my_location_button_content_description\"\n        app:layout_constraintBottom_toTopOf=\"@+id/configure_button\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:srcCompat=\"@drawable/outline_my_location_24\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/configure_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:clickable=\"true\"\n        android:contentDescription=\"@string/configure_button_content_description\"\n        app:layout_constraintBottom_toTopOf=\"@+id/place_details_wrapper\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:srcCompat=\"@drawable/outline_settings_24\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <!-- Wrapper to hold the fragment, the dismiss button, AND the loader -->\n    <FrameLayout\n        android:id=\"@+id/place_details_wrapper\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:elevation=\"4dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        tools:visibility=\"visible\">\n\n        <!-- The container for the fragment -->\n        <FrameLayout\n            android:id=\"@+id/place_details_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <!-- The loader, shown on top of the empty container -->\n        <ProgressBar\n            android:id=\"@+id/loading_indicator_configurable\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\" />\n\n        <!-- The dismiss button with a background. It's hidden initially. -->\n        <ImageButton\n            android:id=\"@+id/dismiss_button\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_gravity=\"top|end\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:background=\"@drawable/close_button_background\"\n            android:contentDescription=\"@string/dismiss_button_content_description\"\n            android:padding=\"12dp\"\n            android:visibility=\"gone\"\n            app:srcCompat=\"@drawable/ic_close\"\n            app:tint=\"@android:color/white\"\n            tools:visibility=\"visible\" />\n    </FrameLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\">\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/map_fragment\"\n        android:name=\"com.google.android.gms.maps.SupportMapFragment\"\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_toTopOf=\"parent\" />\n\n    <!-- Wrapper to hold the fragment, the dismiss button, AND the loader -->\n    <FrameLayout\n        android:id=\"@+id/place_details_wrapper\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:elevation=\"4dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        tools:visibility=\"visible\">\n\n        <!-- The container for the fragment -->\n        <FrameLayout\n            android:id=\"@+id/place_details_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <!-- The loader, shown on top of the empty container -->\n        <ProgressBar\n            android:id=\"@+id/loading_indicator_main\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\" />\n\n        <!-- The dismiss button with a background. It's hidden initially. -->\n        <ImageButton\n            android:id=\"@+id/dismiss_button\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_gravity=\"top|end\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:background=\"@drawable/close_button_background\"\n            android:contentDescription=\"@string/dismiss_button_content_description\"\n            android:padding=\"12dp\"\n            android:visibility=\"gone\"\n            app:srcCompat=\"@drawable/ic_close\"\n            app:tint=\"@android:color/white\"\n            tools:visibility=\"visible\" />\n    </FrameLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/layout/content_selector_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.compose.ui.platform.ComposeView\n        android:id=\"@+id/compose_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"16dp\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/mipmap-anydpi/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlaceDetailsUIKit/src/main/res/mipmap-anydpi/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlaceDetailsUIKit/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<resources>\n    <!-- \"Synthwave\" Avant-Garde Theme Colors -->\n    <color name=\"synthwave_surface\">#1A0A2D</color> <!-- Deep purple background -->\n    <color name=\"synthwave_primary\">#E0218A</color> <!-- Vibrant magenta for primary actions -->\n    <color name=\"synthwave_on_surface\">#F0F8FF</color> <!-- Alice blue for primary text, high contrast -->\n    <color name=\"synthwave_on_surface_variant\">#9E8BBE</color> <!-- Muted lilac for secondary text -->\n    <color name=\"synthwave_outline\">#00E5FF</color> <!-- Bright cyan for outlines, creating a \"neon glow\" -->\n    <color name=\"synthwave_positive\">#00E5FF</color> <!-- Cyan for \"Open\" status -->\n    <color name=\"synthwave_negative\">#FF007F</color> <!-- Hot pink for \"Closed\" status -->\n</resources>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/values/strings.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<resources>\n    <string name=\"app_name\">Place Details UI Kit</string>\n    <string name=\"dismiss_button_content_description\">Dismiss place details</string>\n    <string name=\"configure_button_content_description\">\"Configure the Place Details component \"</string>\n    <string name=\"my_location_button_content_description\">Center map on user\\'s current location</string>\n</resources>"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/values/themes.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<resources>\n    <style name=\"Base.Theme.PlaceDetailsUIKit\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n        <item name=\"android:windowLayoutInDisplayCutoutMode\">shortEdges</item>\n    </style>\n    <style name=\"Theme.PlaceDetailsUIKit\" parent=\"Base.Theme.PlaceDetailsUIKit\" />\n\n    <!--\n    A custom text appearance using our monospaced font.\n    -->\n    <style name=\"app_text_appearence_mono\" parent=\"TextAppearance.MaterialComponents.Body2\">\n        <item name=\"android:fontFamily\">@font/custom_font</item>\n        <item name=\"android:textColor\">@color/synthwave_on_surface_variant</item>\n    </style>\n\n    <style name=\"app_text_appearence_mono_label\" parent=\"TextAppearance.MaterialComponents.Body1\">\n        <item name=\"android:fontFamily\">@font/custom_font</item>\n        <item name=\"android:textColor\">@color/synthwave_on_surface</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n\n    <!--\n    A custom \"Synthwave\" theme for the PlaceDetailsCompactFragment.\n    This theme overrides multiple attributes to create a unique, retro-futuristic look.\n    -->\n    <style name=\"CustomizedPlaceDetailsTheme\" parent=\"PlacesMaterialTheme\">\n        <!-- Core Colors -->\n        <item name=\"placesColorSurface\">@color/synthwave_surface</item>\n        <item name=\"placesColorPrimary\">@color/synthwave_primary</item>\n        <item name=\"placesColorOnSurface\">@color/synthwave_on_surface</item>\n        <item name=\"placesColorOnSurfaceVariant\">@color/synthwave_on_surface_variant</item>\n        <item name=\"placesColorOutlineDecorative\">@color/synthwave_outline</item>\n\n        <!-- Status Colors -->\n        <item name=\"placesColorPositive\">@color/synthwave_positive</item>\n        <item name=\"placesColorNegative\">@color/synthwave_negative</item>\n\n        <!-- Shape -->\n        <item name=\"placesCornerRadius\">8dp</item>\n\n        <!-- Typography -->\n        <item name=\"placesTextAppearanceBodySmall\">@style/app_text_appearence_mono</item>\n        <item name=\"placesTextAppearanceBodyMedium\">@style/app_text_appearence_mono</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/values-night/themes.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Base.Theme.PlaceDetailsUIKit\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n        <!-- Customize your dark theme here. -->\n        <!-- <item name=\"colorPrimary\">@color/my_dark_primary</item> -->\n    </style>\n</resources>"
  },
  {
    "path": "PlaceDetailsUIKit/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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   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": "PlaceDetailsUIKit/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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   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": "PlaceDetailsUIKit/src/test/java/com/example/placedetailsuikit/ExampleUnitTest.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placedetailsuikit\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "PlacesUIKit3D/.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/secrets.properties\n"
  },
  {
    "path": "PlacesUIKit3D/README.md",
    "content": "# Places UI Kit 3D Sample\n\nThis sample demonstrates how to integrate the Places SDK for Android's `PlaceDetailsCompactFragment` with a 3D map view. It allows users to tap on a location on a 3D Google Map to display its details in a compact, embedded view.\n\n## Features\n\n- **3D Google Map**: Utilizes the 3D Maps SDK to display a rich, interactive 3D map.\n- **Place Details**: On tapping a location on the map, the app displays the place's details using the `PlaceDetailsCompactFragment`.\n- **MVVM Architecture**: Follows the Model-View-ViewModel pattern, with a `MainViewModel` managing the UI state.\n- **Location-Aware**: Requests location permissions to center the map on the user's current location.\n- **Secrets Management**: Uses the Secrets Gradle Plugin for Android to securely handle the Google Maps API key.\n\n## Screenshot\n\n![App Screenshot](screenshots/places-ui-kit-3d-demo.png)\n\n## Requirements\n\n- Android Studio\n- An Android device or emulator\n- A Google Maps API key\n\n## Setup and Installation\n\n1.  **Clone the repository:**\n    ```bash\n    git clone https://github.com/googlemaps-samples/android-places-demos.git\n    ```\n2.  **Open the project in Android Studio:**\n    Open the `PlacesUIKit3D` directory in Android Studio.\n\n3.  **Add your API Key:**\n    -   Create a file named `secrets.properties` in the root directory of the `PlacesUIKit3D` project (`/Users/dkhawk/AndroidStudioProjects/github-maps-code/android-places-demos/PlacesUIKit3D`).\n    -   Add your Google Maps API key to the `secrets.properties` file, making sure that the Maps SDK for Android and the Places API are enabled for the key.\n        ```\n        MAPS3D_API_KEY=\"YOUR_API_KEY\"\n        PLACES_API_KEY=\"YOUR_API_KEY\"\n        ```\n    - Note: The `secrets.properties` file is included in the `.gitignore` file to prevent it from being checked into version control.\n\n## Running the Application\n\nOnce the project is set up and the API key is added, you can run the application on an Android device or emulator directly from Android Studio.\n\n- The app will request location permissions.\n- The map will center on the user's location if permission is granted, otherwise it will default to a location in Colorado.\n- Tap on any location on the map to see the Place Details view.\n\n## License\n\n```\nCopyright 2025 Google LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the 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,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n"
  },
  {
    "path": "PlacesUIKit3D/build.gradle.kts",
    "content": "/*\n * Copyright 2025 Google LLC\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// The `plugins` block is where we apply Gradle plugins to this module.\n// Plugins add new tasks and configurations to our build process.\nplugins {\n    id(\"places-demo.android.application\")\n    // This plugin enables Kotlin support in the Android project, allowing us to write code in Kotlin.\n    alias(libs.plugins.kotlin.android)\n    // This plugin from Google helps manage API keys and other secrets by reading them from a `secrets.properties`\n    // file (which should be in .gitignore) and exposing them in the `BuildConfig` file at compile time.\n    // This is crucial for keeping sensitive data out of version control.\n    id(\"places-demo.secrets\")\n    // This plugin provides the necessary integration for using Jetpack Compose with the Kotlin compiler.\n    alias(libs.plugins.kotlin.compose)\n    // KSP (Kotlin Symbol Processing) is used for annotation processing. Hilt uses it to generate code.\n    alias(libs.plugins.ksp)\n    // The Hilt plugin integrates Dagger Hilt for dependency injection.\n    alias(libs.plugins.hilt.android)\n    // The parcelize plugin provides a @Parcelize annotation to automatically generate Parcelable implementations.\n    alias(libs.plugins.jetbrains.kotlin.parcelize)\n}\n\n// The `android` block is where we configure all the Android-specific build options.\nandroid {\n    // The `namespace` is a unique identifier for the app's generated R class. It's also used\n    // as the default `applicationId` if not specified in `defaultConfig`.\n    namespace = \"com.example.placesuikit3d\"\n\n    defaultConfig {\n        // `applicationId` is the unique identifier for the app on the Google Play Store and on the device.\n        applicationId = \"com.example.placesuikit3d\"\n        // `minSdk` is the minimum API level required to run the app. Devices below this level cannot install it.\n        minSdk = 29\n        versionCode = 1\n        versionName = \"1.0\"\n\n        // Specifies the instrumentation runner for running Android tests.\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        // The `release` block configures settings for the release build of the app.\n        release {\n            // `isMinifyEnabled` enables code shrinking with R8 to reduce the app's size.\n            // It's disabled here for simplicity in a sample app, but highly recommended for production.\n            isMinifyEnabled = false\n            // `proguardFiles` specifies the files that define the R8 shrinking and obfuscation rules.\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n\n    kotlin {\n        compilerOptions {\n            freeCompilerArgs.addAll(\n                \"-opt-in=kotlin.RequiresOptIn\",\n                \"-Xannotation-default-target=param-property\"\n            )\n        }\n    }\n\n    buildFeatures {\n        // `viewBinding` generates a binding class for each XML layout file, providing a type-safe\n        // way to access views without `findViewById`. This is used in the XML-based activities.\n        viewBinding = true\n        // `compose` enables Jetpack Compose for the project.\n        compose = true\n        // `buildConfig` generates a `BuildConfig` class that contains constants from the build configuration,\n        // such as the API key from the secrets plugin.\n        buildConfig = true\n    }\n    composeOptions {\n        // Sets the version of the Kotlin compiler extension for Compose. This version must be\n        // compatible with the Kotlin version used in the project.\n        kotlinCompilerExtensionVersion = \"1.5.1\"\n    }\n}\n\n// The `dependencies` block is where we declare all the external libraries the app needs.\n// These are fetched from repositories like Maven Central and Google's Maven repository.\ndependencies {\n    // --- Core AndroidX & UI Libraries ---\n    // These are foundational libraries for building modern Android apps.\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.lifecycle.runtime.ktx)\n    implementation(libs.androidx.fragment.ktx)\n    implementation(libs.material) // For Material Design components (used in XML layouts).\n\n    // --- Jetpack Compose ---\n    // These libraries are for building UIs with Jetpack Compose.\n    implementation(libs.androidx.activity.compose) // Integration between Activity and Compose.\n    implementation(platform(libs.androidx.compose.bom)) // The Compose Bill of Materials (BOM) ensures all Compose libraries use compatible versions.\n    implementation(libs.androidx.ui)\n    implementation(libs.androidx.ui.graphics)\n    implementation(libs.androidx.ui.tooling.preview) // For displaying @Preview composables in Android Studio.\n    implementation(libs.androidx.material3) // The latest Material Design components for Compose.\n    implementation(libs.androidx.fragment.compose)\n    implementation(libs.androidx.material.icons.extended)\n    debugImplementation(libs.androidx.ui.tooling) // Provides tools for inspecting Compose UIs.\n\n    // --- Google Play Services ---\n    // These are the essential libraries for this sample, providing Maps and Places functionality.\n    implementation(libs.play.services.maps3d) // The core SDK for embedding 3D Google Maps.\n    implementation(libs.places) // The SDK for the Places UI Kit (PlaceDetails fragments).\n    implementation(libs.maps.utils.ktx) // Google Maps Utils for polyline decoding and other utilities.\n\n    // --- Dependency Injection ---\n    // Hilt is used for managing dependencies and object lifecycles.\n    implementation(libs.dagger)\n    ksp(libs.hilt.android.compiler)\n    implementation(libs.hilt.android)\n\n    // --- Miscellaneous ---\n    implementation(libs.kotlinx.datetime)\n\n    // --- Testing Libraries ---\n    // These libraries are for writing and running tests.\n    // `testImplementation` is for local unit tests (running on the JVM).\n    testImplementation(libs.junit)\n    testImplementation(libs.google.truth)\n    // `androidTestImplementation` is for instrumented tests (running on an Android device or emulator).\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core) // For UI testing with the View system.\n\n    // --- Compose Testing ---\n    // These are specific to testing Jetpack Compose UIs.\n    androidTestImplementation(platform(libs.androidx.compose.bom)) // BOM for testing libraries.\n    androidTestImplementation(libs.androidx.ui.test.junit4) // The main library for Compose UI tests.\n    debugImplementation(libs.androidx.ui.test.manifest) // Provides a manifest for UI tests.\n}\n\n"
  },
  {
    "path": "PlacesUIKit3D/local.defaults.properties",
    "content": "MAPS3D_API_KEY=DEFAULT_API_KEY\nPLACES_API_KEY=DEFAULT_API_KEY"
  },
  {
    "path": "PlacesUIKit3D/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": "PlacesUIKit3D/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n\n\n    <application\n        android:name=\".Maps3DPlacesApplication\"\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.PlacesUIKit3D\"\n        tools:targetApi=\"31\">\n\n        <meta-data\n            android:name=\"com.google.android.geo.maps3d.API_KEY\"\n            android:value=\"${MAPS3D_API_KEY}\"\n            />\n\n        <activity\n            android:name=\".MainActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/Theme.PlacesUIKit3D\">\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": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/Landmark.kt",
    "content": "// Copyright 2026 Google LLC\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.example.placesuikit3d\n\nimport com.google.android.gms.maps3d.model.LatLngAltitude\n\n/**\n * A data class representing a landmark in the demo.\n *\n * @property id The unique Place ID for this landmark.\n * @property name The human-readable name of the landmark.\n * @property location The coordinates on the 3D map where the camera should point.\n */\ndata class Landmark(\n    val id: String,\n    val name: String,\n    val location: LatLngAltitude\n)\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/LandmarkList.kt",
    "content": "// Copyright 2026 Google LLC\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.example.placesuikit3d\n\nimport androidx.compose.foundation.clickable\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.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Place\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\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.unit.dp\n\n/**\n * A composable that displays a list of landmarks.\n *\n * @param landmarks The list of landmarks to display.\n * @param onLandmarkClick Callback invoked when a landmark is clicked.\n * @param modifier The modifier to apply to the list.\n */\n@Composable\nfun LandmarkList(\n    landmarks: List<Landmark>,\n    onLandmarkClick: (Landmark) -> Unit,\n    modifier: Modifier = Modifier\n) {\n    Column(modifier = modifier) {\n        Text(\n            text = \"Locations\",\n            style = MaterialTheme.typography.headlineSmall,\n            modifier = Modifier.padding(16.dp)\n        )\n        LazyColumn(modifier = Modifier.weight(1f)) {\n            items(landmarks) { landmark ->\n                LandmarkItem(\n                    landmark = landmark,\n                    onClick = { onLandmarkClick(landmark) }\n                )\n                HorizontalDivider()\n            }\n        }\n    }\n}\n\n/**\n * A composable that displays a single landmark item.\n */\n@Composable\nprivate fun LandmarkItem(\n    landmark: Landmark,\n    onClick: () -> Unit\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable(onClick = onClick)\n            .padding(16.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Icon(\n            imageVector = Icons.Default.Place,\n            contentDescription = null,\n            tint = MaterialTheme.colorScheme.primary,\n            modifier = Modifier.padding(end = 16.dp)\n        )\n        Column {\n            Text(\n                text = landmark.name,\n                style = MaterialTheme.typography.titleMedium\n            )\n            Text(\n                text = \"Boulder, CO\",\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/MainActivity.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placesuikit3d\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.compose.setContent\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Box\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.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.MyLocation\nimport androidx.compose.material3.BottomSheetScaffold\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.SheetValue\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.rememberBottomSheetScaffoldState\nimport androidx.compose.material3.rememberStandardBottomSheetState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.unit.dp\nimport kotlinx.coroutines.launch\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.core.app.ActivityCompat\nimport androidx.core.view.WindowCompat\nimport androidx.fragment.app.FragmentContainerView\nimport androidx.fragment.app.commit\nimport com.example.placesuikit3d.ui.theme.PlacesUIKit3DTheme\nimport com.example.placesuikit3d.utils.feet\nimport com.example.placesuikit3d.utils.toValidCamera\nimport com.google.android.gms.location.FusedLocationProviderClient\nimport com.google.android.gms.location.LocationServices\nimport com.google.android.gms.maps3d.GoogleMap3D\nimport com.google.android.gms.maps3d.OnMap3DViewReadyCallback\nimport com.google.android.gms.maps3d.model.Camera\nimport com.google.android.gms.maps3d.model.Map3DMode\nimport com.google.android.gms.maps3d.model.camera\nimport com.google.android.gms.maps3d.model.flyToOptions\nimport com.google.android.gms.maps3d.model.latLngAltitude\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.widget.PlaceDetailsCompactFragment\nimport com.google.android.libraries.places.widget.PlaceLoadListener\nimport com.google.android.libraries.places.widget.model.Orientation\n\n/**\n * The main activity for the 3D map demo.\n *\n * This activity demonstrates how to integrate the Places UI Kit with a 3D map view using Jetpack Compose.\n * It handles map initialization, landmark selection, and displaying place details.\n */\nclass MainActivity : AppCompatActivity(), OnMap3DViewReadyCallback {\n    private val TAG = this::class.java.simpleName\n    private var googleMap3D: GoogleMap3D? = null\n\n    private lateinit var fusedLocationClient: FusedLocationProviderClient\n    private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>\n    private val viewModel: MainViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        requestPermissionLauncher =\n            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->\n                if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true || permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {\n                    fetchLastLocation()\n                } else {\n                    Toast.makeText(this, \"Location permission denied. Showing default location.\", Toast.LENGTH_SHORT).show()\n                    moveToDefaultLocation()\n                }\n            }\n\n        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)\n\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n\n        setContent {\n            PlacesUIKit3DTheme {\n                MainScreen()\n            }\n        }\n    }\n\n    @OptIn(ExperimentalMaterial3Api::class)\n    @Composable\n    fun MainScreen() {\n        val landmarks = viewModel.landmarks\n        val selectedPlaceId by viewModel.placeId.collectAsState()\n        val scope = rememberCoroutineScope()\n        val scaffoldState = rememberBottomSheetScaffoldState(\n            bottomSheetState = rememberStandardBottomSheetState(\n                initialValue = SheetValue.PartiallyExpanded\n            )\n        )\n\n        Box(modifier = Modifier.fillMaxSize()) {\n            BottomSheetScaffold(\n                scaffoldState = scaffoldState,\n                sheetPeekHeight = 120.dp,\n                sheetContent = {\n                    LandmarkList(\n                        landmarks = landmarks,\n                        onLandmarkClick = { landmark ->\n                            viewModel.selectLandmark(landmark)\n                            flyToLandmark(landmark)\n                            scope.launch {\n                                scaffoldState.bottomSheetState.partialExpand()\n                            }\n                        },\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .fillMaxHeight(0.6f)\n                    )\n                }\n            ) { _ ->\n                // Map occupies full screen, ignoring scaffold padding\n                Box(modifier = Modifier.fillMaxSize()) {\n                    MapViewContainer()\n\n                    FloatingActionButton(\n                        onClick = { fetchLastLocation() },\n                        modifier = Modifier\n                            .align(Alignment.TopEnd)\n                            .padding(top = 48.dp, end = 16.dp)\n                    ) {\n                        Icon(Icons.Default.MyLocation, contentDescription = \"My Location\")\n                    }\n                }\n            }\n\n            // Overlay stays on top of the scaffold (outer Box)\n            if (!selectedPlaceId.isNullOrEmpty()) {\n                PlaceDetailsOverlay(\n                    placeId = selectedPlaceId!!,\n                    onDismiss = { viewModel.setSelectedPlaceId(null) },\n                    modifier = Modifier\n                        .align(Alignment.BottomCenter)\n                        // Anchor above the bottom sheet peek height (120dp + 16dp margin)\n                        .padding(bottom = 136.dp, start = 16.dp, end = 16.dp)\n                )\n            }\n        }\n    }\n\n    @Composable\n    fun MapViewContainer() {\n        val context = LocalContext.current\n        val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current\n        \n        val map3DView = remember {\n            com.google.android.gms.maps3d.Map3DView(context).apply {\n                getMap3DViewAsync(this@MainActivity)\n            }\n        }\n\n        androidx.compose.runtime.DisposableEffect(lifecycleOwner) {\n            val observer = LifecycleEventObserver { _, event ->\n                when (event) {\n                    Lifecycle.Event.ON_CREATE -> {\n                        map3DView.onCreate(null)\n                    }\n                    Lifecycle.Event.ON_RESUME -> {\n                        map3DView.onResume()\n                    }\n                    Lifecycle.Event.ON_PAUSE -> {\n                        map3DView.onPause()\n                    }\n                    Lifecycle.Event.ON_DESTROY -> {\n                        map3DView.onDestroy()\n                    }\n                    else -> {}\n                }\n            }\n            lifecycleOwner.lifecycle.addObserver(observer)\n            onDispose {\n                lifecycleOwner.lifecycle.removeObserver(observer)\n            }\n        }\n\n        AndroidView(\n            factory = { map3DView },\n            modifier = Modifier.fillMaxSize()\n        )\n    }\n\n    @Composable\n    fun PlaceDetailsOverlay(\n        placeId: String,\n        onDismiss: () -> Unit,\n        modifier: Modifier = Modifier\n    ) {\n        val containerId = remember { View.generateViewId() }\n\n        Box(\n            modifier = modifier\n                .fillMaxWidth()\n                .heightIn(max = 400.dp)\n                .background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.medium)\n        ) {\n            AndroidView(\n                factory = { ctx ->\n                    FragmentContainerView(ctx).apply {\n                        id = containerId\n                    }\n                },\n                update = { view ->\n                    val fragment = supportFragmentManager.findFragmentById(containerId) as? PlaceDetailsCompactFragment\n                    if (fragment == null) {\n                        val newFragment = PlaceDetailsCompactFragment.newInstance(\n                            PlaceDetailsCompactFragment.ALL_CONTENT,\n                            Orientation.VERTICAL,\n                            R.style.CustomizedPlaceDetailsTheme\n                        ).apply {\n                            setPlaceLoadListener(object : PlaceLoadListener {\n                                override fun onSuccess(place: Place) {\n                                    Log.d(TAG, \"Place loaded: ${place.id}\")\n                                }\n\n                                override fun onFailure(e: Exception) {\n                                    Log.e(TAG, \"Place failed to load for ID: $placeId\", e)\n                                    // Don't auto-dismiss on failure to prevent \"disappearing\" components.\n                                    // The fragment should handle its own error state.\n                                }\n                            })\n                        }\n                        supportFragmentManager.commit {\n                            replace(containerId, newFragment)\n                        }\n                        // Tag the view with the current ID and post the load\n                        view.tag = placeId\n                        Log.e(TAG, \"Loading new fragment for placeId: $placeId\")\n                        view.post { newFragment.loadWithPlaceId(placeId) }\n                    } else {\n                        // Crucially, ONLY load if the place actually changed\n                        val currentlyLoaded = view.tag as? String\n                        if (currentlyLoaded != placeId) {\n                            view.tag = placeId\n                            Log.e(TAG, \"Updating existing fragment for placeId: $placeId\")\n                            fragment.loadWithPlaceId(placeId)\n                        }\n                    }\n                },\n                modifier = Modifier.fillMaxWidth()\n            )\n\n            FloatingActionButton(\n                onClick = onDismiss,\n                modifier = Modifier\n                    .align(Alignment.TopEnd)\n                    .padding(8.dp),\n                containerColor = MaterialTheme.colorScheme.secondaryContainer\n            ) {\n                Icon(\n                    painter = androidx.compose.ui.res.painterResource(id = R.drawable.ic_close),\n                    contentDescription = \"Dismiss\"\n                )\n            }\n        }\n\n        // Clean up fragment when leaving composition\n        androidx.compose.runtime.DisposableEffect(containerId) {\n            onDispose {\n                supportFragmentManager.findFragmentById(containerId)?.let {\n                    supportFragmentManager.commit {\n                        remove(it)\n                    }\n                }\n            }\n        }\n    }\n\n    private fun flyToLandmark(landmark: Landmark) {\n        googleMap3D?.flyCameraTo(\n            flyToOptions {\n                endCamera = camera {\n                    center = landmark.location\n                    range = 1000.0\n                    tilt = 45.0\n                }.toValidCamera()\n                durationInMillis = 2000\n            }\n        )\n    }\n\n    override fun onMap3DViewReady(googleMap3D: GoogleMap3D) {\n        this.googleMap3D = googleMap3D\n        googleMap3D.setMapMode(Map3DMode.HYBRID)\n        googleMap3D.setCamera(initialCamera)\n\n        googleMap3D.setMap3DClickListener { _, placeId ->\n            Log.e(TAG, \"Map clicked: placeId=$placeId\")\n            if (!placeId.isNullOrEmpty()) {\n                viewModel.setSelectedPlaceId(placeId)\n            }\n        }\n\n        if (isLocationPermissionGranted()) {\n            fetchLastLocation()\n        } else {\n            requestLocationPermissions()\n        }\n    }\n\n    private fun isLocationPermissionGranted(): Boolean {\n        return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||\n                ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED\n    }\n\n    private fun requestLocationPermissions() {\n        requestPermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun fetchLastLocation() {\n        if (isLocationPermissionGranted()) {\n            fusedLocationClient.lastLocation.addOnSuccessListener { location ->\n                location?.let {\n                    val userLocation = latLngAltitude {\n                        latitude = it.latitude\n                        longitude = it.longitude\n                        altitude = it.altitude\n                    }\n                    googleMap3D?.flyCameraTo(\n                        flyToOptions {\n                            endCamera = camera {\n                                center = userLocation\n                                range = 5000.0\n                                tilt = 60.0\n                            }.toValidCamera()\n                            durationInMillis = 3000\n                        }\n                    )\n                } ?: moveToDefaultLocation()\n            }.addOnFailureListener { moveToDefaultLocation() }\n        }\n    }\n\n    private fun moveToDefaultLocation() {\n        googleMap3D?.flyCameraTo(flyToOptions { endCamera = initialCamera; durationInMillis = 3000 })\n    }\n\n    override fun onError(error: Exception) {\n        Log.e(TAG, \"Error loading map\", error)\n        super.onError(error)\n    }\n\n    companion object {\n        private val initialCamera: Camera = camera {\n            center = latLngAltitude {\n                latitude = 39.982129291022446\n                longitude = -105.30156359691158\n                altitude = 8148.feet.value\n            }\n            heading = 26.0\n            tilt = 67.0\n            range = 4000.0\n        }.toValidCamera()\n    }\n}\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/MainViewModel.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placesuikit3d\n\nimport androidx.lifecycle.ViewModel\nimport com.google.android.gms.maps3d.model.latLngAltitude\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\n\n/**\n * A simple ViewModel to hold the selected place ID.\n *\n * Using a ViewModel allows the state to survive configuration changes, like screen rotations,\n * ensuring the selected place isn't lost.\n */\nclass MainViewModel : ViewModel() {\n\n    /**\n     * The list of landmarks to display in the list.\n     */\n    val landmarks: List<Landmark> = listOf(\n        Landmark(\n            id = \"ChIJwd_EEkfsa4cRqy6eShKXFXY\",\n            name = \"Chautauqua Park\",\n            location = latLngAltitude {\n                latitude = 39.9989\n                longitude = -105.2828\n                altitude = 1750.0\n            }\n        ),\n        Landmark(\n            id = \"ChIJiTEGLibsa4cRepH7ZMFEcJ8\",\n            name = \"Pearl Street Mall\",\n            location = latLngAltitude {\n                latitude = 40.0177\n                longitude = -105.2819\n                altitude = 1620.0\n            }\n        ),\n        Landmark(\n            id = \"ChIJwR6cajTsa4cR2TH0qKTVKAM\",\n            name = \"University of Colorado Boulder\",\n            location = latLngAltitude {\n                latitude = 40.0076\n                longitude = -105.2659\n                altitude = 1650.0\n            }\n        ),\n        Landmark(\n            id = \"ChIJAfFnzszva4cR04sAt0lSm1g\",\n            name = \"Boulder Reservoir\",\n            location = latLngAltitude {\n                latitude = 40.0780\n                longitude = -105.2220\n                altitude = 1580.0\n            }\n        ),\n        Landmark(\n            id = \"ChIJfXOTtWbsa4cRmW07qJRB6_8\",\n            name = \"The Flatirons\",\n            location = latLngAltitude {\n                latitude = 39.9880\n                longitude = -105.2930\n                altitude = 2100.0\n            }\n        )\n    )\n\n    /**\n     * Sets the selected place ID.\n     *\n     * This function updates the `_placeId` StateFlow with the provided `placeId`.\n     * If `placeId` is null, it means no place is currently selected.\n     *\n     * @param placeId The ID of the selected place, or null if no place is selected.\n     */\n    fun setSelectedPlaceId(placeId: String?) {\n        _placeId.value = placeId\n    }\n\n    /**\n     * The ID of the place to display.\n     * This is a private mutable state flow that can be updated by the ViewModel.\n     */\n    private val _placeId = MutableStateFlow<String?>(null)\n\n    /**\n     * The unique identifier of the place to display in the Place Details view.\n     * This is a StateFlow that can be observed for changes.\n     */\n    val placeId: StateFlow<String?> = _placeId.asStateFlow()\n\n    private val _selectedLandmark = MutableStateFlow<Landmark?>(null)\n    val selectedLandmark: StateFlow<Landmark?> = _selectedLandmark.asStateFlow()\n\n    fun selectLandmark(landmark: Landmark) {\n        _selectedLandmark.value = landmark\n        setSelectedPlaceId(landmark.id)\n    }\n}"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/Maps3DPlacesApplication.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placesuikit3d\n\nimport android.app.Application\nimport android.content.pm.PackageManager\nimport android.util.Log\nimport android.widget.Toast\nimport com.google.android.libraries.places.api.Places\nimport dagger.hilt.android.HiltAndroidApp\nimport java.util.Objects\n\n@HiltAndroidApp\nclass Maps3DPlacesApplication : Application() {\n    val TAG = this::class.java.simpleName\n\n    override fun onCreate() {\n        super.onCreate()\n        checkApiKey()\n        initializePlaces()\n    }\n\n    private fun initializePlaces() {\n        val apiKey = BuildConfig.PLACES_API_KEY\n\n        if (apiKey == null || apiKey.isBlank() || apiKey == \"DEFAULT_API_KEY\") {\n            Toast.makeText(\n                this,\n                \"PLACES_API_KEY was not set in secrets.properties\",\n                Toast.LENGTH_LONG\n            ).show()\n            throw RuntimeException(\"API Key was not set in secrets.properties\")\n        }\n\n        Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)\n        Places.createClient(this)\n    }\n\n    /**\n     * Checks if the API key for Google Maps is properly configured in the application's metadata.\n     *\n     * This method retrieves the API key from the application's metadata, specifically looking for\n     * a string value associated with the key \"com.google.android.geo.maps3d.API_KEY\".\n     * The key must be present, not blank, and not set to the placeholder value \"DEFAULT_API_KEY\".\n     *\n     * If any of these checks fail, a Toast message is displayed indicating that the API key is missing or\n     * incorrectly configured, and a RuntimeException is thrown.\n     */\n    private fun checkApiKey() {\n        try {\n            val appInfo =\n                packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)\n            val bundle = Objects.requireNonNull(appInfo.metaData)\n\n            val apiKey =\n                bundle.getString(\"com.google.android.geo.maps3d.API_KEY\") // Key name is important!\n\n            if (apiKey == null || apiKey.isBlank() || apiKey == \"DEFAULT_API_KEY\") {\n                Toast.makeText(\n                    this,\n                    \"API Key was not set in secrets.properties\",\n                    Toast.LENGTH_LONG\n                ).show()\n                throw RuntimeException(\"API Key was not set in secrets.properties\")\n            }\n        } catch (e: PackageManager.NameNotFoundException) {\n            Log.e(TAG, \"Package name not found.\", e)\n            throw RuntimeException(\"Error getting package info.\", e)\n        } catch (e: NullPointerException) {\n            Log.e(TAG, \"Error accessing meta-data.\", e) // Handle the case where meta-data is completely missing.\n            throw RuntimeException(\"Error accessing meta-data in manifest\", e)\n        }\n    }\n}\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/common/ActiveMapObject.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placesuikit3d.common\n\nimport com.google.android.gms.maps3d.model.Marker\nimport com.google.android.gms.maps3d.model.Model\nimport com.google.android.gms.maps3d.model.Polygon\nimport com.google.android.gms.maps3d.model.Polyline\n\ninternal sealed class ActiveMapObject {\n  abstract fun remove()\n\n  data class ActiveMarker(val marker: Marker) : ActiveMapObject() {\n    override fun remove() {\n      marker.remove()\n    }\n  }\n\n  data class ActivePolyline(val polyline: Polyline) : ActiveMapObject() {\n    override fun remove() {\n      polyline.remove()\n    }\n  }\n\n  data class ActivePolygon(val polygon: Polygon) : ActiveMapObject() {\n    override fun remove() {\n      polygon.remove()\n    }\n  }\n\n  data class ActiveModel(val model: Model) : ActiveMapObject() {\n    override fun remove() {\n      model.remove()\n    }\n  }\n}"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/common/Map3dViewModel.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placesuikit3d.common\n\nimport android.util.Log\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.placesuikit3d.utils.CameraUpdate\nimport com.example.placesuikit3d.utils.copy\nimport com.example.placesuikit3d.utils.toCameraUpdate\nimport com.example.placesuikit3d.utils.toHeading\nimport com.example.placesuikit3d.utils.toRange\nimport com.example.placesuikit3d.utils.toRoll\nimport com.example.placesuikit3d.utils.toTilt\nimport com.example.placesuikit3d.utils.toValidCamera\nimport com.google.android.gms.maps3d.GoogleMap3D\nimport com.google.android.gms.maps3d.OnCameraChangedListener\nimport com.google.android.gms.maps3d.model.Camera\nimport com.google.android.gms.maps3d.model.Camera.DEFAULT_CAMERA\nimport com.google.android.gms.maps3d.model.CameraRestriction\nimport com.google.android.gms.maps3d.model.FlyAroundOptions\nimport com.google.android.gms.maps3d.model.FlyToOptions\nimport com.google.android.gms.maps3d.model.Map3DMode\nimport com.google.android.gms.maps3d.model.MarkerOptions\nimport com.google.android.gms.maps3d.model.Model\nimport com.google.android.gms.maps3d.model.ModelOptions\nimport com.google.android.gms.maps3d.model.PolygonOptions\nimport com.google.android.gms.maps3d.model.PolylineOptions\nimport com.google.android.gms.maps3d.model.flyAroundOptions\nimport kotlinx.coroutines.channels.BufferOverflow\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.launch\nimport kotlin.time.Duration\n\nabstract class Map3dViewModel : ViewModel() {\n  abstract val TAG: String\n\n  /**\n   * The internal state flow holding the GoogleMap3D controller instance.\n   *\n   * This flow is used internally to manage the lifecycle and access to the\n   * GoogleMap3D object provided by the MapView.\n   * It's updated via the `setGoogleMap3D` function.\n   *\n   * Consumers should use the `mapReady` flow to react to the availability of the map.\n   */\n  private var _googleMap3D = MutableStateFlow<GoogleMap3D?>(null)\n\n  private val _cameraRestriction = MutableStateFlow<CameraRestriction?>(null)\n  val cameraRestriction = _cameraRestriction.asStateFlow()\n\n  private val _mapMode = MutableStateFlow(Map3DMode.SATELLITE)\n  val mapMode = _mapMode.asStateFlow()\n\n  // --- Camera Position from Map & Pending Requests ---\n  // This is guaranteed to always be a valid camera\n  private val _currentCamera = MutableStateFlow(DEFAULT_CAMERA)\n  val currentCamera = _currentCamera.asStateFlow()\n\n  private val mapObjects = mutableMapOf<String, MapObject>()\n\n  /**\n   * A [MutableSharedFlow] that buffers [CameraUpdate] requests.\n   *\n   * This flow is used to queue camera updates requested by the ViewModel's consumers.\n   * When a new camera update is emitted to this flow, it's buffered with a replay of 1,\n   * meaning the latest update is available to new collectors. If a new update arrives\n   * before the previous one is processed, the older one is dropped (`BufferOverflow.DROP_OLDEST`).\n   *\n   * This allows the ViewModel to handle camera update requests asynchronously and\n   * ensures that only the most recent request is processed if updates occur rapidly.\n   * The actual camera update is performed within a separate coroutine that collects\n   * from this flow.\n   */\n  private val _pendingCameraUpdate = MutableSharedFlow<CameraUpdate?>(\n    replay = 1,\n    onBufferOverflow = BufferOverflow.DROP_OLDEST\n  )\n\n  private val activeMapObjects = mutableMapOf<String, ActiveMapObject>()\n\n  val mapReady = _googleMap3D.map { it != null }\n\n  init {\n    viewModelScope.launch {\n      _googleMap3D.collect { controller ->\n        stopAnimations()\n        clearObjects()\n        Log.d(TAG, \"Map3D Controller attached\")\n        if (controller != null) {\n          launch {\n            Log.d(TAG, \"Getting camera flow\")\n            getCameraFlow(controller).collect { camera ->\n              _currentCamera.value = camera\n            }\n          }\n          addMapObjects(mapObjects, controller)\n\n          // Return to the last camera position if available\n          controller.setCamera(currentCamera.value)\n\n          // Process pending camera updates\n          launch {\n            _pendingCameraUpdate\n              .filterNotNull()\n              .collect { cameraUpdate ->\n                Log.d(TAG, \"Received camera update request: $cameraUpdate\")\n                cameraUpdate(controller)\n              }\n          }\n\n          launch {\n            _mapMode.collect { mapMode ->\n              controller.setMapMode(mapMode)\n            }\n          }\n\n          launch {\n            _cameraRestriction.collect { cameraRestriction ->\n              controller.setCameraRestriction(cameraRestriction)\n            }\n          }\n        }\n      }\n    }\n  }\n\n  /**\n   * Returns a Flow that emits the current camera position whenever it changes on the GoogleMap3D.\n   *\n   * This Flow is created using `callbackFlow` to bridge the callback-based API of\n   * `OnCameraChangedListener` with Kotlin's coroutine Flows. It automatically attaches and\n   * detaches the listener when collectors subscribe and unsubscribe.\n   *\n   * The Flow emits a validated `Camera` object, ensuring that the pitch, range, and bearing\n   * are within acceptable limits using the `toValidCamera()` extension function.\n   *\n   * @param controller The GoogleMap3D instance to listen for camera changes on.\n   * @return A Flow of `Camera` objects representing the current camera position.\n   */\n  private fun getCameraFlow(controller: GoogleMap3D): Flow<Camera> {\n    // Public Flow that manages the listener lifecycle\n    return callbackFlow {\n      val cameraChangedListener = OnCameraChangedListener { cameraPosition ->\n        val newPosition = cameraPosition.toValidCamera()\n        // Send the new camera position to the flow's channel\n        trySend(newPosition)\n        // Also update the private state\n        _currentCamera.value = newPosition\n      }\n\n      // Get the current map instance (ensure it's not null before setting listener)\n      Log.d(TAG, \"Attaching CameraChangeListener\")\n      controller.setCameraChangedListener(cameraChangedListener)\n\n      // Ensure the initial camera position is emitted when the flow is collected\n      // This handles cases where the map is ready before the flow is collected\n      controller.getCamera()?.let { initial ->\n        val newPosition = initial.toValidCamera()\n        trySend(newPosition)\n        _currentCamera.value = newPosition // Also update private state on collection\n      }\n\n      // The awaitClose block runs when the collector is cancelled\n      awaitClose {\n        // Remove the listener when the flow collection stops\n        Log.d(TAG, \"Detaching CameraChangeListener\")\n        controller.setCameraChangedListener(null)\n      }\n    }\n  }\n\n  /**\n   * Adds a collection of map objects to the GoogleMap3D controller.\n   *\n   * This function iterates through a mutable map of MapObject instances and adds each one\n   * to the provided `GoogleMap3D` controller. For each successfully added object,\n   * it stores the resulting active map object in the `activeMapObjects` map\n   * for later management (like removal).\n   *\n   * @param mapObjects A mutable map where keys are object IDs (String) and values\n   *   are MapObject instances to be added to the map.\n   * @param controller The GoogleMap3D controller to which the objects will be added.\n   */\n  private fun addMapObjects(\n    mapObjects: MutableMap<String, MapObject>,\n    controller: GoogleMap3D\n  ) {\n    mapObjects.forEach { (_, mapObject) ->\n      mapObject.addToMap(controller)?.also { activeObject ->\n        activeMapObjects[mapObject.id] = activeObject\n      }\n    }\n  }\n\n  /**\n   * Sets the Map3DController instance.\n   *\n   * @param googleMap3d The GoogleMap3D instance, or null if it's being detached.\n   */\n  open fun setGoogleMap3D(googleMap3d: GoogleMap3D?) {\n    _googleMap3D.value = googleMap3d\n  }\n\n  private fun stopAnimations() {\n    Log.d(\"Map3dViewModel\", \"stopAnimations: \")\n    _googleMap3D.value?.stopCameraAnimation()\n  }\n\n  open fun releaseGoogleMap3D() {\n    _googleMap3D.value = null\n  }\n\n  /**\n   * Clears the ViewModel's internal tracking of active SDK map objects.\n   * This is called when the controller is detached or changed, as the underlying\n   * map instance those objects belonged to is no longer relevant.\n   */\n  fun clearObjects() {\n    activeMapObjects.forEach { (_, activeObject) ->\n      activeObject.remove()\n    }\n    activeMapObjects.clear()\n  }\n\n  private fun addMapObject(mapObject: MapObject) {\n    mapObjects[mapObject.id] = mapObject // No need to remove the old as the map will replace it\n    _googleMap3D.value?.also { controller ->\n      mapObject.addToMap(controller)?.also { activeObject ->\n        activeMapObjects[mapObject.id] = activeObject\n      }\n    }\n  }\n\n  fun addMarker(options: MarkerOptions) {\n    addMapObject(MapObject.Marker(options))\n  }\n\n  fun removeMapObject(id: String) {\n    mapObjects.remove(id)\n    activeMapObjects.remove(id)?.also { activeObject ->\n      activeObject.remove()\n    }\n  }\n\n  fun addPolyline(polylineOptions: PolylineOptions) {\n    addMapObject(MapObject.Polyline(polylineOptions))\n  }\n\n  fun addPolygon(polygonOptions: PolygonOptions) {\n    addMapObject(MapObject.Polygon(polygonOptions))\n  }\n\n  fun addModel(modelOptions: ModelOptions) {\n    addMapObject(MapObject.Model(modelOptions))\n  }\n\n  fun setCamera(camera: Camera) {\n    CameraUpdate.Move(camera).also { _pendingCameraUpdate.tryEmit(it) }\n  }\n\n  fun flyTo(flyToOptions: FlyToOptions) {\n    CameraUpdate.FlyTo(flyToOptions).also { _pendingCameraUpdate.tryEmit(it) }\n  }\n\n  fun flyAround(flyAroundOptions: FlyAroundOptions) {\n    CameraUpdate.FlyAround(flyAroundOptions).also { _pendingCameraUpdate.tryEmit(it) }\n  }\n\n  fun setCameraRestriction(cameraRestriction: CameraRestriction?) {\n    _cameraRestriction.value = cameraRestriction\n  }\n\n  fun setMapMode(@Map3DMode mode: Int) {\n    _mapMode.value = mode\n  }\n\n  override fun onCleared() {\n    _googleMap3D.value = null\n    super.onCleared()\n  }\n\n  open fun updateCameraAndMove(block: Camera.() -> Camera) {\n    currentCamera.value.let { camera ->\n      _pendingCameraUpdate.tryEmit(\n        CameraUpdate.Move(\n          camera.block() // .also { _currentCamera.value = it }\n        )\n      )\n    }\n  }\n\n  open fun setCameraHeading(heading: Number) {\n    updateCameraAndMove {\n      copy(heading = heading.toHeading())\n    }\n  }\n\n  open fun setCameraTilt(tilt: Number) {\n    updateCameraAndMove {\n      copy(heading = tilt.toTilt())\n    }\n  }\n\n  open fun setCamaraRange(range: Number) {\n    updateCameraAndMove {\n      copy(range = range.toRange())\n    }\n  }\n\n  open fun setCamaraRoll(roll: Number) {\n    updateCameraAndMove {\n      copy(roll = roll.toRoll())\n    }\n  }\n\n  fun flyAroundCurrentCenter(rounds: Double, duration: Duration) {\n    currentCamera.value.let { camera ->\n      flyAround(\n        flyAroundOptions {\n          center = camera\n          durationInMillis = duration.inWholeMilliseconds\n          this.rounds = rounds\n        }\n      )\n    }\n  }\n\n  fun getModel(key: String): Model? {\n    activeMapObjects[key]?.let { activeObject ->\n      if (activeObject is ActiveMapObject.ActiveModel) {\n        return activeObject.model\n      }\n    }\n    return null\n  }\n\n  fun nextMapMode() {\n    val newMapType = when (mapMode.value) {\n      Map3DMode.SATELLITE -> Map3DMode.HYBRID\n      else -> Map3DMode.SATELLITE\n    }\n    setMapMode(newMapType)\n  }\n\n  suspend fun awaitFlyTo(flyToOptions: FlyToOptions) {\n    awaitCameraUpdate(flyToOptions.toCameraUpdate())\n  }\n\n  suspend fun awaitFlyAround(flyAroundOptions: FlyAroundOptions) {\n    awaitCameraUpdate(flyAroundOptions.toCameraUpdate())\n  }\n\n  suspend fun awaitCameraUpdate(cameraUpdate: CameraUpdate) {\n    _googleMap3D.value?.let { controller ->\n      com.example.placesuikit3d.utils.awaitCameraUpdate(controller, cameraUpdate)\n    }\n  }\n}\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/common/MapObject.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placesuikit3d.common\n\nimport com.google.android.gms.maps3d.GoogleMap3D\nimport com.google.android.gms.maps3d.model.MarkerOptions\nimport com.google.android.gms.maps3d.model.ModelOptions\nimport com.google.android.gms.maps3d.model.PolygonOptions\nimport com.google.android.gms.maps3d.model.PolylineOptions\n\nsealed class MapObject {\n  internal abstract fun addToMap(controller: GoogleMap3D): ActiveMapObject?\n  abstract val id: String\n\n  data class Marker(val options: MarkerOptions) : MapObject() {\n    override fun addToMap(controller: GoogleMap3D): ActiveMapObject? {\n      return controller.addMarker(options)?.let { marker ->\n          ActiveMapObject.ActiveMarker(marker)\n      }\n    }\n\n    override val id: String\n      get() = options.id\n  }\n\n  data class Polyline(val options: PolylineOptions) : MapObject() {\n    override fun addToMap(controller: GoogleMap3D): ActiveMapObject {\n      return ActiveMapObject.ActivePolyline(controller.addPolyline(options))\n    }\n\n    override val id: String\n      get() = options.id\n  }\n\n  data class Polygon(val options: PolygonOptions) : MapObject() {\n    override fun addToMap(controller: GoogleMap3D): ActiveMapObject {\n      return ActiveMapObject.ActivePolygon(controller.addPolygon(options))\n    }\n\n    override val id: String\n      get() = options.id\n  }\n\n  data class Model(val options: ModelOptions) : MapObject() {\n    override fun addToMap(controller: GoogleMap3D): ActiveMapObject {\n      return ActiveMapObject.ActiveModel(controller.addModel(options))\n    }\n\n    override val id: String\n      get() = options.id\n  }\n}"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/ui/theme/Color.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placesuikit3d.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": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/ui/theme/Theme.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placesuikit3d.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 PlacesUIKit3DTheme(\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": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/ui/theme/Type.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placesuikit3d.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": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/utils/CameraUpdate.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placesuikit3d.utils\n\nimport com.google.android.gms.maps3d.GoogleMap3D\nimport com.google.android.gms.maps3d.OnCameraAnimationEndListener\nimport com.google.android.gms.maps3d.model.Camera\nimport com.google.android.gms.maps3d.model.FlyAroundOptions\nimport com.google.android.gms.maps3d.model.FlyToOptions\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlin.coroutines.resume\n\n/**\n * Represents an update to the camera of a [GoogleMap3D].\n *\n * This sealed class provides different ways to update the camera, such as flying to a specific location,\n * flying around a point, or simply moving the camera to a new position.\n *\n * The main advantage is to allow creation of the [awaitCameraUpdate] method.\n *\n * Each subclass of [CameraUpdate] defines how the camera should be updated through its `invoke` method.\n *\n * Subclasses:\n * - [FlyTo]: Represents a camera fly-to animation.\n * - [FlyAround]: Represents a camera fly-around animation.\n * - [Move]: Represents a direct camera move without animation.\n */\nsealed class CameraUpdate {\n    abstract operator fun invoke(controller: GoogleMap3D)\n\n    data class FlyTo(val options: FlyToOptions) : CameraUpdate() {\n        override fun invoke(controller: GoogleMap3D) {\n            controller.flyCameraTo(options)\n        }\n    }\n\n    data class FlyAround(val options: FlyAroundOptions) : CameraUpdate() {\n        override fun invoke(controller: GoogleMap3D) {\n            controller.flyCameraAround(options)\n        }\n    }\n\n    data class Move(val camera: Camera) : CameraUpdate() {\n        override fun invoke(controller: GoogleMap3D) {\n            controller.setCamera(camera)\n        }\n    }\n}\n\nfun FlyToOptions.toCameraUpdate(): CameraUpdate {\n    return CameraUpdate.FlyTo(this.toValidFlyToOptions())\n}\n\nfun FlyAroundOptions.toCameraUpdate(): CameraUpdate {\n    return CameraUpdate.FlyAround(this.toValidFlyAroundOptions())\n}\n\nfun FlyToOptions.toValidFlyToOptions(): FlyToOptions {\n    return this.copy(\n        endCamera = this.endCamera.toValidCamera()\n    )\n}\n\nfun FlyAroundOptions.toValidFlyAroundOptions(): FlyAroundOptions {\n    return this.copy(\n        center = this.center.toValidCamera()\n    )\n}\n\n/**\n * Suspends the coroutine until the camera update animation is finished.\n *\n * If the [cameraUpdate] is a [CameraUpdate.Move], it will be applied immediately without waiting.\n *\n * Otherwise, it will wait for the camera animation to finish, then it will resume the coroutine.\n *\n * You can pass in an existing [cameraChangedListener] that will be invoked when the camera\n * animation finishes and also will be restored afterwards.\n *\n * @param controller The [GoogleMap3D] instance to apply the camera update to.\n * @param cameraUpdate The [CameraUpdate] to apply.\n * @param cameraChangedListener An optional existing listener to invoke and restore\n */\nsuspend fun awaitCameraUpdate(\n    controller: GoogleMap3D,\n    cameraUpdate: CameraUpdate,\n    cameraChangedListener: OnCameraAnimationEndListener? = null\n) = suspendCancellableCoroutine { continuation ->\n    // No need to wait if the update is a move\n    if (cameraUpdate is CameraUpdate.Move) {\n        cameraUpdate.invoke(controller)\n        return@suspendCancellableCoroutine\n    }\n\n    // If the coroutine is canceled, stop the camera animation as well.\n    continuation.invokeOnCancellation {\n        controller.stopCameraAnimation()\n    }\n\n    controller.setCameraAnimationEndListener {\n        cameraChangedListener?.onCameraAnimationEnd()\n        controller.setCameraAnimationEndListener(cameraChangedListener)\n        if (continuation.isActive) {\n            continuation.resume(Unit)\n        }\n    }\n\n    cameraUpdate.invoke(controller)\n}\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/utils/Units.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placesuikit3d.utils\n\n\nimport android.content.res.Resources\nimport androidx.annotation.StringRes\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.runtime.Stable\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.ui.res.stringResource\nimport com.example.placesuikit3d.R\n\nconst val METERS_PER_FOOT = 3.28084\nconst val METERS_PER_KILOMETER = 1_000\nconst val FEET_PER_METER = 1 / METERS_PER_FOOT\nconst val FEET_PER_MILE = 5_280\nconst val MILES_PER_METER = 0.000621371\n\n/** A value class to wrap a value representing a measurement in meters. */\n@Immutable\n@JvmInline\nvalue class Meters(val value: Double) : Comparable<Meters> {\n    override fun compareTo(other: Meters) = value.compareTo(other.value)\n\n    operator fun minus(other: Meters) = Meters(value = this.value - other.value)\n}\n\n/** Create a Meters class from a [Number] */\n@Stable\ninline val Number.meters: Meters\n    get() = Meters(value = this.toDouble())\n\n/** Create a Meters class from a [Number] */\n@Stable\ninline val Number.m: Meters\n    get() = Meters(value = this.toDouble())\n\n/** Create a Meters class from a [Number] of kilometers */\n@Stable\ninline val Number.km: Meters\n    get() = Meters(value = this.toDouble() * METERS_PER_KILOMETER)\n\n/** Create a Meters class from a [Number] of feet */\n@Stable\ninline val Number.feet: Meters\n    get() = Meters(value = this.toDouble() * FEET_PER_METER)\n\n/** Create a Meters class from a [Number] of miles */\n@Stable\ninline val Number.miles: Meters\n    get() = Meters(value = this.toDouble() / MILES_PER_METER)\n\n/** Gets the number of equivalent feet from a meters value class */\n@Stable\ninline val Meters.toFeet: Double\n    get() = value * METERS_PER_FOOT\n\n/** Gets the value of a meters class as a Double */\n@Stable\ninline val Meters.toMeters: Double\n    get() = value\n\n/** Gets the number of equivalent kilometers from a meters value class */\n@Stable\ninline val Meters.toKilometers: Double\n    get() = value / METERS_PER_KILOMETER\n\n/** Gets the number of equivalent kilometers from a meters value class */\n@Stable\ninline val Meters.toMiles: Double\n    get() = (value * MILES_PER_METER)\n\n@Stable\noperator fun Meters.plus(other: Meters) = Meters(value = this.value + other.value)\n\n/**\n * A data class representing a value with a string resource ID for its units template.\n *\n * @property value: The numerical value.\n * @property unitsTemplate: The string resource ID for the units.\n */\ndata class ValueWithUnitsTemplate(val value: Double, @StringRes val unitsTemplate: Int)\n\n/** Abstract base class for all units converters. */\nabstract class UnitsConverter {\n    abstract fun toDistanceUnits(meters: Meters): ValueWithUnitsTemplate\n\n    abstract fun toElevationUnits(meters: Meters): ValueWithUnitsTemplate\n\n    @Composable\n    fun toDistanceString(meters: Meters): String {\n        val (value, resourceId) = toDistanceUnits(meters = meters)\n        return stringResource(id = resourceId, value)\n    }\n\n    fun toDistanceString(resources: Resources, meters: Meters): String {\n        val (value, resourceId) = toDistanceUnits(meters = meters)\n        return resources.getString(resourceId, value)\n    }\n\n    @Composable\n    fun toElevationString(meters: Meters): String {\n        val (value, resourceId) = toElevationUnits(meters = meters)\n        return stringResource(id = resourceId, value)\n    }\n}\n\n/**\n * Returns the appropriate [UnitsConverter] based on the given country code.\n *\n * @param countryCode The country code to determine the units converter for.\n * @return The appropriate [UnitsConverter] for the specified country code.\n */\nfun getUnitsConverter(countryCode: String?): UnitsConverter {\n    // TODO: other counties that use imperial units for distances?\n    return if (countryCode == \"US\") {\n        ImperialUnitsConverter\n    } else {\n        MetricUnitsConverter\n    }\n}\n\n/** Class to render measurements in imperial units. */\nobject ImperialUnitsConverter : UnitsConverter() {\n    override fun toDistanceUnits(meters: Meters): ValueWithUnitsTemplate {\n        return if (meters < 0.25.miles) {\n            ValueWithUnitsTemplate(meters.toFeet, R.string.in_feet)\n        } else {\n            ValueWithUnitsTemplate(meters.toMiles, R.string.in_miles)\n        }\n    }\n\n    override fun toElevationUnits(meters: Meters): ValueWithUnitsTemplate {\n        return ValueWithUnitsTemplate(meters.toFeet, R.string.in_feet)\n    }\n}\n\n/** Class to render measurements in metric units. */\nobject MetricUnitsConverter : UnitsConverter() {\n    override fun toDistanceUnits(meters: Meters): ValueWithUnitsTemplate {\n        return if (meters < 1000.meters) {\n            ValueWithUnitsTemplate(meters.toMeters, R.string.in_meters)\n        } else {\n            ValueWithUnitsTemplate(meters.toKilometers, R.string.in_kilometers)\n        }\n    }\n\n    override fun toElevationUnits(meters: Meters): ValueWithUnitsTemplate {\n        return ValueWithUnitsTemplate(meters.toMeters, R.string.in_meters)\n    }\n}\n\n/** A composition local that provides a [UnitsConverter] instance. */\nval LocalUnitsConverter = compositionLocalOf<UnitsConverter> { MetricUnitsConverter }\n\n/** Creates a string to show the distance formatted with units */\n@Composable\nfun Meters.toDistanceString(): String {\n    return LocalUnitsConverter.current.toDistanceString(this)\n}\n\n@Composable\nfun Meters.toElevationString(): String {\n    return LocalUnitsConverter.current.toElevationString(this)\n}\n\noperator fun Meters.plus(value: Number) = Meters(this.value + value.toDouble())\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/java/com/example/placesuikit3d/utils/Utilities.kt",
    "content": "// Copyright 2025 Google LLC\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.example.placesuikit3d.utils\n\nimport com.google.android.gms.maps3d.model.Camera\nimport com.google.android.gms.maps3d.model.FlyAroundOptions\nimport com.google.android.gms.maps3d.model.FlyToOptions\nimport com.google.android.gms.maps3d.model.LatLngAltitude\nimport com.google.android.gms.maps3d.model.camera\nimport com.google.android.gms.maps3d.model.flyAroundOptions\nimport com.google.android.gms.maps3d.model.flyToOptions\nimport com.google.android.gms.maps3d.model.latLngAltitude\nimport java.util.Locale\nimport kotlin.math.floor\n\nval headingRange = 0.0..360.0\nval tiltRange = 0.0..90.0\nval rangeRange = 0.0..63170000.0\nval rollRange = -360.0..360.0\n\nval latitudeRange = -90.0..90.0\nval longitudeRange = -180.0..180.0\nval altitudeRange = 0.0..LatLngAltitude.MAX_ALTITUDE_METERS\n\nconst val DEFAULT_HEADING = 0.0\nconst val DEFAULT_TILT = 60.0\nconst val DEFAULT_RANGE = 1500.0\nconst val DEFAULT_ROLL = 0.0\n\n/**\n * Converts a nullable Camera object into a valid, non-null Camera object.\n * If the input is null, returns the DEFAULT_CAMERA configuration.\n * If the input is non-null, validates its components (center, heading, tilt, roll, range)\n * using helper functions (toValidLocation, toHeading, toTilt, toRoll, toRange).\n *\n * @receiver The nullable Camera object to validate.\n * @return A valid, non-null Camera object.\n */\nfun Camera?.toValidCamera(): Camera {\n    // Use elvis operator for concise null handling\n    val source = this ?: return Camera.DEFAULT_CAMERA // Return default camera if source is null\n\n    // If source is not null, validate its components\n    return camera {\n        // Validate center using the provided toValidLocation function\n        center = source.center.toValidLocation()\n        // Validate orientation and range using the existing to...() functions\n        heading = source.heading.toHeading()\n        tilt = source.tilt.toTilt()\n        roll = source.roll.toRoll()\n        range = source.range.toRange()\n    }\n}\n\n/**\n * Coerces the latitude, longitude, and altitude of a LatLngAltitude object\n * to be within their valid ranges. Longitude is clamped, not wrapped here.\n *\n * @receiver The LatLngAltitude to validate.\n * @return A new LatLngAltitude object with validated components.\n */\nfun LatLngAltitude.toValidLocation(): LatLngAltitude {\n    val objectToCopy = this\n    return latLngAltitude {\n        // Coerce latitude within -90.0 to 90.0\n        latitude = objectToCopy.latitude.coerceIn(latitudeRange)\n        // Coerce longitude within -180.0 to 180.0 (Note: wrapping might be preferred sometimes)\n        longitude = objectToCopy.longitude.coerceIn(longitudeRange)\n        // Coerce altitude within 0.0 to MAX_ALTITUDE_METERS\n        altitude = objectToCopy.altitude.coerceIn(altitudeRange)\n    }\n}\n\n/**\n * Converts a Number? to a valid heading value (0.0 to 360.0).\n * Returns 0.0 if the input is null.\n * Uses wrapIn to ensure the value is within the headingRange.\n *\n * @receiver The Number? to convert.\n * @return The heading value as a Double within [0.0, 360.0).\n */\nfun Number?.toHeading(): Double =\n    this?.toDouble()?.wrapIn(headingRange.start, headingRange.endInclusive) ?: DEFAULT_HEADING\n\n/**\n * Converts a Number? to a valid tilt value (0.0 to 90.0).\n * Returns 0.0 if the input is null.\n * Clamps the value to the tiltRange, as tilt doesn't typically wrap.\n *\n * @receiver The Number? to convert.\n * @return The tilt value as a Double clamped within [0.0, 90.0].\n */\nfun Number?.toTilt(): Double = this?.toDouble()?.coerceIn(tiltRange) ?: DEFAULT_TILT\n\n/**\n * Converts a Number? to a valid roll value (-360.0 to 360.0 or often -180..180).\n * Returns 0.0 if the input is null.\n * Uses wrapIn to ensure the value is within the rollRange.\n * Consider using -180..180 range and wrapIn(lower, upper) for standard roll representation.\n *\n * @receiver The Number? to convert.\n * @return The roll value as a Double within the defined rollRange.\n */\nfun Number?.toRoll(): Double = this?.toDouble()?.wrapIn(rollRange) ?: DEFAULT_ROLL\n\n/**\n * Converts a Number? to a valid range value (0.0 to ~63,170,000.0).\n * Returns 0.0 if the input is null.\n * Clamps the value to the rangeRange, as range/distance doesn't wrap.\n *\n * @receiver The Number? to convert.\n * @return The range value as a Double clamped within the defined rangeRange.\n */\nfun Number?.toRange(): Double = this?.toDouble()?.coerceIn(rangeRange) ?: DEFAULT_RANGE\n\n// Assumes we are close to the range\nfun Double.wrapIn(range: ClosedFloatingPointRange<Double>): Double {\n    var answer = this\n    val delta = range.endInclusive - range.start\n    while (answer > range.endInclusive) {\n        answer -= delta\n    }\n    while (answer < range.start) {\n        answer += delta\n    }\n\n    return answer\n}\n\n/**\n * Wraps a Float value within a specified range.\n * If the value is outside the range, it is adjusted by repeatedly adding or subtracting\n * the range's span (delta) until it falls within the range.\n *\n * @param range The ClosedFloatingPointRange within which to wrap the value.\n * @return The wrapped Float value, guaranteed to be within the specified range.\n */\nfun Float.wrapIn(range: ClosedFloatingPointRange<Float>): Float {\n    var answer = this\n    val delta = range.endInclusive - range.start\n    while (answer > range.endInclusive) {\n        answer -= delta\n    }\n    while (answer < range.start) {\n        answer += delta\n    }\n\n    return answer\n}\n\n/**\n * Wraps a Double value within the specified range [lower, upper).\n * This method ensures that the returned value always falls within the specified range.\n * If the value is outside the range, it will be \"wrapped around\" to fit within the range.\n * For example, if the range is [0.0, 360.0) and the input is 370.0, the output will be 10.0.\n * If the range is [0.0, 360.0) and the input is -10.0, the output will be 350.0.\n *\n * @param lower The lower bound of the range (inclusive).\n * @param upper The upper bound of the range (exclusive).\n * @return The wrapped value within the range [lower, upper).\n * @throws IllegalArgumentException if the upper bound is not greater than the lower bound.\n */\nfun Double.wrapIn(lower: Double, upper: Double): Double {\n    val range = upper - lower\n    if (range <= 0) {\n        throw IllegalArgumentException(\"Upper bound must be greater than lower bound\")\n    }\n    val offset = this - lower\n    return lower + (offset - floor(offset / range) * range)\n}\n\n/**\n * Extension function on Number to get the nearest compass direction string\n * from a given heading in degrees.\n *\n * 0 degrees is North, 90 is East, 180 is South, 270 is West.\n * Handles headings outside the standard 0-360 range (e.g., -90 or 450 degrees).\n *\n * @return A string representing the nearest compass direction (e.g., \"N\", \"NNE\", \"NE\").\n */\nfun Number.toCompassDirection(): String {\n    val directions = listOf(\n        \"N\", \"NNE\", \"NE\", \"ENE\",\n        \"E\", \"ESE\", \"SE\", \"SSE\",\n        \"S\", \"SSW\", \"SW\", \"WSW\",\n        \"W\", \"WNW\", \"NW\", \"NNW\"\n    )\n\n    val headingDegrees = this.toDouble()\n\n    // Normalize heading to 0-359.99... degrees\n    val normalizedHeading = (headingDegrees % 360.0 + 360.0) % 360.0\n\n    // Each of the 16 directions covers an arc of 360/16 = 22.5 degrees.\n    // We add half of this (11.25) to the normalized heading before dividing\n    // to correctly align with the center of each compass arc.\n    val segment = 22.5\n    val index = floor((normalizedHeading + (segment / 2)) / segment).toInt() % directions.size\n\n    return directions[index]\n}\n\n/**\n * Creates a new [Camera] object by copying the current [Camera] and optionally overriding\n * its center, heading, tilt, range, and roll properties.\n *\n * @param center The new center [LatLngAltitude] to use, or null to keep the current center.\n * @param heading The new heading (bearing) to use, or null to keep the current heading.\n * @param tilt The new tilt (pitch) to use, or null to keep the current tilt.\n * @param range The new range (distance from the center) to use, or null to keep the current range.\n * @param roll The new roll to use, or null to keep the current roll.\n * @return A new [Camera] object with the specified properties updated.\n */\nfun Camera.copy(\n    center: LatLngAltitude? = null,\n    heading: Double? = null,\n    tilt: Double? = null,\n    range: Double? = null,\n    roll: Double? = null,\n): Camera {\n    val objectToCopy = this\n    return camera {\n        this.center = center ?: objectToCopy.center\n        this.heading = heading ?: objectToCopy.heading\n        this.tilt = tilt ?: objectToCopy.tilt\n        this.range = range ?: objectToCopy.range\n        this.roll = roll ?: objectToCopy.roll\n    }\n}\n\nfun FlyAroundOptions.copy(\n    center: Camera? = null,\n    durationInMillis: Long? = null,\n    rounds: Double? = null,\n) : FlyAroundOptions {\n    val objectToCopy = this\n\n    return flyAroundOptions {\n        this.center = (center ?: objectToCopy.center)\n        this.durationInMillis = durationInMillis ?: objectToCopy.durationInMillis\n        this.rounds = rounds ?: objectToCopy.rounds\n    }\n}\n\nfun FlyToOptions.copy(\n    endCamera: Camera? = null,\n    durationInMillis: Long? = null,\n) : FlyToOptions {\n    val objectToCopy = this\n\n    return flyToOptions {\n        this.endCamera = (endCamera ?: objectToCopy.endCamera)\n        this.durationInMillis = durationInMillis ?: objectToCopy.durationInMillis\n    }\n}\n\n/**\n * Converts a [Camera] object to a formatted string representation.\n *\n * This function takes a [Camera] object, validates it using [toValidCamera], and then\n * constructs a multi-line string that represents the camera's properties in a human-readable\n * format. The string includes the camera's center (latitude, longitude, altitude),\n * heading, tilt, and range.\n *\n * The latitude, longitude, altitude, heading, tilt, and range are formatted to specific\n * decimal places for readability (6, 6, 1, 0, 0, 0 respectively).\n *\n * The output string is designed to be easily copied and pasted directly into code to recreate\n * a [Camera] object with the same parameters. This is especially useful for quickly positioning\n * the camera to a specific view.\n *\n * Example output:\n * ```\n * camera {\n *     center = latLngAltitude {\n *         latitude = 34.052235\n *         longitude = -118.243685\n *         altitude = 100.0\n *     }\n *     heading = 90\n *     tilt = 45\n *     range = 5000\n * }\n * ```\n *\n * @receiver The [Camera] object to convert.\n * @return A string representation of the [Camera] object, suitable for pasting into source code.\n */\nfun Camera.toCameraString(): String {\n    val camera = this.toValidCamera()\n    return \"\"\"\n        camera {\n            center = latLngAltitude {\n                latitude = ${camera.center.latitude.format(6)}\n                longitude = ${camera.center.longitude.format(6)}\n                altitude = ${camera.center.altitude.format(1)}\n            }\n            heading = ${camera.heading.format(0)}\n            tilt = ${camera.tilt.format(0)}\n            range = ${camera.range.format(0)}\n        }\"\"\".trimIndent()\n}\n\n/**\n * Formats a nullable Double to a string with a specified number of decimal places.\n *\n * If the Double is null, returns \"null\".\n * If decimalPlaces is 0, it formats the number with no decimal places and appends \".0\".\n * If decimalPlaces is greater than 0, it formats the number with the specified number of decimal places.\n *\n * Note, this is intended for logging and debugging not for display to the user.\n *\n * @receiver The nullable Double to format.\n * @param decimalPlaces The number of decimal places to include in the formatted string.\n * @return The formatted string representation of the Double, or \"null\" if the input is null.\n */\ninternal fun Double?.format(decimalPlaces: Int): String {\n    if (this == null) return \"null\"\n\n    return if (decimalPlaces == 0) {\n        String.format(Locale.US, \"%.0f.0\", this)\n    } else {\n        String.format(Locale.US, \"%.${decimalPlaces}f\", this)\n    }\n}\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/drawable/close_button_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <solid android:color=\"#80000000\" /> <!-- Semi-transparent black -->\n</shape>"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/drawable/ic_close.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\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</vector>"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlacesUIKit3D/src/main/res/drawable/ic_launcher_foreground.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\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=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/drawable/ic_my_location.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z\"/>\n</vector>\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/drawable/loader_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"#80000000\" /> <!-- semi-transparent black -->\n    <corners android:radius=\"8dp\" />\n</shape>\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/drawable/outline_my_location_24.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:viewportHeight=\"960\" android:viewportWidth=\"960\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M440,918L440,838Q315,824 225.5,734.5Q136,645 122,520L42,520L42,440L122,440Q136,315 225.5,225.5Q315,136 440,122L440,42L520,42L520,122Q645,136 734.5,225.5Q824,315 838,440L918,440L918,520L838,520Q824,645 734.5,734.5Q645,824 520,838L520,918L440,918ZM480,760Q596,760 678,678Q760,596 760,480Q760,364 678,282Q596,200 480,200Q364,200 282,282Q200,364 200,480Q200,596 282,678Q364,760 480,760ZM480,640Q414,640 367,593Q320,546 320,480Q320,414 367,367Q414,320 480,320Q546,320 593,367Q640,414 640,480Q640,546 593,593Q546,640 480,640ZM480,560Q513,560 536.5,536.5Q560,513 560,480Q560,447 536.5,423.5Q513,400 480,400Q447,400 423.5,423.5Q400,447 400,480Q400,513 423.5,536.5Q447,560 480,560ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z\"/>\n    \n</vector>\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/font/custom_font.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<!--\nThis file defines a custom font family using Android's Downloadable Fonts feature.\nInstead of bundling the font in the APK, this tells the system to download\nRoboto Mono from the Google Fonts provider when needed.\n-->\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    app:fontProviderAuthority=\"com.google.android.gms.fonts\"\n    app:fontProviderPackage=\"com.google.android.gms\"\n    app:fontProviderQuery=\"name=Roboto Mono&amp;weight=400\"\n    app:fontProviderCerts=\"@array/com_google_android_gms_fonts_certs\">\n</font-family>"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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:map3d=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:id=\"@+id/map_container\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <com.google.android.gms.maps3d.Map3DView\n        android:id=\"@+id/map3dView\"\n        map3d:mode=\"hybrid\"\n        map3d:centerLat=\"39.982129291022446\"\n        map3d:centerLng=\"-105.30156359691158\"\n        map3d:centerAlt=\"2483\"\n        map3d:heading=\"270\"\n        map3d:tilt=\"75\"\n        map3d:range=\"6000\"\n        map3d:roll=\"0\"\n        map3d:minAltitude=\"0\"\n        map3d:maxAltitude=\"1000000\"\n        map3d:minHeading=\"0\"\n        map3d:maxHeading=\"360\"\n        map3d:minTilt=\"0\"\n        map3d:maxTilt=\"90\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/my_location_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:src=\"@drawable/outline_my_location_24\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        android:contentDescription=\"@string/my_location\" />\n\n\n    <!-- Wrapper to hold the fragment, the dismiss button, AND the loader -->\n    <FrameLayout\n        android:id=\"@+id/place_details_wrapper\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginBottom=\"26dp\"\n        android:elevation=\"4dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        tools:visibility=\"gone\">\n\n        <!-- The container for the fragment -->\n        <FrameLayout\n            android:id=\"@+id/place_details_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <!-- The loader, shown on top of the empty container -->\n        <LinearLayout\n            android:id=\"@+id/loading_container\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:background=\"@drawable/loader_background\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\"\n            android:padding=\"16dp\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\">\n\n            <ProgressBar\n                android:id=\"@+id/loading_indicator\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"8dp\"\n                android:text=\"@string/loading\"\n                android:textColor=\"@android:color/white\" />\n        </LinearLayout>\n\n        <!-- The dismiss button with a background. It's hidden initially. -->\n        <ImageButton\n            android:id=\"@+id/dismiss_button\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_gravity=\"top|end\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:background=\"@drawable/close_button_background\"\n            android:contentDescription=\"@string/dismiss_button_content_description\"\n            android:padding=\"12dp\"\n            android:visibility=\"visible\"\n            app:srcCompat=\"@drawable/ic_close\"\n            app:tint=\"@android:color/white\"\n            tools:visibility=\"visible\" />\n    </FrameLayout>\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/mipmap-anydpi/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlacesUIKit3D/src/main/res/mipmap-anydpi/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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": "PlacesUIKit3D/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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    <!-- \"Synthwave\" Avant-Garde Theme Colors -->\n    <color name=\"synthwave_surface\">#1A0A2D</color> <!-- Deep purple background -->\n    <color name=\"synthwave_primary\">#E0218A</color> <!-- Vibrant magenta for primary actions -->\n    <color name=\"synthwave_on_surface\">#F0F8FF</color> <!-- Alice blue for primary text, high contrast -->\n    <color name=\"synthwave_on_surface_variant\">#9E8BBE</color> <!-- Muted lilac for secondary text -->\n    <color name=\"synthwave_outline\">#00E5FF</color> <!-- Bright cyan for outlines, creating a \"neon glow\" -->\n    <color name=\"synthwave_positive\">#00E5FF</color> <!-- Cyan for \"Open\" status -->\n    <color name=\"synthwave_negative\">#FF007F</color> <!-- Hot pink for \"Closed\" status -->\n</resources>\n"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<resources>\n    <string name=\"app_name\">Places UI Kit 3D</string>\n\n    <!--\n    Distance in feet, formatted to 0 decimal places.\n    For example, 1234.56 feet will be formatted as \"1,235 ft\".\n    -->\n    <string name=\"in_feet\" translatable=\"false\">%1$,.0f ft</string>\n\n    <!--\n    Distance in miles, formatted to 1 decimal place.\n    For example, 1.2345 miles will be formatted as \"1.2 miles\".\n    -->\n    <string name=\"in_miles\" translatable=\"false\">%1$,.1f miles</string>\n\n    <!--\n    Distance in meters, formatted to 0 decimal places.\n    For example, 1234.56 meters will be formatted as \"1,235 m\".\n    -->\n    <string name=\"in_meters\" translatable=\"false\">%1$,.0f m</string>\n\n    <!--\n    Distance in kilometers, formatted to 1 decimal place.\n    For example, 1.2345 kilometers will be formatted as \"1.2 km\".\n    -->\n    <string name=\"in_kilometers\" translatable=\"false\">%1$,.1f km</string>\n\n    <string name=\"dismiss_button_content_description\">Dismiss place details</string>\n    <string name=\"loading\">Loading…</string>\n    <string name=\"my_location\">My Location</string>\n\n</resources>"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/values/themes.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<resources>\n    <style name=\"Base.Theme.PlacesUIKit3D\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n        <item name=\"android:windowLayoutInDisplayCutoutMode\">shortEdges</item>\n    </style>\n    <style name=\"Theme.PlacesUIKit3D\" parent=\"Base.Theme.PlacesUIKit3D\" />\n\n    <!--\n    A custom text appearance using our monospaced font.\n    -->\n    <style name=\"app_text_appearence_mono\" parent=\"TextAppearance.MaterialComponents.Body2\">\n        <item name=\"android:fontFamily\">@font/custom_font</item>\n        <item name=\"android:textColor\">@color/synthwave_on_surface_variant</item>\n    </style>\n\n    <style name=\"app_text_appearence_mono_label\" parent=\"TextAppearance.MaterialComponents.Body1\">\n        <item name=\"android:fontFamily\">@font/custom_font</item>\n        <item name=\"android:textColor\">@color/synthwave_on_surface</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n\n    <!--\n    A custom \"Synthwave\" theme for the PlaceDetailsCompactFragment.\n    This theme overrides multiple attributes to create a unique, retro-futuristic look.\n    -->\n    <style name=\"CustomizedPlaceDetailsTheme\" parent=\"PlacesMaterialTheme\">\n        <!-- Core Colors -->\n        <item name=\"placesColorSurface\">@color/synthwave_surface</item>\n        <item name=\"placesColorPrimary\">@color/synthwave_primary</item>\n        <item name=\"placesColorOnSurface\">@color/synthwave_on_surface</item>\n        <item name=\"placesColorOnSurfaceVariant\">@color/synthwave_on_surface_variant</item>\n        <item name=\"placesColorOutlineDecorative\">@color/synthwave_outline</item>\n\n        <!-- Status Colors -->\n        <item name=\"placesColorPositive\">@color/synthwave_positive</item>\n        <item name=\"placesColorNegative\">@color/synthwave_negative</item>\n\n        <!-- Shape -->\n        <item name=\"placesCornerRadius\">8dp</item>\n\n        <!-- Typography -->\n        <item name=\"placesTextAppearanceBodySmall\">@style/app_text_appearence_mono</item>\n        <item name=\"placesTextAppearanceBodyMedium\">@style/app_text_appearence_mono</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "PlacesUIKit3D/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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   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": "PlacesUIKit3D/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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   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": "README.md",
    "content": "Google Places SDK for Android Demos\n====================================\n![GitHub contributors](https://img.shields.io/github/contributors/googlemaps/android-places-demos)\n![Apache-2.0](https://img.shields.io/badge/license-Apache-blue)\n[![Discord](https://img.shields.io/discord/676948200904589322)](https://discord.gg/hYsWbmk)\n\nThis repo contains several standalone applications that demonstrate use of the [Google Places SDK for Android](https://developers.google.com/places/android-sdk/):\n\n1. **[demo-java](demo-java):** Basic Java application demonstrating core Places SDK capabilities including Place Autocomplete (Intent and Programmatic), Place Details, and Current Place. \n2. **[demo-kotlin](demo-kotlin):** The Kotlin equivalent of the standard Java demo, showing idiomatic usage of the base SDK.\n3. **[kotlin-demos](kotlin-demos):** Demonstrates the use of the `android-places-ktx` library, highlighting Kotlin Coroutines support and modernized API responses for the Places SDK.\n4. **[PlaceDetailsCompose](PlaceDetailsCompose):** Shows how to build modern, interactive Place Details UI screens leveraging Jetpack Compose and the New Places API.\n5. **[PlaceDetailsUIKit](PlaceDetailsUIKit):** Shows how to build immersive Place Details UI screens using modern Android Views (UIKit) and the New Places API.\n6. **[PlacesUIKit3D](PlacesUIKit3D):** Blends the Places API with the Photorealistic 3D Maps SDK, providing an immersive location-viewing experience with dynamic camera fly-alongs.\n\nAdditionally, the **[snippets](snippets)** app contains code snippets used across the official [Google Places SDK developer documentation](https://developers.google.com/places/android-sdk).\n\nGetting Started\n---------------\n\nThese demos use the Gradle build system.\n\nFirst download the demos by cloning this repository or downloading an archived snapshot. (See the options on the right hand side.)\n\nIn Android Studio, use \"Open an existing Android Studio project\", and select the root directory (`android-places-demos`). This will load all the demo modules at once.\n\nAlternatively use the `./gradlew assembleDebug` command from the root directory to build all projects simultaneously.\n\nThe demos require that you provide your own API keys. The project enforces the presence of required keys before the build can even start to prevent runtime crashes.\n\n1. [Get an API Key](https://developers.google.com/places/android-sdk/get-api-key) with the **Places API (New)** and **Maps SDK for Android** enabled.\n2. In the root directory, create a `secrets.properties` file (this is git-ignored to prevent accidental commits).\n3. Add your keys. See `local.defaults.properties` for the complete list of required and secondary optional keys. At minimum, you must add the required keys:\n   ```properties\n   PLACES_API_KEY=AIza...\n   MAPS_API_KEY=AIza...\n   ```\n   **Optional Keys:**\n   There are also optional keys required for specific demos to function completely:\n   *   `MAPS3D_API_KEY`: Required only for the `PlacesUIKit3D` demo to load the Photorealistic 3D Maps tiles.\n   *   `MAP_ID`: Required only for the `PlaceDetailsCompose` demo to demonstrate cloud-based map styling.\n   ```properties\n   MAPS3D_API_KEY=AIza...\n   MAP_ID=...\n   ```\n4. Sync the Android Studio project, build, and run any of the application modules.\n\n### Running Demos via Command Line\n\nEach runnable project includes a convenient `installAndLaunch` task. Instead of using Android Studio, you can natively build, install, and execute any demo directly on your connected device or emulator with a single command:\n\n```bash\n# Launch the standard Java Demo\n./gradlew :demo-java:installAndLaunch\n\n# Launch the Kotlin Demo\n./gradlew :demo-kotlin:installAndLaunch\n\n# Launch the Kotlin Coroutines (KTX) Demo\n./gradlew :kotlin-demos:installAndLaunch\n\n# Launch the Jetpack Compose Demo\n./gradlew :PlaceDetailsCompose:installAndLaunch\n\n# Launch the UIKit Demo\n./gradlew :PlaceDetailsUIKit:installAndLaunch\n\n# Launch the Photorealistic 3D Maps Demo\n./gradlew :PlacesUIKit3D:installAndLaunch\n\n# Launch the Documentation Snippets app\n./gradlew :snippets:installAndLaunch\n```\n\n## Terms of Service\n\nThis sample uses Google Maps Platform services. Use of Google Maps Platform services through this sample is subject to the Google Maps Platform [Terms of Service].\n\nIf your billing address is in the European Economic Area, effective on 8 July 2025, the [Google Maps Platform EEA Terms of Service](https://cloud.google.com/terms/maps-platform/eea) will apply to your use of the Services. Functionality varies by region. [Learn more](https://developers.google.com/maps/comms/eea/faq).\n\nThis sample is not a Google Maps Platform Core Service. Therefore, the Google Maps Platform Terms of Service (e.g. Technical Support Services, Service Level Agreements, and Deprecation Policy) do not apply to the code in this sample.\n\n## Support\n\nThis sample is offered via an open source [license]. It is not governed by the Google Maps Platform Support [Technical Support Services Guidelines], the [SLA], or the [Deprecation Policy]. However, any Google Maps Platform services used by the sample remain subject to the Google Maps Platform Terms of Service.\n\nIf you find a bug, or have a feature request, please [file an issue] on GitHub. If you would like to get answers to technical questions from other Google Maps Platform developers, ask through one of our [developer community channels]. If you'd like to contribute, please check the [contributing guide].\n\nYou can also discuss this sample on our [Discord server].\n\n[API key]: https://developers.google.com/maps/documentation/android-sdk/get-api-key\n[API key instructions]: https://developers.google.com/maps/documentation/android-sdk/config#step_3_add_your_api_key_to_the_project\n\n[code of conduct]: CODE_OF_CONDUCT.md\n[contributing guide]: CONTRIBUTING.md\n[Deprecation Policy]: https://cloud.google.com/maps-platform/terms\n[developer community channels]: https://developers.google.com/maps/developer-community\n[Discord server]: https://discord.gg/hYsWbmk\n[file an issue]: https://github.com/googlemaps-samples/android-places-demos/issues/new/choose\n[license]: LICENSE\n[pull request]: https://github.com/googlemaps-samples/android-places-demos/compare\n[project]: https://developers.google.com/maps/documentation/android-sdk/cloud-setup#enabling-apis\n[Sign up with Google Maps Platform]: https://console.cloud.google.com/google/maps-apis/start\n[SLA]: https://cloud.google.com/maps-platform/terms/sla\n[Technical Support Services Guidelines]: https://cloud.google.com/maps-platform/terms/tssg\n[Terms of Service]: https://cloud.google.com/maps-platform/terms\n[Google Maps Platform EEA Terms of Service]: https://cloud.google.com/terms/maps-platform/eea\n[Learn more]: https://developers.google.com/maps/comms/eea/faq\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Report a security issue\n\nTo report a security issue, please use https://g.co/vulnz. We use\nhttps://g.co/vulnz for our intake, and do coordination and disclosure here on\nGitHub (including using GitHub Security Advisory). The Google Security Team will\nrespond within 5 working days of your report on g.co/vulnz.\n\nTo contact us about other bugs, please open an issue on GitHub.\n\n> **Note**: This file is synchronized from the https://github.com/googlemaps/.github repository.\n"
  },
  {
    "path": "build-logic/convention/build.gradle.kts",
    "content": "/*\n * Copyright 2026 Google LLC\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\nplugins {\n    `kotlin-dsl`\n}\n\ndependencies {\n    compileOnly(libs.android.gradlePlugin)\n    compileOnly(libs.kotlin.gradlePlugin)\n    compileOnly(libs.secrets.gradlePlugin)\n}\n\nkotlin {\n    jvmToolchain(17)\n}\n"
  },
  {
    "path": "build-logic/convention/src/main/kotlin/places-demo.android.application.gradle.kts",
    "content": "/*\n * Copyright 2026 Google LLC\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\nplugins {\n    id(\"com.android.application\")\n}\n\ninterface DemoAppExtension {\n    val mainActivity: Property<String>\n}\n\nval demoApp = extensions.create<DemoAppExtension>(\"demoApp\")\ndemoApp.mainActivity.convention(\".MainActivity\")\n\nandroid {\n    compileSdk = 36\n\n    defaultConfig {\n        minSdk = 24\n        targetSdk = 36\n    }\n\n    java {\n        toolchain {\n            languageVersion.set(JavaLanguageVersion.of(17))\n        }\n    }\n\n    buildFeatures {\n        buildConfig = true\n    }\n}\n\nafterEvaluate {\n    val androidExt = project.extensions.getByType(com.android.build.api.dsl.ApplicationExtension::class.java)\n    val appId = androidExt.defaultConfig.applicationId ?: androidExt.namespace ?: project.name\n    val namespace = androidExt.namespace ?: appId\n    val mainAct = demoApp.mainActivity.get()\n    val componentName = if (mainAct.startsWith(\".\")) \"$appId/$namespace$mainAct\" else \"$appId/$mainAct\"\n\n    val androidComponents = project.extensions.getByType(com.android.build.api.variant.AndroidComponentsExtension::class.java)\n    val adbPath = androidComponents.sdkComponents.adb.get().asFile.absolutePath\n\n    tasks.register<Exec>(\"installAndLaunch\") {\n        description = \"Installs the debug APK and launches the main activity.\"\n        group = \"application\"\n        dependsOn(\"installDebug\")\n        commandLine(adbPath, \"shell\", \"am\", \"start\", \"-n\", componentName)\n    }\n}\n"
  },
  {
    "path": "build-logic/convention/src/main/kotlin/places-demo.secrets.gradle.kts",
    "content": "/*\n * Copyright 2026 Google LLC\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\nimport java.util.Properties\nimport org.gradle.api.GradleException\nimport org.gradle.api.provider.ListProperty\n\ninterface SecretsVerificationExtension {\n    val requiredKeys: ListProperty<String>\n    val optionalKeys: ListProperty<String>\n}\n\nval secretsVerification = extensions.create<SecretsVerificationExtension>(\"secretsVerification\")\nsecretsVerification.requiredKeys.convention(listOf(\"PLACES_API_KEY\", \"MAPS_API_KEY\"))\n\n// Optional keys:\n// MAPS3D_API_KEY: Needed for the 'PlacesUIKit3D' demo.\n// MAP_ID: Needed for the 'PlaceDetailsCompose' demo.\nsecretsVerification.optionalKeys.convention(listOf(\"MAPS3D_API_KEY\", \"MAP_ID\"))\n\n// Check for secrets.properties file and valid API key before proceeding with build tasks.\nafterEvaluate {\n    val requiredKeysToCheck = secretsVerification.requiredKeys.get()\n    val optionalKeysToCheck = secretsVerification.optionalKeys.get()\n\n    val secretsFile = rootProject.file(\"secrets.properties\")\n    val isCI = System.getenv(\"CI\")?.toBoolean() ?: false\n\n    if (!isCI) {\n        val requestedTasks = gradle.startParameter.taskNames\n        if (requestedTasks.isEmpty() && !secretsFile.exists()) {\n            // It's likely an IDE sync if no tasks are specified, so just issue a warning.\n            println(\"Warning: secrets.properties not found. Gradle sync may succeed, but building/running the app will fail.\")\n        } else if (requestedTasks.isNotEmpty()) {\n            val buildTaskKeywords = listOf(\"build\", \"install\", \"assemble\")\n            val isBuildTask = requestedTasks.any { task ->\n                buildTaskKeywords.any { keyword ->\n                    task.contains(keyword, ignoreCase = true)\n                }\n            }\n\n            val testTaskKeywords = listOf(\"test\", \"report\", \"lint\")\n            val isTestTask = requestedTasks.any { task ->\n                testTaskKeywords.any { keyword ->\n                    task.contains(keyword, ignoreCase = true)\n                }\n            }\n\n            val isDebugTask = requestedTasks.any { task ->\n                task.contains(\"Debug\", ignoreCase = true) || task.contains(\"installAndLaunch\", ignoreCase = true)\n            }\n\n            if (isBuildTask && !isTestTask && isDebugTask) {\n                if (!secretsFile.exists()) {\n                    val defaultsFile = rootProject.file(\"local.defaults.properties\")\n                    val requiredKeysMessage = if (defaultsFile.exists()) {\n                        defaultsFile.readText()\n                    } else {\n                        requiredKeysToCheck.joinToString(\"\\n\") { \"$it=<YOUR_API_KEY>\" }\n                    }\n                    throw GradleException(\"secrets.properties file not found. Please create a 'secrets.properties' file in the root project directory with the following content:\\n\\n$requiredKeysMessage\")\n                }\n\n                val secrets = Properties()\n                secretsFile.inputStream().use { secrets.load(it) }\n\n                val isValidKey = { key: String? ->\n                    !key.isNullOrBlank() && key != \"DEFAULT_API_KEY\" && key!!.matches(Regex(\"^AIza[a-zA-Z0-9_-]{35}$\"))\n                }\n                \n                val isPresentAndNotDefault = { key: String? ->\n                    !key.isNullOrBlank() && key != \"DEFAULT_API_KEY\"\n                }\n\n                requiredKeysToCheck.forEach { reqKey ->\n                    val keyValue = secrets.getProperty(reqKey)\n                    if (reqKey.endsWith(\"API_KEY\", ignoreCase = true)) {\n                        if (!isValidKey(keyValue)) {\n                            throw GradleException(\"Invalid or missing $reqKey in secrets.properties. Please provide a valid Google Maps API key (starts with 'AIza').\")\n                        }\n                    } else {\n                        if (!isPresentAndNotDefault(keyValue)) {\n                            throw GradleException(\"Missing $reqKey in secrets.properties.\")\n                        }\n                    }\n                }\n\n                optionalKeysToCheck.forEach { optKey ->\n                    val keyValue = secrets.getProperty(optKey)\n                    if (isPresentAndNotDefault(keyValue)) {\n                        if (optKey.endsWith(\"API_KEY\", ignoreCase = true)) {\n                            if (!isValidKey(keyValue)) {\n                                val demoMsg = if (optKey == \"MAPS3D_API_KEY\") \" (Required for PlacesUIKit3D demo)\" else \"\"\n                                throw GradleException(\"Invalid $optKey in secrets.properties.$demoMsg Please provide a valid Google Maps API key (starts with 'AIza').\")\n                            }\n                        }\n                    } else {\n                        if (optKey == \"MAPS3D_API_KEY\") {\n                            println(\"Warning: MAPS3D_API_KEY is missing or set to default in secrets.properties. The 'PlacesUIKit3D' demo will fail to load 3D maps at runtime.\")\n                        } else if (optKey == \"MAP_ID\") {\n                            println(\"Warning: MAP_ID is missing or set to default in secrets.properties. The 'PlaceDetailsCompose' demo will fail to load custom map styling at runtime.\")\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nplugins {\n    id(\"com.google.android.libraries.mapsplatform.secrets-gradle-plugin\")\n}\n\nsecrets {\n    // To add your Google Maps Platform API key to this project:\n    // 1. Copy local.defaults.properties to secrets.properties\n    // 2. In the secrets.properties file, replace PLACES_API_KEY=DEFAULT_API_KEY with a key from a\n    //    project with Places API enabled\n    // 3. In the secrets.properties file, replace MAPS_API_KEY=DEFAULT_API_KEY with a key from a\n    //    project with Maps SDK for Android enabled (can be the same project and key as in Step 2)\n    defaultPropertiesFileName = \"local.defaults.properties\"\n\n    // Optionally specify a different file name containing your secrets.\n    // The plugin defaults to \"local.properties\"\n    propertiesFileName = \"secrets.properties\"\n}\n"
  },
  {
    "path": "build-logic/settings.gradle.kts",
    "content": "/*\n * Copyright 2026 Google LLC\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\ndependencyResolutionManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        mavenCentral()\n    }\n    versionCatalogs {\n        create(\"libs\") {\n            from(files(\"../gradle/libs.versions.toml\"))\n        }\n    }\n}\n\npluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\nrootProject.name = \"build-logic\"\ninclude(\":convention\")\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "/*\n * Copyright 2026 Google LLC\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// 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.kotlin.android) apply false\n    alias(libs.plugins.kotlin.compose) apply false\n    alias(libs.plugins.secrets.gradle.plugin) apply false\n    alias(libs.plugins.jetbrains.kotlin.parcelize) apply false\n    alias(libs.plugins.hilt.android) apply false\n    alias(libs.plugins.ksp) apply false\n    alias(libs.plugins.kotlin.kapt) apply false\n}\n\nallprojects {\n    configurations.all {\n        resolutionStrategy {\n            force(\"org.jetbrains.kotlin:kotlin-metadata-jvm:2.3.10\")\n        }\n    }\n}\n"
  },
  {
    "path": "demo-java/build.gradle.kts",
    "content": "/*\n * Copyright 2025 Google LLC\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\nplugins {\n    id(\"places-demo.android.application\")\n    id(\"places-demo.secrets\")\n}\n\nandroid {\n    namespace = \"com.example.placesdemo\"\n\n    defaultConfig {\n        applicationId = \"com.example.placesdemo\"\n        versionCode = 1\n        versionName = \"1.0\"\n\n        multiDexEnabled = true\n    }\n\n    buildFeatures {\n        viewBinding = true\n        buildConfig = true\n    }\n}\n\ndependencies {\n    implementation(libs.constraintlayout)\n    implementation(libs.activity)\n    implementation(libs.fragment)\n    implementation(libs.navigation.fragment)\n    implementation(libs.navigation.ui)\n\n    implementation(libs.appcompat)\n    implementation(libs.material)\n\n    implementation(libs.volley)\n    implementation(libs.glide)\n    implementation(libs.viewbinding)\n    implementation(libs.multidex)\n\n    // Places and Maps SDKs\n    implementation(libs.places)\n    implementation(libs.play.services.maps)\n    implementation(libs.android.maps.utils)\n}\n\n"
  },
  {
    "path": "demo-java/local.defaults.properties",
    "content": "PLACES_API_KEY=DEFAULT_API_KEY\nMAPS_API_KEY=DEFAULT_API_KEY\n"
  },
  {
    "path": "demo-java/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": "demo-java/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Copyright 2020 Google LLC\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<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n\n    <application\n        android:name=\".PlacesDemoApplication\"\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.AppCompat.Light\">\n\n        <meta-data\n            android:name=\"com.google.android.gms.version\"\n            android:value=\"@integer/google_play_services_version\" />\n\n        <meta-data\n            android:name=\"com.google.android.geo.API_KEY\"\n            android:value=\"${MAPS_API_KEY}\" />\n\n        <activity\n            android:name=\".MainActivity\"\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=\".PlaceAutocompleteActivity\"\n            android:exported=\"true\" />\n        <activity\n            android:name=\".AutocompleteAddressActivity\"\n            android:exported=\"true\"\n            android:windowSoftInputMode=\"stateHidden\" />\n        <activity\n            android:name=\".PlaceDetailsAndPhotosActivity\"\n            android:exported=\"true\" />\n        <activity\n            android:name=\".PlaceIsOpenActivity\"\n            android:exported=\"true\" />\n        <activity\n            android:name=\".CurrentPlaceActivity\"\n            android:exported=\"true\" />\n        <activity\n            android:name=\".programmatic_autocomplete.ProgrammaticAutocompleteToolbarActivity\"\n            android:exported=\"true\"\n            android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\" />\n\n    </application>\n</manifest>"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/AutocompleteAddressActivity.java",
    "content": "/*\n * Copyright 2022 Google LLC\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\npackage com.example.placesdemo;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewStub;\nimport android.widget.Button;\nimport android.widget.CheckBox;\nimport android.widget.Toast;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.content.ContextCompat;\n\nimport com.example.placesdemo.databinding.AutocompleteAddressActivityBinding;\nimport com.google.android.gms.location.FusedLocationProviderClient;\nimport com.google.android.gms.location.LocationServices;\nimport com.google.android.gms.maps.CameraUpdateFactory;\nimport com.google.android.gms.maps.GoogleMap;\nimport com.google.android.gms.maps.GoogleMapOptions;\nimport com.google.android.gms.maps.OnMapReadyCallback;\nimport com.google.android.gms.maps.SupportMapFragment;\nimport com.google.android.gms.maps.model.LatLng;\nimport com.google.android.gms.maps.model.MapStyleOptions;\nimport com.google.android.gms.maps.model.Marker;\nimport com.google.android.gms.maps.model.MarkerOptions;\nimport com.google.android.libraries.places.api.Places;\nimport com.google.android.libraries.places.api.model.AddressComponent;\nimport com.google.android.libraries.places.api.model.AddressComponents;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.model.PlaceTypes;\nimport com.google.android.libraries.places.api.net.PlacesClient;\nimport com.google.android.libraries.places.widget.Autocomplete;\nimport com.google.android.libraries.places.widget.model.AutocompleteActivityMode;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static android.Manifest.permission.ACCESS_FINE_LOCATION;\nimport static com.google.maps.android.SphericalUtil.computeDistanceBetween;\nimport androidx.activity.EdgeToEdge;\n\n/**\n * Activity for using Place Autocomplete to assist filling out an address form.\n */\n@SuppressWarnings(\"FieldCanBeLocal\")\npublic class AutocompleteAddressActivity extends AppCompatActivity implements OnMapReadyCallback {\n\n    private static final String TAG = \"ADDRESS_AUTOCOMPLETE\";\n    private static final String MAP_FRAGMENT_TAG = \"MAP\";\n    private LatLng coordinates;\n    private boolean checkProximity = false;\n    private SupportMapFragment mapFragment;\n    private GoogleMap map;\n    private Marker marker;\n    private PlacesClient placesClient;\n    private View mapPanel;\n    private LatLng deviceLocation;\n    private static final double acceptedProximity = 150;\n\n    private AutocompleteAddressActivityBinding binding;\n\n    View.OnClickListener startAutocompleteIntentListener = view -> {\n        view.setOnClickListener(null);\n        startAutocompleteIntent();\n    };\n\n    // [START maps_solutions_android_autocomplete_define]\n    private final ActivityResultLauncher<Intent> startAutocomplete = registerForActivityResult(\n            new ActivityResultContracts.StartActivityForResult(),\n            result -> {\n                if (result.getResultCode() == Activity.RESULT_OK) {\n                    Intent intent = result.getData();\n                    if (intent != null) {\n                        Place place = Autocomplete.getPlaceFromIntent(intent);\n\n                        // Write a method to read the address components from the Place\n                        // and populate the form with the address components\n                        Log.d(TAG, \"Place: \" + place.getAddressComponents());\n                        fillInAddress(place);\n                    }\n                } else if (result.getResultCode() == Activity.RESULT_CANCELED) {\n                    // The user canceled the operation.\n                    Log.i(TAG, \"User canceled autocomplete\");\n                }\n            });\n    // [END maps_solutions_android_autocomplete_define]\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {\n        super.onActivityResult(requestCode, resultCode, intent);\n        binding.autocompleteAddress1.setOnClickListener(startAutocompleteIntentListener);\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        // Enable edge-to-edge display. This must be called before calling super.onCreate().\n        EdgeToEdge.enable(this);\n        super.onCreate(savedInstanceState);\n\n        binding = AutocompleteAddressActivityBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient(this);\n\n        // Attach an Autocomplete intent to the Address 1 EditText field\n        binding.autocompleteAddress1.setOnClickListener(startAutocompleteIntentListener);\n\n        // Update checkProximity when user checks the checkbox\n        CheckBox checkProximityBox = findViewById(R.id.checkbox_proximity);\n        checkProximityBox.setOnCheckedChangeListener((view, isChecked) -> {\n            // Set the boolean to match user preference for when the Submit button is clicked\n            checkProximity = isChecked;\n        });\n\n        // Submit and optionally check proximity\n        Button saveButton = findViewById(R.id.autocomplete_save_button);\n        saveButton.setOnClickListener(v -> saveForm());\n\n        // Reset the form\n        Button resetButton = findViewById(R.id.autocomplete_reset_button);\n        resetButton.setOnClickListener(v -> clearForm());\n    }\n\n    // [START maps_solutions_android_autocomplete_intent]\n    private void startAutocompleteIntent() {\n\n        // Set the fields to specify which types of place data to\n        // return after the user has made a selection.\n        List<Place.Field> fields = Arrays.asList(Place.Field.ADDRESS_COMPONENTS,\n                Place.Field.LOCATION, Place.Field.VIEWPORT);\n\n        // Build the autocomplete intent with field, country, and type filters applied\n        Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)\n                .setCountries(List.of(\"US\"))\n                .setTypesFilter(List.of(\"establishment\"))\n                .build(this);\n        startAutocomplete.launch(intent);\n    }\n    // [END maps_solutions_android_autocomplete_intent]\n\n    // [START maps_solutions_android_autocomplete_map_ready]\n    @Override\n    public void onMapReady(@NonNull GoogleMap googleMap) {\n        map = googleMap;\n        try {\n            // Customise the styling of the base map using a JSON object defined\n            // in a string resource.\n            boolean success = map.setMapStyle(\n                    MapStyleOptions.loadRawResourceStyle(this, R.raw.style_json));\n\n            if (!success) {\n                Log.e(TAG, \"Style parsing failed.\");\n            }\n        } catch (Resources.NotFoundException e) {\n            Log.e(TAG, \"Can't find style. Error: \", e);\n        }\n        map.moveCamera(CameraUpdateFactory.newLatLngZoom(coordinates, 15f));\n        marker = map.addMarker(new MarkerOptions().position(coordinates));\n    }\n    // [END maps_solutions_android_autocomplete_map_ready]\n\n    private void fillInAddress(Place place) {\n        AddressComponents components = place.getAddressComponents();\n        StringBuilder address1 = new StringBuilder();\n        StringBuilder postcode = new StringBuilder();\n\n        // Get each component of the address from the place details,\n        // and then fill-in the corresponding field on the form.\n        // Possible AddressComponent types are documented at https://goo.gle/32SJPM1\n        if (components != null) {\n            for (AddressComponent component : components.asList()) {\n                String type = component.getTypes().get(0);\n                switch (type) {\n                    case \"street_number\": {\n                        address1.insert(0, component.getName());\n                        break;\n                    }\n\n                    case \"route\": {\n                        address1.append(\" \");\n                        address1.append(component.getShortName());\n                        break;\n                    }\n\n                    case \"postal_code\": {\n                        postcode.insert(0, component.getName());\n                        break;\n                    }\n\n                    case \"postal_code_suffix\": {\n                        postcode.append(\"-\").append(component.getName());\n                        break;\n                    }\n\n                    case \"locality\":\n                        binding.autocompleteCity.setText(component.getName());\n                        break;\n\n                    case \"administrative_area_level_1\": {\n                        binding.autocompleteState.setText(component.getShortName());\n                        break;\n                    }\n\n                    case \"country\":\n                        binding.autocompleteCountry.setText(component.getName());\n                        break;\n                }\n            }\n        }\n\n        binding.autocompleteAddress1.setText(address1.toString());\n        binding.autocompletePostal.setText(postcode.toString());\n\n        // After filling the form with address components from the Autocomplete\n        // prediction, set cursor focus on the second address line to encourage\n        // entry of sub-premise information such as apartment, unit, or floor number.\n        binding.autocompleteAddress2.requestFocus();\n\n        // Add a map for visual confirmation of the address\n        showMap(place);\n    }\n\n    // [START maps_solutions_android_autocomplete_map_add]\n    private void showMap(Place place) {\n        coordinates = place.getLocation();\n\n        // It isn't possible to set a fragment's id programmatically so we set a tag instead and\n        // search for it using that.\n        mapFragment = (SupportMapFragment)\n                getSupportFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG);\n\n        // We only create a fragment if it doesn't already exist.\n        if (mapFragment == null) {\n            mapPanel = ((ViewStub) findViewById(R.id.stub_map)).inflate();\n            GoogleMapOptions mapOptions = new GoogleMapOptions();\n            mapOptions.mapToolbarEnabled(false);\n\n            // To programmatically add the map, we first create a SupportMapFragment.\n            mapFragment = SupportMapFragment.newInstance(mapOptions);\n\n            // Then we add it using a FragmentTransaction.\n            getSupportFragmentManager()\n                    .beginTransaction()\n                    .add(R.id.confirmation_map, mapFragment, MAP_FRAGMENT_TAG)\n                    .commit();\n            mapFragment.getMapAsync(this);\n        } else {\n            updateMap(coordinates);\n        }\n    }\n    // [END maps_solutions_android_autocomplete_map_add]\n\n    private void updateMap(LatLng latLng) {\n        marker.setPosition(latLng);\n        map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f));\n        if (mapPanel.getVisibility() == View.GONE) {\n            mapPanel.setVisibility(View.VISIBLE);\n        }\n    }\n\n    private void saveForm() {\n        Log.d(TAG, \"checkProximity = \" + checkProximity);\n        if (checkProximity) {\n            checkLocationPermissions();\n        } else {\n            Toast.makeText(\n                            this,\n                            R.string.autocomplete_skipped_message,\n                            Toast.LENGTH_SHORT)\n                    .show();\n        }\n    }\n\n    private void clearForm() {\n        binding.autocompleteAddress1.setText(\"\");\n        binding.autocompleteAddress2.getText().clear();\n        binding.autocompleteCity.getText().clear();\n        binding.autocompleteState.getText().clear();\n        binding.autocompletePostal.getText().clear();\n        binding.autocompleteCountry.getText().clear();\n        if (mapPanel != null) {\n            mapPanel.setVisibility(View.GONE);\n        }\n        binding.autocompleteAddress1.requestFocus();\n    }\n\n    // [START maps_solutions_android_permission_request]\n    // Register the permissions callback, which handles the user's response to the\n    // system permissions dialog. Save the return value, an instance of\n    // ActivityResultLauncher, as an instance variable.\n    private final ActivityResultLauncher<String> requestPermissionLauncher =\n            registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {\n                if (isGranted) {\n                    // Since ACCESS_FINE_LOCATION is the only permission in this sample,\n                    // run the location comparison task once permission is granted.\n                    // Otherwise, check which permission is granted.\n                    getAndCompareLocations();\n                } else {\n                    // Fallback behavior if user denies permission\n                    Log.d(TAG, \"User denied permission\");\n                }\n            });\n    // [END maps_solutions_android_permission_request]\n\n    // [START maps_solutions_android_location_permissions]\n    private void checkLocationPermissions() {\n        if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION)\n                == PackageManager.PERMISSION_GRANTED) {\n            getAndCompareLocations();\n        } else {\n            requestPermissionLauncher.launch(\n                    ACCESS_FINE_LOCATION);\n        }\n    }\n    // [END maps_solutions_android_location_permissions]\n\n    @SuppressLint(\"MissingPermission\")\n    private void getAndCompareLocations() {\n        // TODO: Detect and handle if user has entered or modified the address manually and update\n        // the coordinates variable to the Lat/Lng of the manually entered address. May use\n        // Geocoding API to convert the manually entered address to a Lat/Lng.\n        LatLng enteredLocation = coordinates;\n        map.setMyLocationEnabled(true);\n\n        // [START maps_solutions_android_location_get]\n        FusedLocationProviderClient fusedLocationClient =\n                LocationServices.getFusedLocationProviderClient(this);\n\n        fusedLocationClient.getLastLocation()\n                .addOnSuccessListener(this, location -> {\n                    // Got last known location. In some rare situations this can be null.\n                    if (location == null) {\n                        return;\n                    }\n\n                    deviceLocation = new LatLng(location.getLatitude(), location.getLongitude());\n                    // [START_EXCLUDE]\n                    Log.d(TAG, \"device location = \" + deviceLocation);\n                    Log.d(TAG, \"entered location = \" + enteredLocation.toString());\n\n                    // [START maps_solutions_android_location_distance]\n                    // Use the computeDistanceBetween function in the Maps SDK for Android Utility Library\n                    // to use spherical geometry to compute the distance between two Lat/Lng points.\n                    double distanceInMeters = computeDistanceBetween(deviceLocation, enteredLocation);\n                    if (distanceInMeters <= acceptedProximity) {\n                        Log.d(TAG, \"location matched\");\n                        // TODO: Display UI based on the locations matching\n                    } else {\n                        Log.d(TAG, \"location not matched\");\n                        // TODO: Display UI based on the locations not matching\n                    }\n                    // [END maps_solutions_android_location_distance]\n                    // [END_EXCLUDE]\n                });\n    }\n    // [END maps_solutions_android_location_get]\n}"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/CurrentPlaceActivity.java",
    "content": "/*\n * Copyright 2024 Google LLC\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\npackage com.example.placesdemo;\n\nimport com.example.placesdemo.databinding.CurrentPlaceActivityBinding;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.libraries.places.api.Places;\nimport com.google.android.libraries.places.api.model.Place.Field;\nimport com.google.android.libraries.places.api.model.PlaceLikelihood;\nimport com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;\nimport com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;\nimport com.google.android.libraries.places.api.net.PlacesClient;\n\nimport android.Manifest.permission;\nimport android.annotation.SuppressLint;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\n\nimport java.util.List;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresPermission;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.content.ContextCompat;\n\nimport static android.Manifest.permission.ACCESS_FINE_LOCATION;\nimport static android.Manifest.permission.ACCESS_WIFI_STATE;\n\nimport androidx.activity.EdgeToEdge;\n\n/**\n * Activity to demonstrate {@link PlacesClient#findCurrentPlace(FindCurrentPlaceRequest)}.\n */\npublic class CurrentPlaceActivity extends AppCompatActivity {\n\n    private static final String TAG = \"CURRENT_PLACE\";\n\n    private PlacesClient placesClient;\n    private FieldSelector fieldSelector;\n\n    private CurrentPlaceActivityBinding binding;\n\n    // [START maps_solutions_android_permission_request]\n    // Register the permissions callback, which handles the user's response to the\n    // system permissions dialog. Save the return value, an instance of\n    // ActivityResultLauncher, as an instance variable.\n    @SuppressLint(\"MissingPermission\")\n    private final ActivityResultLauncher<String[]> requestPermissionLauncher =\n            registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> {\n                if (Boolean.TRUE.equals(isGranted.get(permission.ACCESS_FINE_LOCATION))\n                        && Boolean.TRUE.equals(isGranted.get(ACCESS_WIFI_STATE))) {\n                    findCurrentPlaceWithPermissions();\n                } else {\n                    // Fallback behavior if user denies permission\n                    Log.d(TAG, \"User denied permission\");\n                }\n            });\n    // [END maps_solutions_android_permission_request]\n\n    // [START maps_solutions_android_location_permissions]\n    @SuppressLint(\"MissingPermission\")\n    private void checkLocationPermissions() {\n        if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION)\n                == PackageManager.PERMISSION_GRANTED) {\n            findCurrentPlaceWithPermissions();\n        } else {\n            requestPermissionLauncher.launch(new String[]{permission.ACCESS_FINE_LOCATION, permission.ACCESS_WIFI_STATE});\n        }\n    }\n    // [END maps_solutions_android_location_permissions]\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        // Enable edge-to-edge display. This must be called before calling super.onCreate().\n        EdgeToEdge.enable(this);\n        super.onCreate(savedInstanceState);\n\n        binding = CurrentPlaceActivityBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient(this);\n\n        // Set view objects\n        List<Field> placeFields = FieldSelector.allExcept(\n                Field.ADDRESS_COMPONENTS,\n                Field.CURBSIDE_PICKUP,\n                Field.CURRENT_OPENING_HOURS,\n                Field.DELIVERY,\n                Field.DINE_IN,\n                Field.EDITORIAL_SUMMARY,\n                Field.INTERNATIONAL_PHONE_NUMBER,\n                Field.OPENING_HOURS,\n                Field.RESERVABLE,\n                Field.SECONDARY_OPENING_HOURS,\n                Field.SERVES_BEER,\n                Field.SERVES_BREAKFAST,\n                Field.SERVES_BRUNCH,\n                Field.SERVES_DINNER,\n                Field.SERVES_LUNCH,\n                Field.SERVES_VEGETARIAN_FOOD,\n                Field.SERVES_WINE,\n                Field.TAKEOUT,\n                Field.UTC_OFFSET,\n                Field.WEBSITE_URI\n        );\n        fieldSelector = new FieldSelector(\n                binding.useCustomFields,\n                binding.customFieldsList,\n                placeFields,\n                savedInstanceState);\n        setLoading(false);\n\n        // Set listeners for programmatic Find Current Place\n        binding.findCurrentPlaceButton.setOnClickListener((view) -> checkLocationPermissions());\n    }\n\n    @Override\n    protected void onSaveInstanceState(@NonNull Bundle bundle) {\n        super.onSaveInstanceState(bundle);\n        fieldSelector.onSaveInstanceState(bundle);\n    }\n\n    /**\n     * Fetches a list of {@link PlaceLikelihood} instances that represent the Places the user is\n     * most\n     * likely to be at currently.\n     */\n    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE})\n    private void findCurrentPlaceWithPermissions() {\n        setLoading(true);\n\n        FindCurrentPlaceRequest currentPlaceRequest =\n                FindCurrentPlaceRequest.newInstance(getPlaceFields());\n        Task<FindCurrentPlaceResponse> currentPlaceTask =\n                placesClient.findCurrentPlace(currentPlaceRequest);\n\n        currentPlaceTask.addOnSuccessListener(\n                (response) ->\n                        binding.response.setText(StringUtil.stringify(response, isDisplayRawResultsChecked())));\n\n        currentPlaceTask.addOnFailureListener(\n                (exception) -> {\n                    exception.printStackTrace();\n                    binding.response.setText(exception.getMessage());\n                });\n\n        currentPlaceTask.addOnCompleteListener(task -> setLoading(false));\n    }\n\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n\n    private List<Field> getPlaceFields() {\n        if (binding.useCustomFields.isChecked()) {\n            return fieldSelector.getSelectedFields();\n        } else {\n            return fieldSelector.getAllFields();\n        }\n    }\n\n    private boolean isDisplayRawResultsChecked() {\n        return binding.displayRawResults.isChecked();\n    }\n\n    private void setLoading(boolean loading) {\n        binding.loading.setVisibility(loading ? View.VISIBLE : View.INVISIBLE);\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/FieldSelector.java",
    "content": "/*\n * Copyright 2018 Google LLC\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\npackage com.example.placesdemo;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AdapterView;\nimport android.widget.AdapterView.OnItemClickListener;\nimport android.widget.ArrayAdapter;\nimport android.widget.CheckBox;\nimport android.widget.CheckedTextView;\nimport android.widget.ListView;\nimport android.widget.TextView;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AlertDialog;\n\nimport com.google.android.libraries.places.api.model.Place.Field;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/** Helper class for selecting {@link Field} values. */\npublic final class FieldSelector {\n  private static final String SELECTED_PLACE_FIELDS_KEY = \"selected_place_fields\";\n\n  private final Map<Field, State> fieldStates;\n\n  private final TextView outputView;\n\n  /**\n   * Returns all {@link Field} values except those passed in.\n   *\n   * <p>Convenience method for when most {@link Field} values are desired. Useful for APIs that do\n   * no support all {@link Field} values.\n   */\n  static List<Field> allExcept(Field... placeFieldsToOmit) {\n    // Arrays.asList is immutable, create a mutable list to allow removing fields\n    List<Field> placeFields = new ArrayList<>(Arrays.asList(Field.values()));\n    placeFields.removeAll(Arrays.asList(placeFieldsToOmit));\n\n    return placeFields;\n  }\n\n  public FieldSelector(CheckBox enableView, TextView outputView, @Nullable Bundle savedState) {\n    this(enableView, outputView, Arrays.asList(Field.values()), savedState);\n  }\n\n  public FieldSelector(\n          CheckBox enableView,\n          TextView outputView,\n          List<Field> validFields,\n          @Nullable Bundle savedState) {\n    fieldStates = new HashMap<>();\n    for (Field field : validFields) {\n      fieldStates.put(field, new State(field));\n    }\n\n    if (savedState != null) {\n      List<Integer> selectedFields = savedState.getIntegerArrayList(SELECTED_PLACE_FIELDS_KEY);\n      if (selectedFields != null) {\n        restoreState(selectedFields);\n      }\n      outputView.setText(getSelectedString());\n    }\n\n    outputView.setOnClickListener(\n        v -> {\n          if (v.isEnabled()) {\n            showDialog(v.getContext());\n          }\n        });\n\n    enableView.setOnClickListener(\n            view -> {\n              boolean isChecked = enableView.isChecked();\n              outputView.setEnabled(isChecked);\n              if (isChecked) {\n                showDialog(view.getContext());\n              } else {\n                outputView.setText(\"\");\n                for (State state : fieldStates.values()) {\n                  state.checked = false;\n            }\n          }\n        });\n\n    this.outputView = outputView;\n  }\n\n  /**\n   * Shows dialog to allow user to select {@link Field} values they want.\n   */\n  public void showDialog(Context context) {\n    ListView listView = new ListView(context);\n    PlaceFieldArrayAdapter adapter = new PlaceFieldArrayAdapter(context, fieldStates.values());\n    listView.setAdapter(adapter);\n    listView.setOnItemClickListener(adapter);\n\n    new AlertDialog.Builder(context)\n            .setTitle(\"Select Place Fields\")\n            .setPositiveButton(\n                    \"Done\",\n                    (dialog, which) -> {\n                      outputView.setText(getSelectedString());\n                    })\n            .setView(listView)\n            .show();\n  }\n\n  /**\n   * Returns all {@link Field} that are selectable.\n   */\n  public List<Field> getAllFields() {\n    return new ArrayList<>(fieldStates.keySet());\n  }\n\n  /**\n   * Returns all {@link Field} values the user selected.\n   */\n  public List<Field> getSelectedFields() {\n    List<Field> selectedList = new ArrayList<>();\n    for (Map.Entry<Field, State> entry : fieldStates.entrySet()) {\n      if (entry.getValue().checked) {\n        selectedList.add(entry.getKey());\n      }\n    }\n\n    return selectedList;\n  }\n\n  /**\n   * Returns a String representation of all selected {@link Field} values. See {@link\n   * #getSelectedFields()}.\n   */\n  public String getSelectedString() {\n    StringBuilder builder = new StringBuilder();\n    for (Field field : getSelectedFields()) {\n      builder.append(field).append(\"\\n\");\n    }\n\n    return builder.toString();\n  }\n\n\n  public void onSaveInstanceState(Bundle bundle) {\n    List<Field> fields = getSelectedFields();\n\n    ArrayList<Integer> serializedFields = new ArrayList<>();\n    for (Field field : fields) {\n      serializedFields.add(field.ordinal());\n    }\n    bundle.putIntegerArrayList(SELECTED_PLACE_FIELDS_KEY, serializedFields);\n  }\n\n  private void restoreState(List<Integer> selectedFields) {\n    for (Integer serializedField : selectedFields) {\n      Field field = Field.values()[serializedField];\n      State state = fieldStates.get(field);\n      if (state != null) {\n        state.checked = true;\n      }\n    }\n  }\n\n  //////////////////////////\n  // Helper methods below //\n  //////////////////////////\n\n  /**\n   * Holds selection state for a place field.\n   */\n  public static final class State {\n    public final Field field;\n    public boolean checked;\n\n    public State(Field field) {\n      this.field = field;\n    }\n  }\n\n  private static final class PlaceFieldArrayAdapter extends ArrayAdapter<State>\n      implements OnItemClickListener {\n\n    public PlaceFieldArrayAdapter(Context context, Collection<State> states) {\n      super(context, android.R.layout.simple_list_item_multiple_choice, new ArrayList<>(states));\n    }\n\n    private static void updateView(View view, State state) {\n      if (view instanceof CheckedTextView) {\n        CheckedTextView checkedTextView = (CheckedTextView) view;\n        checkedTextView.setText(state.field.toString());\n        checkedTextView.setChecked(state.checked);\n      }\n    }\n\n    @Override\n    public View getView(int position, @Nullable View convertView, ViewGroup parent) {\n      View view = super.getView(position, convertView, parent);\n      State state = getItem(position);\n      updateView(view, state);\n\n      return view;\n    }\n\n    @Override\n    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n      State state = getItem(position);\n      state.checked = !state.checked;\n      updateView(view, state);\n    }\n  }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/MainActivity.java",
    "content": "/*\n * Copyright 2022 Google LLC\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\npackage com.example.placesdemo;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.widget.Toast;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport com.example.placesdemo.programmatic_autocomplete.ProgrammaticAutocompleteToolbarActivity;\nimport com.google.android.libraries.places.api.Places;\n\nimport androidx.activity.EdgeToEdge;\n\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        // Enable edge-to-edge display. This must be called before calling super.onCreate().\n        EdgeToEdge.enable(this);\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        setLaunchActivityClickListener(R.id.autocomplete_button, PlaceAutocompleteActivity.class);\n        setLaunchActivityClickListener(R.id.autocomplete_address_button, AutocompleteAddressActivity.class);\n        setLaunchActivityClickListener(R.id.programmatic_autocomplete_button, ProgrammaticAutocompleteToolbarActivity.class\n        );\n        setLaunchActivityClickListener(R.id.place_and_photo_button, PlaceDetailsAndPhotosActivity.class);\n        setLaunchActivityClickListener(R.id.is_open_button, PlaceIsOpenActivity.class);\n        setLaunchActivityClickListener(R.id.current_place_button, CurrentPlaceActivity.class);\n    }\n\n    private void setLaunchActivityClickListener(\n            int onClickResId, Class<? extends AppCompatActivity> activityClassToLaunch) {\n        findViewById(onClickResId)\n                .setOnClickListener(\n                        v -> {\n                            Intent intent = new Intent(MainActivity.this, activityClassToLaunch);\n                            startActivity(intent);\n                        });\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/PlaceAutocompleteActivity.java",
    "content": "/*\n * Copyright 2022 Google LLC\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\npackage com.example.placesdemo;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.CheckBox;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport com.example.placesdemo.databinding.PlaceAutocompleteActivityBinding;\nimport com.google.android.gms.common.api.Status;\nimport com.google.android.gms.maps.model.LatLng;\nimport com.google.android.gms.maps.model.LatLngBounds;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.libraries.places.api.Places;\nimport com.google.android.libraries.places.api.model.AutocompleteSessionToken;\nimport com.google.android.libraries.places.api.model.LocationBias;\nimport com.google.android.libraries.places.api.model.LocationRestriction;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.model.RectangularBounds;\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest;\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse;\nimport com.google.android.libraries.places.api.net.PlacesClient;\nimport com.google.android.libraries.places.widget.Autocomplete;\nimport com.google.android.libraries.places.widget.AutocompleteActivity;\nimport com.google.android.libraries.places.widget.AutocompleteSupportFragment;\nimport com.google.android.libraries.places.widget.listener.PlaceSelectionListener;\nimport com.google.android.libraries.places.widget.model.AutocompleteActivityMode;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport androidx.activity.EdgeToEdge;\n\n/**\n * Activity to demonstrate Place Autocomplete (activity widget intent, fragment widget, and\n * {@link PlacesClient#([PlacesClient.findAutocompletePredictions])}).\n */\npublic class PlaceAutocompleteActivity extends AppCompatActivity {\n\n    private static final int AUTOCOMPLETE_REQUEST_CODE = 23487;\n    private PlacesClient placesClient;\n    private FieldSelector fieldSelector;\n    private PlaceAutocompleteActivityBinding binding;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        // Enable edge-to-edge display. This must be called before calling super.onCreate().\n        EdgeToEdge.enable(this);\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.place_autocomplete_activity);\n\n        binding = PlaceAutocompleteActivityBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient(this);\n\n        // Set up view objects\n        binding.autocompleteUseTypesFilterCheckbox.setOnCheckedChangeListener(\n                (buttonView, isChecked) -> binding.autocompleteTypesFilterEdittext.setEnabled(isChecked));\n        fieldSelector =\n                new FieldSelector(\n                        binding.useCustomFields,\n                        binding.customFieldsList,\n                        savedInstanceState);\n\n        setupAutocompleteSupportFragment();\n\n        // Set listeners for Autocomplete activity\n        binding.autocompleteActivityButton\n                .setOnClickListener(view -> startAutocompleteActivity());\n\n        // Set listeners for programmatic Autocomplete\n        binding.fetchAutocompletePredictionsButton.setOnClickListener(view -> findAutocompletePredictions());\n\n        // UI initialization\n        setLoading(false);\n    }\n\n    @Override\n    protected void onSaveInstanceState(@NonNull Bundle bundle) {\n        super.onSaveInstanceState(bundle);\n        fieldSelector.onSaveInstanceState(bundle);\n    }\n\n    private void setupAutocompleteSupportFragment() {\n        final AutocompleteSupportFragment autocompleteSupportFragment =\n                (AutocompleteSupportFragment)\n                        getSupportFragmentManager().findFragmentById(R.id.autocomplete_support_fragment);\n        if (autocompleteSupportFragment != null) {\n            autocompleteSupportFragment.setPlaceFields(getPlaceFields());\n            autocompleteSupportFragment.setOnPlaceSelectedListener(getPlaceSelectionListener());\n        }\n\n        binding.autocompleteSupportFragmentUpdateButton\n                .setOnClickListener(\n                        view ->\n                                autocompleteSupportFragment\n                                        .setPlaceFields(getPlaceFields())\n                                        .setText(getQuery())\n                                        .setHint(getHint())\n                                        .setCountries(getCountries())\n                                        .setLocationBias(getLocationBias())\n                                        .setLocationRestriction(getLocationRestriction())\n                                        .setTypesFilter(getTypesFilter())\n                                        .setActivityMode(getMode()));\n    }\n\n    private PlaceSelectionListener getPlaceSelectionListener() {\n        return new PlaceSelectionListener() {\n            @Override\n            public void onPlaceSelected(@NonNull Place place) {\n                binding.response.setText(\n                        StringUtil.stringifyAutocompleteWidget(place, isDisplayRawResultsChecked()));\n            }\n\n            @Override\n            public void onError(@NonNull Status status) {\n                binding.response.setText(status.getStatusMessage());\n            }\n        };\n    }\n\n    /**\n     * Called when AutocompleteActivity finishes\n     */\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {\n        if (requestCode == AUTOCOMPLETE_REQUEST_CODE) {\n            if (resultCode == AutocompleteActivity.RESULT_OK) {\n                Place place = Autocomplete.getPlaceFromIntent(intent);\n                binding.response.setText(\n                        StringUtil.stringifyAutocompleteWidget(place, isDisplayRawResultsChecked()));\n            } else if (resultCode == AutocompleteActivity.RESULT_ERROR) {\n                Status status = Autocomplete.getStatusFromIntent(intent);\n                binding.response.setText(status.getStatusMessage());\n            }  // The user canceled the operation.\n\n        }\n\n        // Required because this class extends AppCompatActivity which extends FragmentActivity\n        // which implements this method to pass onActivityResult calls to child fragments\n        // (eg AutocompleteFragment).\n        super.onActivityResult(requestCode, resultCode, intent);\n    }\n\n    private void startAutocompleteActivity() {\n        Intent autocompleteIntent =\n                new Autocomplete.IntentBuilder(getMode(), getPlaceFields())\n                        .setInitialQuery(getQuery())\n                        .setHint(getHint())\n                        .setCountries(getCountries())\n                        .setLocationBias(getLocationBias())\n                        .setLocationRestriction(getLocationRestriction())\n                        .setTypesFilter(getTypesFilter())\n                        .build(this);\n        startActivityForResult(autocompleteIntent, AUTOCOMPLETE_REQUEST_CODE);\n    }\n\n    private void findAutocompletePredictions() {\n        setLoading(true);\n\n        FindAutocompletePredictionsRequest.Builder requestBuilder =\n                FindAutocompletePredictionsRequest.builder()\n                        .setQuery(getQuery())\n                        .setCountries(getCountries())\n                        .setOrigin((getOrigin()))\n                        .setLocationBias(getLocationBias())\n                        .setLocationRestriction(getLocationRestriction())\n                        .setTypesFilter(getTypesFilter());\n\n        if (isUseSessionTokenChecked()) {\n            requestBuilder.setSessionToken(AutocompleteSessionToken.newInstance());\n        }\n\n        Task<FindAutocompletePredictionsResponse> task =\n                placesClient.findAutocompletePredictions(requestBuilder.build());\n\n        task.addOnSuccessListener(\n                (response) ->\n                        binding.response.setText(StringUtil.stringify(response, isDisplayRawResultsChecked())));\n\n        task.addOnFailureListener(\n                (exception) -> {\n                    exception.printStackTrace();\n                    binding.response.setText(exception.getMessage());\n                });\n\n        task.addOnCompleteListener(response -> setLoading(false));\n    }\n\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n\n    private List<Place.Field> getPlaceFields() {\n        if (((CheckBox) findViewById(R.id.use_custom_fields)).isChecked()) {\n            return fieldSelector.getSelectedFields();\n        } else {\n            return fieldSelector.getAllFields();\n        }\n    }\n\n    @Nullable\n    private String getQuery() {\n        return getTextViewValue(R.id.autocomplete_query);\n    }\n\n    @Nullable\n    private String getHint() {\n        return getTextViewValue(R.id.autocomplete_hint);\n    }\n\n    private List<String> getCountries() {\n        String countryString = getTextViewValue(R.id.autocomplete_country);\n        if (TextUtils.isEmpty(countryString)) {\n            return new ArrayList<>();\n        }\n\n        return StringUtil.countriesStringToArrayList(countryString);\n    }\n\n    @Nullable\n    private String getTextViewValue(@IdRes int textViewResId) {\n        String value = ((TextView) findViewById(textViewResId)).getText().toString();\n        return TextUtils.isEmpty(value) ? null : value;\n    }\n\n    @Nullable\n    private LocationBias getLocationBias() {\n        return getBounds(\n                R.id.autocomplete_location_bias_south_west, R.id.autocomplete_location_bias_north_east);\n    }\n\n    @Nullable\n    private LocationRestriction getLocationRestriction() {\n        return getBounds(\n                R.id.autocomplete_location_restriction_south_west,\n                R.id.autocomplete_location_restriction_north_east);\n    }\n\n    @Nullable\n    private RectangularBounds getBounds(int resIdSouthWest, int resIdNorthEast) {\n        String southWest = ((TextView) findViewById(resIdSouthWest)).getText().toString();\n        String northEast = ((TextView) findViewById(resIdNorthEast)).getText().toString();\n        if (TextUtils.isEmpty(southWest) && TextUtils.isEmpty(northEast)) {\n            return null;\n        }\n\n        LatLngBounds bounds = StringUtil.convertToLatLngBounds(southWest, northEast);\n        if (bounds == null) {\n            showErrorAlert(R.string.error_alert_message_invalid_bounds);\n            return null;\n        }\n\n        return RectangularBounds.newInstance(bounds);\n    }\n\n    @Nullable\n    private LatLng getOrigin() {\n        String originStr =\n                ((TextView) findViewById(R.id.autocomplete_location_origin)).getText().toString();\n        if (TextUtils.isEmpty(originStr)) {\n            return null;\n        }\n\n        LatLng origin = StringUtil.convertToLatLng(originStr);\n        if (origin == null) {\n            showErrorAlert(R.string.error_alert_message_invalid_origin);\n            return null;\n        }\n\n        return origin;\n    }\n\n    private List<String> getTypesFilter() {\n        EditText typesFilterEditText = findViewById(R.id.autocomplete_types_filter_edittext);\n        return typesFilterEditText.isEnabled()\n                ? Arrays.asList(typesFilterEditText.getText().toString().split(\"[\\\\s,]+\"))\n                : new ArrayList<>();\n    }\n\n\n    private AutocompleteActivityMode getMode() {\n        boolean isOverlayMode =\n                ((CheckBox) findViewById(R.id.autocomplete_activity_overlay_mode)).isChecked();\n        return isOverlayMode ? AutocompleteActivityMode.OVERLAY : AutocompleteActivityMode.FULLSCREEN;\n    }\n\n    private boolean isDisplayRawResultsChecked() {\n        return ((CheckBox) findViewById(R.id.display_raw_results)).isChecked();\n    }\n\n    private boolean isUseSessionTokenChecked() {\n        return ((CheckBox) findViewById(R.id.autocomplete_use_session_token)).isChecked();\n    }\n\n    private void setLoading(boolean loading) {\n        findViewById(R.id.loading).setVisibility(loading ? View.VISIBLE : View.INVISIBLE);\n    }\n\n    private void showErrorAlert(@StringRes int messageResId) {\n        new AlertDialog.Builder(this)\n                .setTitle(R.string.error_alert_title)\n                .setMessage(messageResId)\n                .show();\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/PlaceDetailsAndPhotosActivity.java",
    "content": "/*\n * Copyright 2022 Google LLC\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\npackage com.example.placesdemo;\n\nimport com.bumptech.glide.Glide;\nimport com.example.placesdemo.databinding.PlaceDetailsAndPhotosActivityBinding;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.libraries.places.api.Places;\nimport com.google.android.libraries.places.api.model.PhotoMetadata;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.model.Place.Field;\nimport com.google.android.libraries.places.api.net.FetchPhotoRequest;\nimport com.google.android.libraries.places.api.net.FetchPhotoResponse;\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest;\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse;\nimport com.google.android.libraries.places.api.net.PlacesClient;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.CheckBox;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport java.util.List;\n\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport androidx.activity.EdgeToEdge;\n\n/**\n * Activity to demonstrate {@link PlacesClient#fetchPlace(FetchPlaceRequest)}.\n */\npublic class PlaceDetailsAndPhotosActivity extends AppCompatActivity {\n\n    private static final String FETCHED_PHOTO_KEY = \"photo_image\";\n    private PlacesClient placesClient;\n    private PhotoMetadata photo;\n    private FieldSelector fieldSelector;\n\n    private PlaceDetailsAndPhotosActivityBinding binding;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        // Enable edge-to-edge display. This must be called before calling super.onCreate().\n        EdgeToEdge.enable(this);\n        super.onCreate(savedInstanceState);\n\n        binding = PlaceDetailsAndPhotosActivityBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient(this);\n        if (savedInstanceState != null) {\n            photo = savedInstanceState.getParcelable(FETCHED_PHOTO_KEY);\n        }\n\n        binding.fetchPhotoCheckbox.setOnCheckedChangeListener(\n                (buttonView, isChecked) -> setPhotoSizingEnabled(isChecked));\n        binding.useCustomPhotoReference.setOnCheckedChangeListener(\n                (buttonView, isChecked) -> setCustomPhotoReferenceEnabled(isChecked));\n        fieldSelector =\n                new FieldSelector(\n                        binding.useCustomFields,\n                        binding.customFieldsList,\n                        savedInstanceState);\n\n        // Set listeners for programmatic Fetch Place\n        findViewById(R.id.fetch_place_and_photo_button).setOnClickListener(view -> fetchPlace());\n\n        // UI initialization\n        setLoading(false);\n        setPhotoSizingEnabled(binding.fetchPhotoCheckbox.isChecked());\n        setCustomPhotoReferenceEnabled(binding.useCustomPhotoReference.isChecked());\n        if (photo != null) {\n            fetchPhoto(photo);\n        }\n    }\n\n    @Override\n    protected void onSaveInstanceState(@NonNull Bundle bundle) {\n        super.onSaveInstanceState(bundle);\n        fieldSelector.onSaveInstanceState(bundle);\n        bundle.putParcelable(FETCHED_PHOTO_KEY, photo);\n    }\n\n    /**\n     * Fetches the {@link Place} specified via the UI and displays it. May also trigger {@link\n     * #fetchPhoto(PhotoMetadata)} if set in the UI.\n     */\n    private void fetchPlace() {\n        clearViews();\n\n        dismissKeyboard(binding.placeIdField);\n\n        final boolean isFetchPhotoChecked = isFetchPhotoChecked();\n        final boolean isFetchIconChecked = isFetchIconChecked();\n        List<Field> placeFields = getPlaceFields();\n        String customPhotoReference = getCustomPhotoReference();\n        if (!validateInputs(isFetchPhotoChecked, isFetchIconChecked, placeFields,\n                customPhotoReference)) {\n            return;\n        }\n\n        setLoading(true);\n\n        FetchPlaceRequest request = FetchPlaceRequest.newInstance(getPlaceId(), placeFields);\n        Task<FetchPlaceResponse> placeTask = placesClient.fetchPlace(request);\n\n        placeTask.addOnSuccessListener(\n\n                (response) -> {\n                    binding.response.setText(StringUtil.stringify(response, isDisplayRawResultsChecked()));\n                    if (isFetchPhotoChecked) {\n                        attemptFetchPhoto(response.getPlace());\n                    }\n                    if (isFetchIconChecked) {\n                        attemptFetchIcon(response.getPlace());\n                    }\n                });\n\n        placeTask.addOnFailureListener(\n                (exception) -> {\n                    exception.printStackTrace();\n                    binding.response.setText(exception.getMessage());\n                });\n\n        placeTask.addOnCompleteListener(response -> setLoading(false));\n    }\n\n    private void attemptFetchPhoto(Place place) {\n        List<PhotoMetadata> photoMetadatas = place.getPhotoMetadatas();\n        if (photoMetadatas != null && !photoMetadatas.isEmpty()) {\n            fetchPhoto(photoMetadatas.get(0));\n        }\n    }\n\n    private void attemptFetchIcon(Place place) {\n        binding.icon.setImageBitmap(null);\n        Integer bc = place.getIconBackgroundColor();\n        binding.icon.setBackgroundColor(bc == null ? Color.TRANSPARENT : bc);\n        String url = place.getIconMaskUrl();\n        Glide.with(this).load(url).into(binding.icon);\n    }\n\n    /**\n     * Fetches a Bitmap using the Places API and displays it.\n     *\n     * @param photoMetadata from a {@link Place} instance.\n     */\n    private void fetchPhoto(PhotoMetadata photoMetadata) {\n        photo = photoMetadata;\n\n        binding.photo.setImageBitmap(null);\n        setLoading(true);\n\n        String customPhotoReference = getCustomPhotoReference();\n        if (!TextUtils.isEmpty(customPhotoReference)) {\n            photoMetadata = PhotoMetadata.builder(customPhotoReference).build();\n        }\n\n        FetchPhotoRequest.Builder photoRequestBuilder = FetchPhotoRequest.builder(photoMetadata);\n\n        Integer maxWidth = readIntFromTextView(R.id.photo_max_width);\n        if (maxWidth != null) {\n            photoRequestBuilder.setMaxWidth(maxWidth);\n        }\n\n        Integer maxHeight = readIntFromTextView(R.id.photo_max_height);\n        if (maxHeight != null) {\n            photoRequestBuilder.setMaxHeight(maxHeight);\n        }\n\n        Task<FetchPhotoResponse> photoTask = placesClient.fetchPhoto(photoRequestBuilder.build());\n\n        photoTask.addOnSuccessListener(\n                response -> {\n                    Bitmap bitmap = response.getBitmap();\n                    binding.photo.setImageBitmap(bitmap);\n                    StringUtil.prepend(binding.photoMetadata, StringUtil.stringify(bitmap));\n                });\n\n        photoTask.addOnFailureListener(\n                exception -> {\n                    exception.printStackTrace();\n                    StringUtil.prepend(binding.response, \"Photo: \" + exception.getMessage());\n                });\n\n        photoTask.addOnCompleteListener(response -> setLoading(false));\n    }\n\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n\n    private void dismissKeyboard(EditText focusedEditText) {\n        InputMethodManager imm = (InputMethodManager) getSystemService(\n                Context.INPUT_METHOD_SERVICE);\n        imm.hideSoftInputFromWindow(focusedEditText.getWindowToken(), 0);\n    }\n\n    private boolean validateInputs(boolean isFetchPhotoChecked, boolean isFetchIconChecked,\n                                   List<Field> placeFields, String customPhotoReference) {\n        if (isFetchPhotoChecked) {\n            if (!placeFields.contains(Field.PHOTO_METADATAS)) {\n\n                binding.response.setText(\n                        \"'Also fetch photo?' is selected, but PHOTO_METADATAS Place Field is not.\");\n                return false;\n            }\n        } else if (!TextUtils.isEmpty(customPhotoReference)) {\n            binding.response.setText(\n                    \"Using 'Custom photo reference', but 'Also fetch photo?' is not selected.\");\n            return false;\n        }\n        if (isFetchIconChecked && !placeFields.contains(Field.ICON_MASK_URL)) {\n            binding.response.setText(R.string.fetch_icon_missing_fields_warning);\n            return false;\n        }\n\n        return true;\n    }\n\n    private String getPlaceId() {\n        return ((TextView) findViewById(R.id.place_id_field)).getText().toString();\n    }\n\n    private List<Field> getPlaceFields() {\n        if (((CheckBox) findViewById(R.id.use_custom_fields)).isChecked()) {\n            return fieldSelector.getSelectedFields();\n        } else {\n            return fieldSelector.getAllFields();\n        }\n    }\n\n    private boolean isDisplayRawResultsChecked() {\n        return ((CheckBox) findViewById(R.id.display_raw_results)).isChecked();\n    }\n\n    private boolean isFetchPhotoChecked() {\n        return ((CheckBox) findViewById(R.id.fetch_photo_checkbox)).isChecked();\n    }\n\n    private boolean isFetchIconChecked() {\n        return ((CheckBox) findViewById(R.id.fetch_icon_checkbox)).isChecked();\n    }\n\n    private String getCustomPhotoReference() {\n        return ((TextView) findViewById(R.id.custom_photo_reference)).getText().toString();\n    }\n\n    private void setPhotoSizingEnabled(boolean enabled) {\n        setEnabled(R.id.photo_max_width, enabled);\n        setEnabled(R.id.photo_max_height, enabled);\n    }\n\n    private void setCustomPhotoReferenceEnabled(boolean enabled) {\n        setEnabled(R.id.custom_photo_reference, enabled);\n    }\n\n    private void setEnabled(@IdRes int resId, boolean enabled) {\n        TextView view = findViewById(resId);\n        view.setEnabled(enabled);\n        view.setText(\"\");\n    }\n\n    @Nullable\n    private Integer readIntFromTextView(@IdRes int resId) {\n        Integer intValue = null;\n        View view = findViewById(resId);\n\n        if (view instanceof TextView) {\n            CharSequence contents = ((TextView) view).getText();\n            if (!TextUtils.isEmpty(contents)) {\n                try {\n                    intValue = Integer.parseInt(contents.toString());\n                } catch (NumberFormatException e) {\n                    showErrorAlert(R.string.error_alert_message_invalid_photo_size);\n                }\n            }\n        }\n\n        return intValue;\n    }\n\n    private void showErrorAlert(@StringRes int messageResId) {\n        new AlertDialog.Builder(this)\n                .setTitle(R.string.error_alert_title)\n                .setMessage(messageResId)\n                .show();\n    }\n\n    private void setLoading(boolean loading) {\n        findViewById(R.id.loading).setVisibility(loading ? View.VISIBLE : View.INVISIBLE);\n    }\n\n    private void clearViews() {\n        binding.response.setText(null);\n        binding.photo.setImageBitmap(null);\n        binding.photoMetadata.setText(null);\n        binding.icon.setImageBitmap(null);\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/PlaceIsOpenActivity.java",
    "content": "/*\n * Copyright 2025 Google LLC\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.example.placesdemo;\n\nimport android.annotation.SuppressLint;\nimport android.app.DatePickerDialog;\nimport android.app.TimePickerDialog;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.AdapterView;\nimport android.widget.AdapterView.OnItemSelectedListener;\nimport android.widget.ArrayAdapter;\nimport android.widget.CheckBox;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport com.example.placesdemo.databinding.PlaceIsOpenActivityBinding;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.libraries.places.api.Places;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.model.Place.Field;\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest;\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse;\nimport com.google.android.libraries.places.api.net.IsOpenRequest;\nimport com.google.android.libraries.places.api.net.IsOpenResponse;\nimport com.google.android.libraries.places.api.net.PlacesClient;\n\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.TimeZone;\n\nimport androidx.activity.EdgeToEdge;\n\n/**\n * Activity to demonstrate {@link PlacesClient#isOpen(IsOpenRequest)}.\n */\npublic class PlaceIsOpenActivity extends AppCompatActivity {\n    private final String defaultTimeZone = \"America/Los_Angeles\";\n    @NonNull\n    private Calendar isOpenCalendar = Calendar.getInstance();\n    private PlaceIsOpenActivityBinding binding;\n\n    private FieldSelector fieldSelector;\n    private PlacesClient placesClient;\n    private Place place;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        // Enable edge-to-edge display. This must be called before calling super.onCreate().\n        EdgeToEdge.enable(this);\n        super.onCreate(savedInstanceState);\n\n        binding = PlaceIsOpenActivityBinding.inflate(getLayoutInflater());\n        View rootView = binding.getRoot();\n        setContentView(rootView);\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient(/* context= */ this);\n\n        fieldSelector =\n                new FieldSelector(\n                        binding.checkBoxUseCustomFields,\n                        binding.textViewCustomFieldsList,\n                        savedInstanceState);\n\n        binding.buttonFetchPlace.setOnClickListener(view -> fetchPlace());\n        binding.buttonIsOpen.setOnClickListener(view -> isOpenByPlaceId());\n\n        isOpenCalendar = Calendar.getInstance(TimeZone.getTimeZone(defaultTimeZone));\n\n        // UI initialization\n        setLoading(false);\n        initializeSpinnerAndAddListener();\n        addIsOpenDateSelectionListener();\n        addIsOpenTimeSelectionListener();\n\n        updateIsOpenDate();\n        updateIsOpenTime();\n    }\n\n    @Override\n    protected void onSaveInstanceState(@NonNull Bundle bundle) {\n        super.onSaveInstanceState(bundle);\n        fieldSelector.onSaveInstanceState(bundle);\n    }\n\n    /**\n     * Get details about the Place ID listed in the input field, then check if the Place is open.\n     */\n    private void fetchPlace() {\n        clearViews();\n        dismissKeyboard(binding.editTextPlaceId);\n        setLoading(true);\n\n        List<Field> placeFields = getPlaceFields();\n        FetchPlaceRequest request = FetchPlaceRequest.newInstance(getPlaceId(), placeFields);\n        Task<FetchPlaceResponse> placeTask = placesClient.fetchPlace(request);\n\n        placeTask.addOnSuccessListener(\n                (response) -> {\n                    place = response.getPlace();\n                    isOpenByPlaceObject(place);\n                });\n\n        placeTask.addOnFailureListener(\n                (exception) -> {\n                    exception.printStackTrace();\n                    binding.textViewResponse.setText(exception.getMessage());\n                });\n\n        placeTask.addOnCompleteListener(response -> setLoading(false));\n    }\n\n    /**\n     * Check if the place is open at the time specified in the input fields.\n     * Requires a Place object that includes Place.Field.ID\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private void isOpenByPlaceObject(Place place) {\n        clearViews();\n        dismissKeyboard(binding.editTextPlaceId);\n        setLoading(true);\n\n        IsOpenRequest request;\n\n        try {\n            request = IsOpenRequest.newInstance(place, isOpenCalendar.getTimeInMillis());\n        } catch (IllegalArgumentException e) {\n            e.printStackTrace();\n            binding.textViewResponse.setText(e.getMessage());\n            setLoading(false);\n            return;\n        }\n\n        Task<IsOpenResponse> placeTask = placesClient.isOpen(request);\n\n        placeTask.addOnSuccessListener(\n                (response) -> binding.textViewResponse.setText(\"Is place open? \"\n                                                 + response.isOpen()\n                                                 + \"\\nExtra place details: \\n\"\n                                                 + StringUtil.stringify(place)));\n\n        placeTask.addOnFailureListener(\n                (exception) -> {\n                    exception.printStackTrace();\n                    binding.textViewResponse.setText(exception.getMessage());\n                });\n\n        placeTask.addOnCompleteListener(response -> setLoading(false));\n    }\n\n    /**\n     * Check if the place is open at the time specified in the input fields.\n     * Use the Place ID in the input field for the isOpenRequest.\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private void isOpenByPlaceId() {\n        clearViews();\n        dismissKeyboard(binding.editTextPlaceId);\n        setLoading(true);\n\n        IsOpenRequest request;\n\n        try {\n            request = IsOpenRequest.newInstance(getPlaceId(), isOpenCalendar.getTimeInMillis());\n        } catch (IllegalArgumentException e) {\n            e.printStackTrace();\n            binding.textViewResponse.setText(e.getMessage());\n            setLoading(false);\n            return;\n        }\n\n        Task<IsOpenResponse> placeTask = placesClient.isOpen(request);\n\n        placeTask.addOnSuccessListener(\n                (response) -> binding.textViewResponse.setText(\"Is place open? \" + response.isOpen()));\n\n        placeTask.addOnFailureListener(\n                (exception) -> {\n                    exception.printStackTrace();\n                    binding.textViewResponse.setText(exception.getMessage());\n                });\n\n        placeTask.addOnCompleteListener(response -> setLoading(false));\n    }\n\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n\n    private void dismissKeyboard(EditText focusedEditText) {\n        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);\n        imm.hideSoftInputFromWindow(focusedEditText.getWindowToken(), 0);\n    }\n\n    private String getPlaceId() {\n        return ((TextView) binding.editTextPlaceId).getText().toString();\n    }\n\n    /**\n     * Fetch the fields necessary for an isOpen request, unless user has checked the box to\n     * select a custom list of fields. Also fetches name and address for display text.\n     */\n    private List<Field> getPlaceFields() {\n        if (((CheckBox) binding.checkBoxUseCustomFields).isChecked()) {\n            return fieldSelector.getSelectedFields();\n        } else {\n            return new ArrayList<>(Arrays.asList(\n                    Field.FORMATTED_ADDRESS,\n                    Field.BUSINESS_STATUS,\n                    Field.CURRENT_OPENING_HOURS,\n                    Field.ID,\n                    Field.DISPLAY_NAME,\n                    Field.OPENING_HOURS,\n                    Field.UTC_OFFSET\n            ));\n        }\n    }\n\n    private void setLoading(boolean loading) {\n        binding.progressBarLoading.setVisibility(loading ? View.VISIBLE : View.INVISIBLE);\n    }\n\n    private void clearViews() {\n        binding.textViewResponse.setText(null);\n    }\n\n    private void initializeSpinnerAndAddListener() {\n        ArrayAdapter<String> adapter =\n                new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, TimeZone.getAvailableIDs());\n        binding.spinnerTimeZones.setAdapter(adapter);\n        binding.spinnerTimeZones.setSelection(adapter.getPosition(defaultTimeZone));\n\n        binding.spinnerTimeZones.setOnItemSelectedListener(\n                new OnItemSelectedListener() {\n                    @Override\n                    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                        String timeZone = parent.getItemAtPosition(position).toString();\n                        isOpenCalendar.setTimeZone(TimeZone.getTimeZone(timeZone));\n                        updateIsOpenDate();\n                        updateIsOpenTime();\n                    }\n\n                    @Override\n                    public void onNothingSelected(AdapterView<?> parent) {\n                    }\n                });\n    }\n\n    private void addIsOpenDateSelectionListener() {\n        DatePickerDialog.OnDateSetListener listener =\n                (view, year, month, day) -> {\n                    isOpenCalendar.set(Calendar.YEAR, year);\n                    isOpenCalendar.set(Calendar.MONTH, month);\n                    isOpenCalendar.set(Calendar.DAY_OF_MONTH, day);\n                    updateIsOpenDate();\n                };\n\n        binding.editTextIsOpenDate.setOnClickListener(\n                view -> new DatePickerDialog(\n                        PlaceIsOpenActivity.this,\n                        listener,\n                        isOpenCalendar.get(Calendar.YEAR),\n                        isOpenCalendar.get(Calendar.MONTH),\n                        isOpenCalendar.get(Calendar.DAY_OF_MONTH))\n                        .show());\n    }\n\n    private void updateIsOpenDate() {\n        SimpleDateFormat dateFormat = new SimpleDateFormat(\"MM/dd/yy\", Locale.US);\n        binding.editTextIsOpenDate.setText(dateFormat.format(isOpenCalendar.getTime()));\n    }\n\n    private void addIsOpenTimeSelectionListener() {\n        TimePickerDialog.OnTimeSetListener listener =\n                (view, hourOfDay, minute) -> {\n                    isOpenCalendar.set(Calendar.HOUR_OF_DAY, hourOfDay);\n                    isOpenCalendar.set(Calendar.MINUTE, minute);\n                    updateIsOpenTime();\n                };\n\n        binding.editTextIsOpenTime.setOnClickListener(\n                view -> new TimePickerDialog(\n                        PlaceIsOpenActivity.this,\n                        listener,\n                        isOpenCalendar.get(Calendar.HOUR_OF_DAY),\n                        isOpenCalendar.get(Calendar.MINUTE),\n                        true)\n                        .show());\n    }\n\n    private void updateIsOpenTime() {\n        String formattedHour =\n                String.format(Locale.getDefault(), \"%02d\", isOpenCalendar.get(Calendar.HOUR_OF_DAY));\n        String formattedMinutes =\n                String.format(Locale.getDefault(), \"%02d\", isOpenCalendar.get(Calendar.MINUTE));\n        binding.editTextIsOpenTime.setText(\n                String.format(Locale.getDefault(), \"%s:%s\", formattedHour, formattedMinutes));\n    }\n}"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/PlacesDemoApplication.java",
    "content": "/*\n * Copyright 2025 Google LLC\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.example.placesdemo;\n\nimport android.app.Application;\nimport android.widget.Toast;\n\nimport com.google.android.libraries.places.api.Places;\n\npublic class PlacesDemoApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        final String apiKey = BuildConfig.PLACES_API_KEY;\n\n        if (apiKey.equals(\"\")) {\n            Toast.makeText(this, getString(R.string.error_api_key), Toast.LENGTH_LONG).show();\n            return;\n        }\n\n        Places.initialize(getApplicationContext(), apiKey);\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/StringUtil.java",
    "content": "/*\n * Copyright 2018 Google LLC\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\npackage com.example.placesdemo;\n\nimport android.graphics.Bitmap;\nimport android.text.TextUtils;\nimport android.widget.TextView;\n\nimport androidx.annotation.Nullable;\n\nimport com.google.android.gms.maps.model.LatLng;\nimport com.google.android.gms.maps.model.LatLngBounds;\nimport com.google.android.libraries.places.api.model.AutocompletePrediction;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.model.PlaceLikelihood;\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse;\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse;\nimport com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Utility class for converting objects to viewable strings and back.\n */\npublic final class StringUtil {\n\n  private static final String FIELD_SEPARATOR = \"\\n\\t\";\n  private static final String RESULT_SEPARATOR = \"\\n---\\n\\t\";\n\n  static void prepend(TextView textView, String prefix) {\n    textView.setText(prefix + \"\\n\\n\" + textView.getText());\n  }\n\n  @Nullable\n  static LatLngBounds convertToLatLngBounds(\n      @Nullable String southWest, @Nullable String northEast) {\n    LatLng soundWestLatLng = convertToLatLng(southWest);\n    LatLng northEastLatLng = convertToLatLng(northEast);\n    if (soundWestLatLng == null || northEast == null) {\n      return null;\n    }\n\n    return new LatLngBounds(soundWestLatLng, northEastLatLng);\n  }\n\n  @Nullable\n  static LatLng convertToLatLng(@Nullable String value) {\n    if (TextUtils.isEmpty(value)) {\n      return null;\n    }\n\n    String[] split = value.split(\",\", -1);\n    if (split.length != 2) {\n      return null;\n    }\n\n    try {\n      return new LatLng(Double.parseDouble(split[0]), Double.parseDouble(split[1]));\n    } catch (NullPointerException | NumberFormatException e) {\n      return null;\n    }\n  }\n\n  static List<String> countriesStringToArrayList(String countriesString) {\n\n    // Allow these delimiters: , ; | / \\\n    return Arrays.asList(countriesString\n            .replaceAll(\"\\\\s\", \"|\")\n            .split(\"[,;|/\\\\\\\\]\",-1));\n  }\n\n  static String stringify(FindAutocompletePredictionsResponse response, boolean raw) {\n    StringBuilder builder = new StringBuilder();\n\n    builder\n        .append(response.getAutocompletePredictions().size())\n        .append(\" Autocomplete Predictions Results:\");\n\n    if (raw) {\n      builder.append(RESULT_SEPARATOR);\n      appendListToStringBuilder(builder, response.getAutocompletePredictions());\n    } else {\n      for (AutocompletePrediction autocompletePrediction : response.getAutocompletePredictions()) {\n        builder\n            .append(RESULT_SEPARATOR)\n            .append(autocompletePrediction.getFullText(/* matchStyle */ null));\n      }\n    }\n\n    return builder.toString();\n  }\n\n  static String stringify(FetchPlaceResponse response, boolean raw) {\n    StringBuilder builder = new StringBuilder();\n\n    builder.append(\"Fetch Place Result:\").append(RESULT_SEPARATOR);\n    if (raw) {\n      builder.append(response.getPlace());\n    } else {\n      builder.append(stringify(response.getPlace()));\n    }\n\n    return builder.toString();\n  }\n\n  static String stringify(FindCurrentPlaceResponse response, boolean raw) {\n    StringBuilder builder = new StringBuilder();\n\n    builder.append(response.getPlaceLikelihoods().size()).append(\" Current Place Results:\");\n\n    if (raw) {\n      builder.append(RESULT_SEPARATOR);\n      appendListToStringBuilder(builder, response.getPlaceLikelihoods());\n    } else {\n      for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {\n        builder\n            .append(RESULT_SEPARATOR)\n            .append(\"Likelihood: \")\n            .append(placeLikelihood.getLikelihood())\n            .append(FIELD_SEPARATOR)\n            .append(\"Place: \")\n            .append(stringify(placeLikelihood.getPlace()));\n      }\n    }\n\n    return builder.toString();\n  }\n\n  static String stringify(Place place) {\n    return place.getDisplayName()\n            + \" (\"\n            + place.getFormattedAddress()\n            + \")\";\n  }\n\n  static String stringify(Bitmap bitmap) {\n    StringBuilder builder = new StringBuilder();\n\n    builder\n        .append(\"Photo size (width x height)\")\n        .append(RESULT_SEPARATOR)\n        .append(bitmap.getWidth())\n        .append(\", \")\n        .append(bitmap.getHeight());\n\n    return builder.toString();\n  }\n\n  public static String stringifyAutocompleteWidget(Place place, boolean raw) {\n    StringBuilder builder = new StringBuilder();\n\n    builder.append(\"Autocomplete Widget Result:\").append(RESULT_SEPARATOR);\n\n    if (raw) {\n      builder.append(place);\n    } else {\n      builder.append(stringify(place));\n    }\n\n    return builder.toString();\n  }\n\n  private static <T> void appendListToStringBuilder(StringBuilder builder, List<T> items) {\n    if (items.isEmpty()) {\n      return;\n    }\n\n    builder.append(items.get(0));\n    for (int i = 1; i < items.size(); i++) {\n      builder.append(RESULT_SEPARATOR);\n      builder.append(items.get(i));\n    }\n  }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/model/AddressType.java",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model;\n\n/**\n * The Address types. Please see <a\n * href=\"https://developers.google.com/maps/documentation/geocoding/intro#Types\">Address Types and\n * Address Component Types</a> for more detail. Some addresses contain additional place categories.\n * Please see <a href=\"https://developers.google.com/places/supported_types\">Place Types</a> for\n * more detail.\n */\npublic enum AddressType {\n\n  /** A precise street address. */\n  STREET_ADDRESS(\"street_address\"),\n\n  /** A precise street number. */\n  STREET_NUMBER(\"street_number\"),\n\n  /** The floor in the address of the building. */\n  FLOOR(\"floor\"),\n\n  /** The room in the address of the building */\n  ROOM(\"room\"),\n\n  /** A specific mailbox. */\n  POST_BOX(\"post_box\"),\n\n  /** A named route (such as \"US 101\"). */\n  ROUTE(\"route\"),\n\n  /** A major intersection, usually of two major roads. */\n  INTERSECTION(\"intersection\"),\n\n  /** A continent. */\n  CONTINENT(\"continent\"),\n\n  /** A political entity. Usually, this type indicates a polygon of some civil administration. */\n  POLITICAL(\"political\"),\n\n  /** The national political entity, typically the highest order type returned by the Geocoder. */\n  COUNTRY(\"country\"),\n\n  /**\n   * A first-order civil entity below the country level. Within the United States, these\n   * administrative levels are states. Not all nations exhibit these administrative levels.\n   */\n  ADMINISTRATIVE_AREA_LEVEL_1(\"administrative_area_level_1\"),\n\n  /**\n   * A second-order civil entity below the country level. Within the United States, these\n   * administrative levels are counties. Not all nations exhibit these administrative levels.\n   */\n  ADMINISTRATIVE_AREA_LEVEL_2(\"administrative_area_level_2\"),\n\n  /**\n   * A third-order civil entity below the country level. This type indicates a minor civil division.\n   * Not all nations exhibit these administrative levels.\n   */\n  ADMINISTRATIVE_AREA_LEVEL_3(\"administrative_area_level_3\"),\n\n  /**\n   * A fourth-order civil entity below the country level. This type indicates a minor civil\n   * division. Not all nations exhibit these administrative levels.\n   */\n  ADMINISTRATIVE_AREA_LEVEL_4(\"administrative_area_level_4\"),\n\n  /**\n   * A fifth-order civil entity below the country level. This type indicates a minor civil division.\n   * Not all nations exhibit these administrative levels.\n   */\n  ADMINISTRATIVE_AREA_LEVEL_5(\"administrative_area_level_5\"),\n\n  /** A commonly-used alternative name for the entity. */\n  COLLOQUIAL_AREA(\"colloquial_area\"),\n\n  /** An incorporated city or town political entity. */\n  LOCALITY(\"locality\"),\n\n  /**\n   * A specific type of Japanese locality, used to facilitate distinction between multiple locality\n   * components within a Japanese address.\n   */\n  WARD(\"ward\"),\n\n  /**\n   * A first-order civil entity below a locality. Some locations may receive one of the additional\n   * types: {@code SUBLOCALITY_LEVEL_1} to {@code SUBLOCALITY_LEVEL_5}. Each sublocality level is a\n   * civil entity. Larger numbers indicate a smaller geographic area.\n   */\n  SUBLOCALITY(\"sublocality\"),\n  SUBLOCALITY_LEVEL_1(\"sublocality_level_1\"),\n  SUBLOCALITY_LEVEL_2(\"sublocality_level_2\"),\n  SUBLOCALITY_LEVEL_3(\"sublocality_level_3\"),\n  SUBLOCALITY_LEVEL_4(\"sublocality_level_4\"),\n  SUBLOCALITY_LEVEL_5(\"sublocality_level_5\"),\n\n  /** A named neighborhood. */\n  NEIGHBORHOOD(\"neighborhood\"),\n\n  /** A named location, usually a building or collection of buildings with a common name. */\n  PREMISE(\"premise\"),\n\n  /**\n   * A first-order entity below a named location, usually a singular building within a collection of\n   * buildings with a common name.\n   */\n  SUBPREMISE(\"subpremise\"),\n\n  /** A postal code as used to address postal mail within the country. */\n  POSTAL_CODE(\"postal_code\"),\n\n  /** A postal code prefix as used to address postal mail within the country. */\n  POSTAL_CODE_PREFIX(\"postal_code_prefix\"),\n\n  /** A postal code prefix as used to address postal mail within the country. */\n  POSTAL_CODE_SUFFIX(\"postal_code_suffix\"),\n\n  /** A prominent natural feature. */\n  NATURAL_FEATURE(\"natural_feature\"),\n\n  /** An airport. */\n  AIRPORT(\"airport\"),\n\n  /** A university. */\n  UNIVERSITY(\"university\"),\n\n  /** A named park. */\n  PARK(\"park\"),\n\n  /** A museum. */\n  MUSEUM(\"museum\"),\n\n  /**\n   * A named point of interest. Typically, these \"POI\"s are prominent local entities that don't\n   * easily fit in another category, such as \"Empire State Building\" or \"Statue of Liberty.\"\n   */\n  POINT_OF_INTEREST(\"point_of_interest\"),\n\n  /** A place that has not yet been categorized. */\n  ESTABLISHMENT(\"establishment\"),\n\n  /** The location of a bus stop. */\n  BUS_STATION(\"bus_station\"),\n\n  /** The location of a train station. */\n  TRAIN_STATION(\"train_station\"),\n\n  /** The location of a subway station. */\n  SUBWAY_STATION(\"subway_station\"),\n\n  /** The location of a transit station. */\n  TRANSIT_STATION(\"transit_station\"),\n\n  /** The location of a light rail station. */\n  LIGHT_RAIL_STATION(\"light_rail_station\"),\n\n  /** The location of a church. */\n  CHURCH(\"church\"),\n\n  /** The location of a primary school. */\n  PRIMARY_SCHOOL(\"primary_school\"),\n\n  /** The location of a secondary school. */\n  SECONDARY_SCHOOL(\"secondary_school\"),\n\n  /** The location of a finance institute. */\n  FINANCE(\"finance\"),\n\n  /** The location of a post office. */\n  POST_OFFICE(\"post_office\"),\n\n  /** The location of a place of worship. */\n  PLACE_OF_WORSHIP(\"place_of_worship\"),\n\n  /**\n   * A grouping of geographic areas, such as locality and sublocality, used for mailing addresses in\n   * some countries.\n   */\n  POSTAL_TOWN(\"postal_town\"),\n\n  /** Currently not a documented return type. */\n  SYNAGOGUE(\"synagogue\"),\n\n  /** Currently not a documented return type. */\n  FOOD(\"food\"),\n\n  /** Currently not a documented return type. */\n  GROCERY_OR_SUPERMARKET(\"grocery_or_supermarket\"),\n\n  /** Currently not a documented return type. */\n  STORE(\"store\"),\n\n  /** The location of a drugstore. */\n  DRUGSTORE(\"drugstore\"),\n\n  /** Currently not a documented return type. */\n  LAWYER(\"lawyer\"),\n\n  /** Currently not a documented return type. */\n  HEALTH(\"health\"),\n\n  /** Currently not a documented return type. */\n  INSURANCE_AGENCY(\"insurance_agency\"),\n\n  /** Currently not a documented return type. */\n  GAS_STATION(\"gas_station\"),\n\n  /** Currently not a documented return type. */\n  CAR_DEALER(\"car_dealer\"),\n\n  /** Currently not a documented return type. */\n  CAR_REPAIR(\"car_repair\"),\n\n  /** Currently not a documented return type. */\n  MEAL_TAKEAWAY(\"meal_takeaway\"),\n\n  /** Currently not a documented return type. */\n  FURNITURE_STORE(\"furniture_store\"),\n\n  /** Currently not a documented return type. */\n  HOME_GOODS_STORE(\"home_goods_store\"),\n\n  /** Currently not a documented return type. */\n  SHOPPING_MALL(\"shopping_mall\"),\n\n  /** Currently not a documented return type. */\n  GYM(\"gym\"),\n\n  /** Currently not a documented return type. */\n  ACCOUNTING(\"accounting\"),\n\n  /** Currently not a documented return type. */\n  MOVING_COMPANY(\"moving_company\"),\n\n  /** Currently not a documented return type. */\n  LODGING(\"lodging\"),\n\n  /** Currently not a documented return type. */\n  STORAGE(\"storage\"),\n\n  /** Currently not a documented return type. */\n  CASINO(\"casino\"),\n\n  /** Currently not a documented return type. */\n  PARKING(\"parking\"),\n\n  /** Currently not a documented return type. */\n  STADIUM(\"stadium\"),\n\n  /** Currently not a documented return type. */\n  TRAVEL_AGENCY(\"travel_agency\"),\n\n  /** Currently not a documented return type. */\n  NIGHT_CLUB(\"night_club\"),\n\n  /** Currently not a documented return type. */\n  BEAUTY_SALON(\"beauty_salon\"),\n\n  /** Currently not a documented return type. */\n  HAIR_CARE(\"hair_care\"),\n\n  /** Currently not a documented return type. */\n  SPA(\"spa\"),\n\n  /** Currently not a documented return type. */\n  SHOE_STORE(\"shoe_store\"),\n\n  /** Currently not a documented return type. */\n  BAKERY(\"bakery\"),\n\n  /** Currently not a documented return type. */\n  PHARMACY(\"pharmacy\"),\n\n  /** Currently not a documented return type. */\n  SCHOOL(\"school\"),\n\n  /** Currently not a documented return type. */\n  BOOK_STORE(\"book_store\"),\n\n  /** Currently not a documented return type. */\n  DEPARTMENT_STORE(\"department_store\"),\n\n  /** Currently not a documented return type. */\n  RESTAURANT(\"restaurant\"),\n\n  /** Currently not a documented return type. */\n  REAL_ESTATE_AGENCY(\"real_estate_agency\"),\n\n  /** Currently not a documented return type. */\n  BAR(\"bar\"),\n\n  /** Currently not a documented return type. */\n  DOCTOR(\"doctor\"),\n\n  /** Currently not a documented return type. */\n  HOSPITAL(\"hospital\"),\n\n  /** Currently not a documented return type. */\n  FIRE_STATION(\"fire_station\"),\n\n  /** Currently not a documented return type. */\n  SUPERMARKET(\"supermarket\"),\n\n  /** Currently not a documented return type. */\n  CITY_HALL(\"city_hall\"),\n\n  /** Currently not a documented return type. */\n  LOCAL_GOVERNMENT_OFFICE(\"local_government_office\"),\n\n  /** Currently not a documented return type. */\n  ATM(\"atm\"),\n\n  /** Currently not a documented return type. */\n  BANK(\"bank\"),\n\n  /** Currently not a documented return type. */\n  LIBRARY(\"library\"),\n\n  /** Currently not a documented return type. */\n  CAR_WASH(\"car_wash\"),\n\n  /** Currently not a documented return type. */\n  HARDWARE_STORE(\"hardware_store\"),\n\n  /** Currently not a documented return type. */\n  AMUSEMENT_PARK(\"amusement_park\"),\n\n  /** Currently not a documented return type. */\n  AQUARIUM(\"aquarium\"),\n\n  /** Currently not a documented return type. */\n  ART_GALLERY(\"art_gallery\"),\n\n  /** Currently not a documented return type. */\n  BICYCLE_STORE(\"bicycle_store\"),\n\n  /** Currently not a documented return type. */\n  BOWLING_ALLEY(\"bowling_alley\"),\n\n  /** Currently not a documented return type. */\n  CAFE(\"cafe\"),\n\n  /** Currently not a documented return type. */\n  CAMPGROUND(\"campground\"),\n\n  /** Currently not a documented return type. */\n  CAR_RENTAL(\"car_rental\"),\n\n  /** Currently not a documented return type. */\n  CEMETERY(\"cemetery\"),\n\n  /** Currently not a documented return type. */\n  CLOTHING_STORE(\"clothing_store\"),\n\n  /** Currently not a documented return type. */\n  CONVENIENCE_STORE(\"convenience_store\"),\n\n  /** Currently not a documented return type. */\n  COURTHOUSE(\"courthouse\"),\n\n  /** Currently not a documented return type. */\n  DENTIST(\"dentist\"),\n\n  /** Currently not a documented return type. */\n  ELECTRICIAN(\"electrician\"),\n\n  /** Currently not a documented return type. */\n  ELECTRONICS_STORE(\"electronics_store\"),\n\n  /** Currently not a documented return type. */\n  EMBASSY(\"embassy\"),\n\n  /** Currently not a documented return type. */\n  FLORIST(\"florist\"),\n\n  /** Currently not a documented return type. */\n  FUNERAL_HOME(\"funeral_home\"),\n\n  /** Currently not a documented return type. */\n  GENERAL_CONTRACTOR(\"general_contractor\"),\n\n  /** Currently not a documented return type. */\n  HINDU_TEMPLE(\"hindu_temple\"),\n\n  /** Currently not a documented return type. */\n  JEWELRY_STORE(\"jewelry_store\"),\n\n  /** Currently not a documented return type. */\n  LAUNDRY(\"laundry\"),\n\n  /** Currently not a documented return type. */\n  LIQUOR_STORE(\"liquor_store\"),\n\n  /** Currently not a documented return type. */\n  LOCKSMITH(\"locksmith\"),\n\n  /** Currently not a documented return type. */\n  MEAL_DELIVERY(\"meal_delivery\"),\n\n  /** Currently not a documented return type. */\n  MOSQUE(\"mosque\"),\n\n  /** Currently not a documented return type. */\n  MOVIE_RENTAL(\"movie_rental\"),\n\n  /** Currently not a documented return type. */\n  MOVIE_THEATER(\"movie_theater\"),\n\n  /** Currently not a documented return type. */\n  PAINTER(\"painter\"),\n\n  /** Currently not a documented return type. */\n  PET_STORE(\"pet_store\"),\n\n  /** Currently not a documented return type. */\n  PHYSIOTHERAPIST(\"physiotherapist\"),\n\n  /** Currently not a documented return type. */\n  PLUMBER(\"plumber\"),\n\n  /** Currently not a documented return type. */\n  POLICE(\"police\"),\n\n  /** Currently not a documented return type. */\n  ROOFING_CONTRACTOR(\"roofing_contractor\"),\n\n  /** Currently not a documented return type. */\n  RV_PARK(\"rv_park\"),\n\n  /** Currently not a documented return type. */\n  TAXI_STAND(\"taxi_stand\"),\n\n  /** Currently not a documented return type. */\n  VETERINARY_CARE(\"veterinary_care\"),\n\n  /** Currently not a documented return type. */\n  ZOO(\"zoo\"),\n\n  /** An archipelago. */\n  ARCHIPELAGO(\"archipelago\"),\n\n  /** A tourist attraction */\n  TOURIST_ATTRACTION(\"tourist_attraction\"),\n\n  /** Currently not a documented return type. */\n  TOWN_SQUARE(\"town_square\"),\n\n  /**\n   * Indicates an unknown address type returned by the server. The Java Client for Google Maps\n   * Services should be updated to support the new value.\n   */\n  UNKNOWN(\"unknown\");\n\n  private final String addressType;\n\n  AddressType(final String addressType) {\n    this.addressType = addressType;\n  }\n\n  @Override\n  public String toString() {\n    return addressType;\n  }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/model/AutocompleteEditText.java",
    "content": "/*\n * Copyright 2025 Google LLC\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.example.placesdemo.model;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\n\npublic class AutocompleteEditText extends androidx.appcompat.widget.AppCompatEditText {\n    public AutocompleteEditText(Context context) {\n        super(context);\n    }\n\n    public AutocompleteEditText(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        super.onTouchEvent(event);\n\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN:\n                return true;\n\n            case MotionEvent.ACTION_UP:\n                performClick();\n                return true;\n        }\n        return false;\n    }\n\n    // Because we call this from onTouchEvent, this code will be executed for both\n    // normal touch events and for when the system calls this using Accessibility\n    @Override\n    public boolean performClick() {\n        super.performClick();\n        return true;\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/model/Bounds.java",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model;\n\nimport com.google.android.gms.maps.model.LatLng;\nimport java.io.Serializable;\n\n/** The northeast and southwest points that delineate the outer bounds of a map. */\npublic class Bounds implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n  /** The northeast corner of the bounding box. */\n  public LatLng northeast;\n  /** The southwest corner of the bounding box. */\n  public LatLng southwest;\n\n  @Override\n  public String toString() {\n    return String.format(\"[%s, %s]\", northeast, southwest);\n  }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/model/GeocodingResult.java",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model;\n\nimport androidx.annotation.NonNull;\n\nimport java.io.Serializable;\nimport java.util.Arrays;\n\npublic class GeocodingResult implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * The human-readable address of this location.\n     *\n     * <p>Often this address is equivalent to the \"postal address,\" which sometimes differs from\n     * country to country. (Note that some countries, such as the United Kingdom, do not allow\n     * distribution of true postal addresses due to licensing restrictions.) This address is generally\n     * composed of one or more address components. For example, the address \"111 8th Avenue, New York,\n     * NY\" contains separate address components for \"111\" (the street number, \"8th Avenue\" (the\n     * route), \"New York\" (the city) and \"NY\" (the US state). These address components contain\n     * additional information.\n     */\n    public String formattedAddress;\n\n    /**\n     * All the localities contained in a postal code. This is only present when the result is a postal\n     * code that contains multiple localities.\n     */\n    public String[] postcodeLocalities;\n\n    /** Location information for this result. */\n    public Geometry geometry;\n\n    /**\n     * The types of the returned result. This array contains a set of zero or more tags identifying\n     * the type of feature returned in the result. For example, a geocode of \"Chicago\" returns\n     * \"locality\" which indicates that \"Chicago\" is a city, and also returns \"political\" which\n     * indicates it is a political entity.\n     */\n    public AddressType[] types;\n\n    /**\n     * Indicates that the geocoder did not return an exact match for the original request, though it\n     * was able to match part of the requested address. You may wish to examine the original request\n     * for misspellings and/or an incomplete address.\n     *\n     * <p>Partial matches most often occur for street addresses that do not exist within the locality\n     * you pass in the request. Partial matches may also be returned when a request matches two or\n     * more locations in the same locality. For example, \"21 Henr St, Bristol, UK\" will return a\n     * partial match for both Henry Street and Henrietta Street. Note that if a request includes a\n     * misspelled address component, the geocoding service may suggest an alternate address.\n     * Suggestions triggered in this way will not be marked as a partial match.\n     */\n    public boolean partialMatch;\n\n    /** A unique identifier for this place. */\n    public String placeId;\n\n    /** The Plus Code identifier for this place. */\n    public PlusCode plusCode;\n\n    @NonNull\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder(\"[GeocodingResult\");\n        if (partialMatch) {\n            sb.append(\" PARTIAL MATCH\");\n        }\n        sb.append(\" placeId=\").append(placeId);\n        sb.append(\" \").append(geometry);\n        sb.append(\", formattedAddress=\").append(formattedAddress);\n        sb.append(\", types=\").append(Arrays.toString(types));\n        if (postcodeLocalities != null && postcodeLocalities.length > 0) {\n            sb.append(\", postcodeLocalities=\").append(Arrays.toString(postcodeLocalities));\n        }\n        sb.append(\"]\");\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/model/Geometry.java",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model;\n\nimport androidx.annotation.NonNull;\n\nimport com.google.android.gms.maps.model.LatLng;\n\nimport java.io.Serializable;\n\n/** The geometry of a Geocoding result. */\npublic class Geometry implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n  /**\n   * The bounding box which can fully contain the returned result (optionally returned). Note that\n   * these bounds may not match the recommended viewport. (For example, San Francisco includes the\n   * Farallon islands, which are technically part of the city, but probably should not be returned\n   * in the viewport.)\n   */\n  public Bounds bounds;\n\n  /**\n   * The geocoded latitude/longitude value. For normal address lookups, this field is typically the\n   * most important.\n   */\n  public LatLng location;\n\n  /** The level of certainty of this geocoding result. */\n  public LocationType locationType;\n\n  /**\n   * The recommended viewport for displaying the returned result. Generally the viewport is used to\n   * frame a result when displaying it to a user.\n   */\n  public Bounds viewport;\n\n  @NonNull\n  @Override\n  public String toString() {\n    return String.format(\n        \"[Geometry: %s (%s) bounds=%s, viewport=%s]\", location, locationType, bounds, viewport);\n  }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/model/LocationType.java",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model;\n\n/**\n * Location types for a reverse geocoding request. Please see <a\n * href=\"https://developers.google.com/maps/documentation/geocoding/start#reverse\">Reverse\n * Geocoding</a> for more detail.\n */\npublic enum LocationType {\n  /**\n   * Restricts the results to addresses for which we have location information accurate down to\n   * street address precision.\n   */\n  ROOFTOP,\n\n  /**\n   * Restricts the results to those that reflect an approximation (usually on a road) interpolated\n   * between two precise points (such as intersections). An interpolated range generally indicates\n   * that rooftop geocodes are unavailable for a street address.\n   */\n  RANGE_INTERPOLATED,\n\n  /**\n   * Restricts the results to geometric centers of a location such as a polyline (for example, a\n   * street) or polygon (region).\n   */\n  GEOMETRIC_CENTER,\n\n  /** Restricts the results to those that are characterized as approximate. */\n  APPROXIMATE,\n\n  /**\n   * Indicates an unknown location type returned by the server. The Java Client for Google Maps\n   * Services should be updated to support the new value.\n   */\n  UNKNOWN;\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/model/PlusCode.java",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model;\n\nimport java.io.Serializable;\n\n/** A Plus Code encoded location reference. */\npublic class PlusCode implements Serializable {\n\n  private static final long serialVersionUID = 1L;\n\n  /** The global Plus Code identifier. */\n  public String globalCode;\n\n  /** The compound Plus Code identifier. May be null for locations in remote areas. */\n  public String compoundCode;\n\n  @Override\n  public String toString() {\n    StringBuilder sb = new StringBuilder(\"[PlusCode: \");\n    sb.append(globalCode);\n    if (compoundCode != null) {\n      sb.append(\", compoundCode=\").append(compoundCode);\n    }\n    sb.append(\"]\");\n    return sb.toString();\n  }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/programmatic_autocomplete/LatLngAdapter.java",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.programmatic_autocomplete;\n\nimport com.google.android.gms.maps.model.LatLng;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\n\nimport java.io.IOException;\n\n/** Handle conversion from varying types of latitude and longitude representations. */\npublic class LatLngAdapter extends TypeAdapter<LatLng> {\n    /**\n     * Reads in a JSON object and try to create a LatLng in one of the following formats.\n     *\n     * <pre>{\n     *   \"lat\" : -33.8353684,\n     *   \"lng\" : 140.8527069\n     * }\n     *\n     * {\n     *   \"latitude\": -33.865257570508334,\n     *   \"longitude\": 151.19287000481452\n     * }</pre>\n     */\n    @Override\n    public LatLng read(JsonReader reader) throws IOException {\n        if (reader.peek() == JsonToken.NULL) {\n            reader.nextNull();\n            return null;\n        }\n\n        double lat = 0;\n        double lng = 0;\n        boolean hasLat = false;\n        boolean hasLng = false;\n\n        reader.beginObject();\n        while (reader.hasNext()) {\n            String name = reader.nextName();\n            if (\"lat\".equals(name) || \"latitude\".equals(name)) {\n                lat = reader.nextDouble();\n                hasLat = true;\n            } else if (\"lng\".equals(name) || \"longitude\".equals(name)) {\n                lng = reader.nextDouble();\n                hasLng = true;\n            }\n        }\n        reader.endObject();\n\n        if (hasLat && hasLng) {\n            return new LatLng(lat, lng);\n        } else {\n            return null;\n        }\n    }\n\n    /** Not supported. */\n    @Override\n    public void write(JsonWriter out, LatLng value) throws IOException {\n        throw new UnsupportedOperationException(\"Unimplemented method.\");\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/programmatic_autocomplete/PlacePredictionAdapter.java",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.programmatic_autocomplete;\n\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\nimport com.example.placesdemo.R;\nimport com.example.placesdemo.programmatic_autocomplete.PlacePredictionAdapter.PlacePredictionViewHolder;\nimport com.google.android.libraries.places.api.model.AutocompletePrediction;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A {@link RecyclerView.Adapter} for a {@link com.google.android.libraries.places.api.model.AutocompletePrediction}.\n */\npublic class PlacePredictionAdapter extends RecyclerView.Adapter<PlacePredictionViewHolder> {\n\n    private final List<AutocompletePrediction> predictions = new ArrayList<>();\n\n    private OnPlaceClickListener onPlaceClickListener;\n\n    @NonNull\n    @Override\n    public PlacePredictionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {\n        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());\n        return new PlacePredictionViewHolder(\n            inflater.inflate(R.layout.place_prediction_item, parent, false));\n    }\n\n    @Override\n    public void onBindViewHolder(@NonNull PlacePredictionViewHolder holder, int position) {\n        final AutocompletePrediction prediction = predictions.get(position);\n        holder.setPrediction(prediction);\n        holder.itemView.setOnClickListener(v -> {\n            if (onPlaceClickListener != null) {\n                onPlaceClickListener.onPlaceClicked(prediction);\n            }\n        });\n    }\n\n    @Override\n    public int getItemCount() {\n        return predictions.size();\n    }\n\n    public void setPredictions(List<AutocompletePrediction> predictions) {\n        this.predictions.clear();\n        this.predictions.addAll(predictions);\n        notifyDataSetChanged();\n    }\n\n    public void setPlaceClickListener(OnPlaceClickListener onPlaceClickListener) {\n        this.onPlaceClickListener = onPlaceClickListener;\n    }\n\n    public static class PlacePredictionViewHolder extends RecyclerView.ViewHolder {\n\n        private final TextView title;\n        private final TextView address;\n\n        public PlacePredictionViewHolder(@NonNull View itemView) {\n            super(itemView);\n            title = itemView.findViewById(R.id.text_view_title);\n            address = itemView.findViewById(R.id.text_view_address);\n        }\n\n        public void setPrediction(AutocompletePrediction prediction) {\n            title.setText(prediction.getPrimaryText(null));\n            address.setText(prediction.getSecondaryText(null));\n        }\n    }\n\n    interface OnPlaceClickListener {\n        void onPlaceClicked(AutocompletePrediction place);\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/java/com/example/placesdemo/programmatic_autocomplete/ProgrammaticAutocompleteToolbarActivity.java",
    "content": "// Copyright 2022 Google LLC\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.example.placesdemo.programmatic_autocomplete;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.widget.ProgressBar;\nimport android.widget.SearchView;\nimport android.widget.SearchView.OnQueryTextListener;\nimport android.widget.ViewAnimator;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.DividerItemDecoration;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.android.volley.Request.Method;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.android.volley.toolbox.Volley;\nimport com.example.placesdemo.BuildConfig;\nimport com.example.placesdemo.R;\nimport com.example.placesdemo.model.GeocodingResult;\nimport com.google.android.gms.common.api.ApiException;\nimport com.google.android.gms.maps.model.LatLng;\nimport com.google.android.libraries.places.api.Places;\nimport com.google.android.libraries.places.api.model.AutocompletePrediction;\nimport com.google.android.libraries.places.api.model.AutocompleteSessionToken;\nimport com.google.android.libraries.places.api.model.LocationBias;\nimport com.google.android.libraries.places.api.model.PlaceTypes;\nimport com.google.android.libraries.places.api.model.RectangularBounds;\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest;\nimport com.google.android.libraries.places.api.net.PlacesClient;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\n\nimport java.util.List;\n\nimport androidx.activity.EdgeToEdge;\n\n/**\n * An Activity that demonstrates programmatic as-you-type place predictions. The parameters of the\n * request are currently hard coded in this Activity, to modify these parameters (e.g. location\n * bias, place types, etc.), see {@link ProgrammaticAutocompleteToolbarActivity#getPlacePredictions(String)}.\n *\n * @see <a href=\"https://developers.google.com/places/android-sdk/autocomplete#get_place_predictions_programmatically\">documentation</a>\n */\npublic class ProgrammaticAutocompleteToolbarActivity extends AppCompatActivity {\n\n    private static final String TAG = ProgrammaticAutocompleteToolbarActivity.class.getSimpleName();\n    private final Handler handler = new Handler();\n    private final PlacePredictionAdapter adapter = new PlacePredictionAdapter();\n    private final Gson gson = new GsonBuilder().registerTypeAdapter(LatLng.class, new LatLngAdapter())\n            .create();\n\n    private RequestQueue queue;\n    private PlacesClient placesClient;\n    private AutocompleteSessionToken sessionToken;\n\n    private ViewAnimator viewAnimator;\n    private ProgressBar progressBar;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        // Enable edge-to-edge display. This must be called before calling super.onCreate().\n        EdgeToEdge.enable(this);\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_programmatic_autocomplete);\n        setSupportActionBar(findViewById(R.id.toolbar));\n\n        // Initialize members\n        progressBar = findViewById(R.id.progress_bar);\n        viewAnimator = findViewById(R.id.view_animator);\n        placesClient = Places.createClient(this);\n        queue = Volley.newRequestQueue(this);\n        initRecyclerView();\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.menu, menu);\n        final SearchView searchView =\n                (SearchView) menu.findItem(R.id.search).getActionView();\n        assert searchView != null;\n        initSearchView(searchView);\n        return super.onCreateOptionsMenu(menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(@NonNull MenuItem item) {\n        if (item.getItemId() == R.id.search) {\n            sessionToken = AutocompleteSessionToken.newInstance();\n            return false;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private void initSearchView(SearchView searchView) {\n        searchView.setQueryHint(getString(R.string.search_a_place));\n        searchView.setIconifiedByDefault(false);\n        searchView.setFocusable(true);\n        searchView.setIconified(false);\n        searchView.requestFocusFromTouch();\n        searchView.setOnQueryTextListener(new OnQueryTextListener() {\n            @Override\n            public boolean onQueryTextSubmit(String query) {\n                return false;\n            }\n\n            @Override\n            public boolean onQueryTextChange(String newText) {\n                progressBar.setIndeterminate(true);\n\n                // Cancel any previous place prediction requests\n                handler.removeCallbacksAndMessages(null);\n\n                // Start a new place prediction request in 300 ms\n                handler.postDelayed(() -> getPlacePredictions(newText), 300);\n                return true;\n            }\n        });\n    }\n\n    private void initRecyclerView() {\n        final RecyclerView recyclerView = findViewById(R.id.recycler_view);\n        final LinearLayoutManager layoutManager = new LinearLayoutManager(this);\n        recyclerView.setLayoutManager(layoutManager);\n        recyclerView.setAdapter(adapter);\n        recyclerView\n                .addItemDecoration(new DividerItemDecoration(this, layoutManager.getOrientation()));\n        adapter.setPlaceClickListener(this::geocodePlaceAndDisplay);\n    }\n\n    /**\n     * This method demonstrates the programmatic approach to getting place predictions. The\n     * parameters in this request are currently biased to Boulder, Colorado.\n     *\n     * @param query the plus code query string (e.g. \"85GP2Q2X+2R\")\n     */\n    private void getPlacePredictions(String query) {\n\n        // The value of 'bias' biases prediction results to the rectangular region provided\n        // (currently Kolkata). Modify these values to get results for another area. Make sure to\n        // pass in the appropriate value/s for .setCountries() in the\n        // FindAutocompletePredictionsRequest.Builder object as well.\n        final LocationBias bias = RectangularBounds.newInstance(\n                new LatLng(39.91, -105.75), // SW lat, lng\n                new LatLng(40.26, -105.02) // NE lat, lng\n        );\n\n        // Create a new programmatic Place Autocomplete request in Places SDK for Android\n        final FindAutocompletePredictionsRequest newRequest = FindAutocompletePredictionsRequest\n                .builder()\n                .setSessionToken(sessionToken)\n                .setLocationBias(bias)\n                .setQuery(query)\n                .setCountries(List.of(\"US\"))\n                .setTypesFilter(List.of(PlaceTypes.ESTABLISHMENT))\n                .build();\n\n        // Perform autocomplete predictions request\n        placesClient.findAutocompletePredictions(newRequest).addOnSuccessListener((response) -> {\n            List<AutocompletePrediction> predictions = response.getAutocompletePredictions();\n            adapter.setPredictions(predictions);\n\n            progressBar.setIndeterminate(false);\n            viewAnimator.setDisplayedChild(predictions.isEmpty() ? 0 : 1);\n        }).addOnFailureListener((exception) -> {\n            progressBar.setIndeterminate(false);\n            if (exception instanceof ApiException apiException) {\n                Log.e(TAG, \"Place not found: \" + apiException.getStatusCode());\n            }\n        });\n    }\n\n    /**\n     * Performs a Geocoding API request and displays the result in a dialog.\n     *\n     * @see <a href=\"https://developers.google.com/maps/documentation/geocoding/intro\">documentation</a>\n     */\n    private void geocodePlaceAndDisplay(AutocompletePrediction placePrediction) {\n        // Construct the request URL\n        final String apiKey = BuildConfig.PLACES_API_KEY;\n        final String url = \"https://maps.googleapis.com/maps/api/geocode/json?place_id=%s&key=%s\";\n        final String requestURL = String.format(url, placePrediction.getPlaceId(), apiKey);\n\n        // Use the HTTP request URL for Geocoding API to get geographic coordinates for the place\n        JsonObjectRequest request = new JsonObjectRequest(Method.GET, requestURL, null,\n                                                          response -> {\n                                                              try {\n                                                                  // Inspect the value of \"results\" and make sure it's not empty\n                                                                  JSONArray results = response.getJSONArray(\"results\");\n                                                                  if (results.length() == 0) {\n                                                                      Log.w(TAG, \"No results from geocoding request.\");\n                                                                      return;\n                                                                  }\n\n                                                                  // Use Gson to convert the response JSON object to a POJO\n                                                                  GeocodingResult result = gson.fromJson(\n                                                                          results.getString(0), GeocodingResult.class);\n                                                                  displayDialog(placePrediction, result);\n                                                              } catch (JSONException e) {\n                                                                  e.printStackTrace();\n                                                              }\n                                                          }, error -> Log.e(TAG, \"Request failed\"));\n\n        // Add the request to the Request queue.\n        queue.add(request);\n    }\n\n    private void displayDialog(AutocompletePrediction place, GeocodingResult result) {\n        new AlertDialog.Builder(this)\n                .setTitle(place.getPrimaryText(null))\n                .setMessage(\"Geocoding result:\\n\" + result.geometry.location)\n                .setPositiveButton(android.R.string.ok, null)\n                .show();\n    }\n}\n"
  },
  {
    "path": "demo-java/src/main/res/drawable/ic_search_black_24dp.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<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:fillColor=\"#FF000000\"\n        android:pathData=\"M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z\"/>\n</vector>\n"
  },
  {
    "path": "demo-java/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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  The ScrollView is used to ensure that all content is visible on smaller devices.\n  android:fitsSystemWindows=\"true\" is required to be set on the root view so that the\n  system insets are applied to the content, preventing it from being obscured by the system bars.\n-->\n<ScrollView\n    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:fitsSystemWindows=\"true\"\n    tools:context=\".MainActivity\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"@dimen/spacing_large\">\n\n        <Button\n            android:id=\"@+id/autocomplete_button\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"@dimen/spacing_small\"\n            android:text=\"@string/autocomplete_button\"/>\n\n        <Button\n            android:id=\"@+id/autocomplete_address_button\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"@dimen/spacing_small\"\n            android:text=\"@string/autocomplete_address_button\"/>\n\n        <Button\n            android:id=\"@+id/programmatic_autocomplete_button\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"@dimen/spacing_small\"\n            android:text=\"@string/programmatic_autocomplete_geocoding_button\"/>\n\n        <Button\n            android:id=\"@+id/current_place_button\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"@dimen/spacing_small\"\n            android:text=\"@string/current_place_button\"/>\n\n        <Button\n            android:id=\"@+id/place_and_photo_button\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"@dimen/spacing_small\"\n            android:text=\"@string/place_and_photo_button\"/>\n\n        <Button\n            android:id=\"@+id/is_open_button\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"@dimen/spacing_small\"\n            android:text=\"@string/main_isOpenButtonText\"/>\n\n    </LinearLayout>\n\n</ScrollView>\n\n"
  },
  {
    "path": "demo-java/src/main/res/layout/activity_programmatic_autocomplete.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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  android:fitsSystemWindows=\"true\" is required to be set on the root view so that the\n  system insets are applied to the content, preventing it from being obscured by the system bars.\n-->\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:fitsSystemWindows=\"true\"\n  android:orientation=\"vertical\"\n  tools:context=\".programmatic_autocomplete.ProgrammaticAutocompleteToolbarActivity\">\n\n  <com.google.android.material.appbar.AppBarLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\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\n    <ProgressBar\n      android:id=\"@+id/progress_bar\"\n      style=\"@style/Widget.AppCompat.ProgressBar.Horizontal\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginTop=\"-7dp\"\n      android:layout_marginBottom=\"-7dp\" />\n\n  </com.google.android.material.appbar.AppBarLayout>\n\n  <ViewAnimator\n    android:id=\"@+id/view_animator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <TextView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_margin=\"16dp\"\n      android:gravity=\"center\"\n      android:lineSpacingMultiplier=\"1.15\"\n      android:text=\"@string/programmatic_place_predictions_instructions\"\n      android:textSize=\"20sp\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n      android:id=\"@+id/recycler_view\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\" />\n  </ViewAnimator>\n\n</LinearLayout>"
  },
  {
    "path": "demo-java/src/main/res/layout/autocomplete_address_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2021 Google LLC\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  The ScrollView is used to ensure that all content is visible on smaller devices.\n  android:fitsSystemWindows=\"true\" is required to be set on the root view so that the\n  system insets are applied to the content, preventing it from being obscured by the system bars.\n-->\n<ScrollView\n    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:fitsSystemWindows=\"true\"\n    tools:context=\".AutocompleteAddressActivity\">\n\n  <LinearLayout\n      android:id=\"@+id/autocomplete_scroll_container\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      android:padding=\"@dimen/spacing_large\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_address1_label\"/>\n\n    <com.example.placesdemo.model.AutocompleteEditText\n        android:id=\"@+id/autocomplete_address1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:autofillHints=\"\"\n        android:hint=\"@string/autocomplete_address1_label\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_address2_label\"/>\n\n    <EditText\n        android:id=\"@+id/autocomplete_address2\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:autofillHints=\"\"\n        android:hint=\"@string/autocomplete_address2_label\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_city_label\"/>\n\n    <EditText\n        android:id=\"@+id/autocomplete_city\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:autofillHints=\"\"\n        android:hint=\"@string/autocomplete_city_label\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:baselineAligned=\"false\">\n\n      <LinearLayout\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:orientation=\"vertical\">\n\n      <TextView\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_state_label\"/>\n\n      <EditText\n          android:id=\"@+id/autocomplete_state\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_state_label\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"textCapCharacters\"/>\n\n      </LinearLayout>\n\n      <LinearLayout\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:orientation=\"vertical\">\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/autocomplete_postal_label\"/>\n\n      <EditText\n          android:id=\"@+id/autocomplete_postal\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"0dp\"\n          android:layout_weight=\"1\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_postal_label\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"number\"/>\n      </LinearLayout>\n    </LinearLayout>\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_country_label\"/>\n\n    <EditText\n        android:id=\"@+id/autocomplete_country\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:autofillHints=\"\"\n        android:hint=\"@string/autocomplete_country_label\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <!-- Proximity check option provided for development testing convenience.\n     User would typically not control this option. -->\n    <CheckBox android:id=\"@+id/checkbox_proximity\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_proximity_check\"/>\n\n    <!-- The map for visual confirmation of the selected address -->\n    <!-- Stub to only load the map after Autocomplete prediction selection -->\n    <ViewStub\n        android:id=\"@+id/stub_map\"\n        android:inflatedId=\"@+id/panel_map\"\n        android:layout=\"@layout/autocomplete_address_map\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"200dp\"\n        android:layout_gravity=\"bottom\" />\n\n    <Button\n        android:id=\"@+id/autocomplete_save_button\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_save_button\"/>\n\n    <!-- Reset button provided for development testing convenience. Not recommended for user-facing\n    forms due to risk of mis-click when aiming for Submit button. -->\n    <Button\n        android:id=\"@+id/autocomplete_reset_button\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@android:color/transparent\"\n        android:text=\"@string/autocomplete_reset_button\"/>\n  </LinearLayout>\n</ScrollView>\n\n"
  },
  {
    "path": "demo-java/src/main/res/layout/autocomplete_address_map.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2021 Google LLC\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<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/map_panel_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_map_label\"/>\n\n    <!-- A map will be added here programmatically\n    for visual confirmation of the selected address -->\n    <!-- [START maps_solutions_android_autocomplete_map_fragment] -->\n    <fragment\n        android:name=\"com.google.android.gms.maps.SupportMapFragment\"\n        android:id=\"@+id/confirmation_map\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n    <!-- [END maps_solutions_android_autocomplete_map_fragment] -->\n\n</LinearLayout>"
  },
  {
    "path": "demo-java/src/main/res/layout/current_place_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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  The ScrollView is used to ensure that all content is visible on smaller devices.\n  android:fitsSystemWindows=\"true\" is required to be set on the root view so that the\n  system insets are applied to the content, preventing it from being obscured by the system bars.\n-->\n<ScrollView\n    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:fitsSystemWindows=\"true\"\n    tools:context=\".CurrentPlaceActivity\">\n\n  <LinearLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      android:padding=\"@dimen/spacing_large\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <CheckBox\n          android:id=\"@+id/use_custom_fields\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:text=\"@string/use_custom_fields\"/>\n\n      <TextView\n          android:id=\"@+id/custom_fields_list\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"/>\n\n    </LinearLayout>\n\n    <Button\n        android:id=\"@+id/find_current_place_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/find_current_place_button\"/>\n\n    <CheckBox\n        android:id=\"@+id/display_raw_results\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"false\"\n        android:text=\"@string/display_raw_results\"/>\n\n    <ProgressBar\n        android:id=\"@+id/loading\"\n        style=\"?android:attr/progressBarStyleSmall\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"invisible\"/>\n\n    <TextView\n        android:id=\"@+id/response\"\n        android:textIsSelectable=\"true\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n  </LinearLayout>\n\n</ScrollView>\n\n"
  },
  {
    "path": "demo-java/src/main/res/layout/place_autocomplete_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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  The ScrollView is used to ensure that all content is visible on smaller devices.\n  android:fitsSystemWindows=\"true\" is required to be set on the root view so that the\n  system insets are applied to the content, preventing it from being obscured by the system bars.\n-->\n<ScrollView\n    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:fitsSystemWindows=\"true\"\n    tools:context=\".PlaceAutocompleteActivity\">\n\n  <LinearLayout\n      android:id=\"@+id/autocomplete_scroll_container\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      android:padding=\"@dimen/spacing_large\">\n\n    <!--Autocomplete parameters-->\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_location_origin_label\"/>\n\n    <EditText\n        android:id=\"@+id/autocomplete_location_origin\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/autocomplete_location_origin_hint\"\n        android:autofillHints=\"\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_location_bias_label\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <EditText\n          android:id=\"@+id/autocomplete_location_bias_south_west\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:minHeight=\"48dp\"\n          android:layout_weight=\"1\"\n          android:hint=\"@string/autocomplete_location_south_west_hint\"\n          android:autofillHints=\"\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\"/>\n\n      <EditText\n          android:id=\"@+id/autocomplete_location_bias_north_east\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:hint=\"@string/autocomplete_location_north_east_hint\"\n          android:autofillHints=\"\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\"/>\n    </LinearLayout>\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_location_restriction_label\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <EditText\n          android:id=\"@+id/autocomplete_location_restriction_south_west\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:minHeight=\"48dp\"\n          android:layout_weight=\"1\"\n          android:hint=\"@string/autocomplete_location_south_west_hint\"\n          android:autofillHints=\"\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\"/>\n\n      <EditText\n          android:id=\"@+id/autocomplete_location_restriction_north_east\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:hint=\"@string/autocomplete_location_north_east_hint\"\n          android:autofillHints=\"\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\"/>\n    </LinearLayout>\n\n    <EditText\n        android:id=\"@+id/autocomplete_query\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/autocomplete_query_hint\"\n        android:autofillHints=\"\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <!-- Autocomplete fragment only -->\n    <EditText\n        android:id=\"@+id/autocomplete_hint\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/autocomplete_hint_hint\"\n        android:autofillHints=\"\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <EditText\n        android:id=\"@+id/autocomplete_country\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/autocomplete_country_hint\"\n        android:autofillHints=\"\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n      <CheckBox\n          android:id=\"@+id/autocomplete_use_types_filter_checkbox\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:minHeight=\"48dp\"\n          android:checked=\"false\"\n          android:text=\"@string/autocomplete_use_types_filter\"/>\n\n      <EditText\n          android:id=\"@+id/autocomplete_types_filter_edittext\"\n          android:autofillHints=\"\"\n          android:enabled=\"false\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:hint=\"@string/autocomplete_types_filter_hint\"\n          android:inputType=\"text\"/>\n\n    </LinearLayout>\n\n    <!-- Autocomplete predictions only -->\n    <CheckBox\n        android:id=\"@+id/autocomplete_use_session_token\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"48dp\"\n        android:checked=\"false\"\n        android:text=\"@string/autocomplete_use_session_token\"/>\n\n    <!-- Autocomplete activity only -->\n    <CheckBox\n        android:id=\"@+id/autocomplete_activity_overlay_mode\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"48dp\"\n        android:checked=\"false\"\n        android:text=\"@string/autocomplete_activity_overlay_mode\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <CheckBox\n          android:id=\"@+id/use_custom_fields\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:minHeight=\"48dp\"\n          android:text=\"@string/use_custom_fields\"/>\n\n      <TextView\n          android:id=\"@+id/custom_fields_list\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"/>\n\n    </LinearLayout>\n\n    <Button\n        android:id=\"@+id/fetch_autocomplete_predictions_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/fetch_autocomplete_predictions_button\"/>\n\n    <Button\n        android:id=\"@+id/autocomplete_activity_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_activity_button\"/>\n\n    <!-- Autocomplete support fragment -->\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/autocomplete_support_fragment_text_label\"\n        android:text=\"@string/autocomplete_support_fragment_text_label\"/>\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/autocomplete_support_fragment\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:name=\"com.google.android.libraries.places.widget.AutocompleteSupportFragment\"\n        tools:layout=\"@layout/places_autocomplete_fragment\" />\n\n    <Button\n        android:id=\"@+id/autocomplete_support_fragment_update_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_support_fragment_update_button\"/>\n\n    <!-- Results -->\n    <CheckBox\n        android:id=\"@+id/display_raw_results\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"false\"\n        android:text=\"@string/display_raw_results\"/>\n\n    <ProgressBar\n        android:id=\"@+id/loading\"\n        style=\"?android:attr/progressBarStyleSmall\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"invisible\"/>\n\n    <TextView\n        android:id=\"@+id/response\"\n        android:textIsSelectable=\"true\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n  </LinearLayout>\n</ScrollView>\n\n"
  },
  {
    "path": "demo-java/src/main/res/layout/place_details_and_photos_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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  The ScrollView is used to ensure that all content is visible on smaller devices.\n  android:fitsSystemWindows=\"true\" is required to be set on the root view so that the\n  system insets are applied to the content, preventing it from being obscured by the system bars.\n-->\n<ScrollView\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:fitsSystemWindows=\"true\"\n    tools:context=\".PlaceDetailsAndPhotosActivity\">\n\n  <LinearLayout\n      android:id=\"@+id/place_scroll_container\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      android:padding=\"@dimen/spacing_large\">\n\n    <EditText\n        android:id=\"@+id/place_id_field\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/place_id_field_hint\"\n        android:autofillHints=\"\"\n        android:imeOptions=\"actionGo\"\n        android:inputType=\"text\"\n        android:text=\"@string/place_id_default\"/>\n\n    <CheckBox\n        android:id=\"@+id/fetch_photo_checkbox\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        android:text=\"@string/fetch_photo_checkbox\"/>\n\n    <CheckBox\n        android:id=\"@+id/fetch_icon_checkbox\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"false\"\n        android:text=\"@string/fetch_icon_checkbox\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <EditText\n          android:id=\"@+id/photo_max_width\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:hint=\"@string/photo_max_width_hint\"\n          android:autofillHints=\"\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"number\"/>\n\n      <EditText\n          android:id=\"@+id/photo_max_height\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:hint=\"@string/photo_max_height_hint\"\n          android:autofillHints=\"\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"number\"/>\n\n    </LinearLayout>\n\n    <CheckBox\n        android:id=\"@+id/use_custom_photo_reference\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"false\"\n        android:text=\"@string/use_custom_photo_reference\"/>\n\n    <EditText\n        android:id=\"@+id/custom_photo_reference\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/custom_photo_reference_hint\"\n        android:autofillHints=\"\"\n        android:imeOptions=\"actionNext\"\n        android:inputType=\"text\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <CheckBox\n          android:id=\"@+id/use_custom_fields\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:text=\"@string/use_custom_fields\"/>\n\n      <TextView\n          android:id=\"@+id/custom_fields_list\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"/>\n\n    </LinearLayout>\n\n    <Button\n        android:id=\"@+id/fetch_place_and_photo_button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/fetch_place_and_photo_button\"/>\n\n    <CheckBox\n        android:id=\"@+id/display_raw_results\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"false\"\n        android:text=\"@string/display_raw_results\"/>\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/icon_with_background\"/>\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:contentDescription=\"@string/icon_view_image_content_description\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/material_grey_300\"\n        android:padding=\"4dp\"\n        android:minHeight=\"48dp\"\n        android:minWidth=\"48dp\"\n        app:tint=\"@android:color/white\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/place_photo\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <ImageView\n          android:id=\"@+id/photo\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:background=\"@color/material_grey_300\"\n          android:minHeight=\"48dp\"\n          android:minWidth=\"48dp\"/>\n\n      <ProgressBar\n          android:id=\"@+id/loading\"\n          style=\"?android:attr/progressBarStyleSmall\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:visibility=\"invisible\"/>\n\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/photo_metadata\"\n        android:textIsSelectable=\"true\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n    <TextView\n        android:id=\"@+id/response\"\n        android:freezesText=\"true\"\n        android:textIsSelectable=\"true\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n  </LinearLayout>\n\n</ScrollView>\n"
  },
  {
    "path": "demo-java/src/main/res/layout/place_is_open_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<!--\n  The ScrollView is used to ensure that all content is visible on smaller devices.\n  android:fitsSystemWindows=\"true\" is required to be set on the root view so that the\n  system insets are applied to the content, preventing it from being obscured by the system bars.\n-->\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=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    tools:context=\".PlaceIsOpenActivity\">\n\n  <LinearLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"vertical\"\n      android:padding=\"@dimen/spacing_large\">\n\n    <EditText\n        android:id=\"@+id/editText_placeId\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"48dp\"\n        android:hint=\"@string/isOpen_place_id_hint\"\n        android:autofillHints=\"\"\n        android:imeOptions=\"actionGo\"\n        android:inputType=\"text\"\n        android:text=\"@string/isOpen_default_place_id\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <CheckBox\n          android:id=\"@+id/checkBox_useCustomFields\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:minHeight=\"48dp\"\n          android:layout_weight=\"1\"\n          android:text=\"@string/isOpen_use_custom_fields_text\"/>\n\n      <TextView\n          android:id=\"@+id/textView_customFieldsList\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"/>\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"@dimen/spacing_xlarge\"\n        android:layout_marginBottom=\"@dimen/spacing_xlarge\"\n        android:orientation=\"vertical\">\n\n      <TextView\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/isOpen_use_custom_time_hint\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"horizontal\"\n          android:weightSum=\"10\">\n\n        <TextView\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"3\"\n            android:text=\"@string/isOpen_spinner_description\"/>\n\n        <Spinner\n            android:id=\"@+id/spinner_timeZones\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:minHeight=\"48dp\"\n            android:layout_weight=\"7\"\n            android:padding=\"10dp\" />\n\n      </LinearLayout>\n\n      <EditText\n          android:id=\"@+id/editText_isOpenDate\"\n          android:autofillHints=\"\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:clickable=\"false\"\n          android:cursorVisible=\"false\"\n          android:focusable=\"false\"\n          android:focusableInTouchMode=\"false\"\n          android:longClickable=\"false\"\n          android:hint=\"@string/isOpen_date_hint\"\n          android:inputType=\"date\" />\n\n      <EditText\n          android:id=\"@+id/editText_isOpenTime\"\n          android:autofillHints=\"\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:clickable=\"false\"\n          android:cursorVisible=\"false\"\n          android:focusable=\"false\"\n          android:focusableInTouchMode=\"false\"\n          android:longClickable=\"false\"\n          android:hint=\"@string/isOpen_time_hint\"\n          android:inputType=\"time\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <Button\n          android:id=\"@+id/button_fetchPlace\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:text=\"@string/isOpen_fetch_place_button_text\"\n          tools:ignore=\"ButtonStyle\" />\n\n      <Button\n          android:id=\"@+id/button_isOpen\"\n          android:layout_width=\"0dp\"\n          android:layout_height=\"wrap_content\"\n          android:layout_weight=\"1\"\n          android:text=\"@string/isOpen_is_open_button_text\"\n          tools:ignore=\"ButtonStyle\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n      <ProgressBar\n          android:id=\"@+id/progressBar_loading\"\n          style=\"?android:attr/progressBarStyleSmall\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:visibility=\"invisible\"/>\n\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/textView_response\"\n        android:freezesText=\"true\"\n        android:textIsSelectable=\"true\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n  </LinearLayout>\n\n</ScrollView>"
  },
  {
    "path": "demo-java/src/main/res/layout/place_prediction_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<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=\"wrap_content\"\n  android:padding=\"16dp\"\n  android:orientation=\"vertical\">\n\n  <TextView\n    android:id=\"@+id/text_view_title\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:textStyle=\"bold\"\n    android:textSize=\"18sp\"\n    tools:text=\"Place Name\" />\n\n  <TextView\n    android:id=\"@+id/text_view_address\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:textSize=\"16sp\"\n    android:layout_marginTop=\"2dp\"\n    tools:text=\"123 Main Street, San Francisco, CA USA\" />\n\n</LinearLayout>"
  },
  {
    "path": "demo-java/src/main/res/menu/menu.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n  <item\n    android:id=\"@+id/search\"\n    android:icon=\"@drawable/ic_search_black_24dp\"\n    android:title=\"@string/search\"\n    app:actionViewClass=\"android.widget.SearchView\"\n    app:showAsAction=\"collapseActionView|ifRoom\" />\n</menu>"
  },
  {
    "path": "demo-java/src/main/res/raw/style_json.json",
    "content": "[\n  {\n    \"featureType\": \"poi\",\n    \"elementType\": \"all\",\n    \"stylers\": [\n      {\n        \"visibility\": \"off\"\n      }\n    ]\n  },\n  {\n    \"featureType\": \"transit\",\n    \"elementType\": \"labels.icon\",\n    \"stylers\": [\n      {\n        \"visibility\": \"off\"\n      }\n    ]\n  }\n]"
  },
  {
    "path": "demo-java/src/main/res/values/dimens.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<resources>\n  <dimen name=\"spacing_xlarge\">8dp</dimen>\n  <dimen name=\"spacing_large\">4dp</dimen>\n  <dimen name=\"spacing_small\">2dp</dimen>\n</resources>"
  },
  {
    "path": "demo-java/src/main/res/values/strings.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<resources>\n  <!-- Title of the app. -->\n  <string name=\"app_name\" translatable=\"false\">Android Places Demo</string>\n\n  <!-- title for error alerts. -->\n  <string name=\"error_alert_title\" translatable=\"false\">Uh-Oh!</string>\n  <!-- message to show for error alerts when location origin is malformed. -->\n  <string name=\"error_alert_message_invalid_origin\" translatable=\"false\">Unable to parse Location Origin. Expected format: \"33, 128\"</string>\n  <!-- message to show for error alerts when location bias or restriction is malformed. -->\n  <string name=\"error_alert_message_invalid_bounds\" translatable=\"false\">Unable to parse Location Bias or Location Restriction. Expected format: \"33, 128\"</string>\n  <!-- message to show for error alerts when a photo size field is malformed. -->\n  <string name=\"error_alert_message_invalid_photo_size\" translatable=\"false\">Unable to parse Photo size. Expected format is integers</string>\n\n  <string name=\"error_api_key\" translatable=\"false\">No API key defined in gradle.properties</string>\n\n  <!-- Text for raw results checkbox. When checked raw return values will be displayed. -->\n  <string name=\"display_raw_results\" translatable=\"false\">Display raw results?</string>\n\n\n  <!-- MAIN ACTIVITY -->\n\n  <!-- Button for launching autocomplete test activity. -->\n  <string name=\"autocomplete_button\" translatable=\"false\">Place Autocomplete</string>\n\n  <!-- Button for launching autocomplete address form activity. -->\n  <string name=\"autocomplete_address_button\" translatable=\"false\">Place Autocomplete Address Form</string>\n\n  <!-- Button for launching autocomplete address form activity. -->\n  <string name=\"programmatic_autocomplete_geocoding_button\">Programmatic Autocomplete + Geocoding</string>\n\n  <!-- Button for launching current place test activity. -->\n  <string name=\"current_place_button\" translatable=\"false\">Current Place</string>\n\n  <!-- Button for launching place and photo test activity. -->\n  <string name=\"place_and_photo_button\" translatable=\"false\">Place Details and Place Photos</string>\n\n  <!-- Button for launching is open test activity. -->\n  <string name=\"main_isOpenButtonText\" translatable=\"false\">Place is Open?</string>\n\n  <!-- AUTOCOMPLETE -->\n\n  <!-- Hint for the autocomplete widget's initial query and autocomplete prediction's query field. -->\n  <string name=\"autocomplete_query_hint\" translatable=\"false\">(Initial) Query</string>\n\n  <!-- Hint for the autocomplete widget's query field. -->\n  <string name=\"autocomplete_hint_hint\" translatable=\"false\">Hint</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's countries field. -->\n  <string name=\"autocomplete_country_hint\" translatable=\"false\">Countries (e.g CH, US, RO)</string>\n\n  <!-- Label for the autocomplete prediction's and widget's location origin field. -->\n  <string name=\"autocomplete_location_origin_label\" translatable=\"false\">Location Origin (format: -33.2, 128.2)</string>\n\n  <!-- Label for the autocomplete prediction's and widget's location bias related fields. -->\n  <string name=\"autocomplete_location_bias_label\" translatable=\"false\">Location Bias (format: -33.2, 128.2)</string>\n\n  <!-- Label for the autocomplete prediction's and widget's location restriction related fields. -->\n  <string name=\"autocomplete_location_restriction_label\" translatable=\"false\">Location Restriction (format: -33.2, 128.2)</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's location origin field. -->\n  <string name=\"autocomplete_location_origin_hint\" translatable=\"false\">Origin (lat, lng)</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's location southwest field. -->\n  <string name=\"autocomplete_location_south_west_hint\" translatable=\"false\">South West (lat, lng)</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's location northeast field. -->\n  <string name=\"autocomplete_location_north_east_hint\" translatable=\"false\">North East (lat, lng)</string>\n\n  <!-- Text for enabling autocomplete prediction's and widget's types filter field. -->\n  <string name=\"autocomplete_use_types_filter\" translatable=\"false\">Use TypesFilter?</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's types filter field. -->\n  <string name=\"autocomplete_types_filter_hint\" translatable=\"false\">\n    <font size=\"16\">\n      Comma separated list of up to 5 place types\n    </font>\n  </string>\n\n\n  <!-- Text for enabling autocomplete prediction's use of session tokens. -->\n  <string name=\"autocomplete_use_session_token\" translatable=\"false\">(Predictions-only) Use session token?</string>\n\n  <!-- Text for autocomplete activity mode (overlay or fullscreen) checkbox. -->\n  <string name=\"autocomplete_activity_overlay_mode\" translatable=\"false\">Overlay mode?</string>\n\n  <!-- Text for the autocomplete support fragment section label. -->\n  <string name=\"autocomplete_support_fragment_text_label\" translatable=\"false\">Autocomplete Support Fragment:</string>\n\n  <!-- Text for the autocomplete fragment section label. -->\n  <string name=\"autocomplete_fragment_text_label\" translatable=\"false\">Autocomplete Fragment:</string>\n\n  <!-- Text for the autocomplete support fragment update button. -->\n  <string name=\"autocomplete_support_fragment_update_button\" translatable=\"false\">Update Support Fragment</string>\n\n  <!-- Text for the autocomplete fragment update button. -->\n  <string name=\"autocomplete_fragment_update_button\" translatable=\"false\">Update Fragment</string>\n\n  <!-- Text for starting the autocomplete activity button. -->\n  <string name=\"autocomplete_activity_button\" translatable=\"false\">Open Activity</string>\n\n  <!-- Text for the fetch autocomplete predictions submit button. -->\n  <string name=\"fetch_autocomplete_predictions_button\" translatable=\"false\">Fetch Predictions</string>\n\n\n  <!-- AUTOCOMPLETE ADDRESS -->\n\n  <!-- Hint for the autocomplete form's first address line field. -->\n  <string name=\"autocomplete_address1_hint\" translatable=\"false\">Enter the address</string>\n\n  <!-- Label for the autocomplete form's heading. -->\n  <string name=\"autocomplete_address_heading_label\" translatable=\"false\">Delivery Address</string>\n\n  <!-- Label for the address form's first address line field. -->\n  <string name=\"autocomplete_address1_label\" translatable=\"false\">Address Line 1</string>\n\n  <!-- Label for the address form's second address line field. -->\n  <string name=\"autocomplete_address2_label\" translatable=\"false\">Apartment, unit, suite, or floor #</string>\n\n  <!-- Label for the address form's city field. -->\n  <string name=\"autocomplete_city_label\" translatable=\"false\">City</string>\n\n  <!-- Label for the address form's state field. -->\n  <string name=\"autocomplete_state_label\" translatable=\"false\">State/Province</string>\n\n  <!-- Label for the address form's postal code field. -->\n  <string name=\"autocomplete_postal_label\" translatable=\"false\">Postal code</string>\n\n  <!-- Label for the address form's country field. -->\n  <string name=\"autocomplete_country_label\" translatable=\"false\">Country/Region</string>\n\n  <!-- Label for the address form's proximity check setting. -->\n  <string name=\"autocomplete_proximity_check\" translatable=\"false\">Check device proximity</string>\n\n  <!-- Label for the address form's confirmation map. -->\n  <string name=\"autocomplete_map_label\" translatable=\"false\">Map view of the selected address</string>\n\n  <!-- Text for the address form submit button. -->\n  <string name=\"autocomplete_save_button\" translatable=\"false\">Submit address</string>\n\n  <!-- Text for the address form reset button. -->\n  <string name=\"autocomplete_reset_button\" translatable=\"false\">Clear form</string>\n\n  <!-- Text for the toast message for permissions denied. -->\n  <string name=\"autocomplete_denied_message\" translatable=\"false\">User denied location permission. Cannot check proximity to entered address.</string>\n\n  <!-- Text for the toast message for proximity match in progress. -->\n  <string name=\"autocomplete_progress_message\" translatable=\"false\">Checking proximity</string>\n\n  <!-- Text for the toast message for a successful proximity match. -->\n  <string name=\"autocomplete_match_message\" translatable=\"false\">Device location matches entered address</string>\n\n  <!-- Text for the toast message for a failed proximity match. -->\n  <string name=\"autocomplete_nomatch_message\" translatable=\"false\">Device location does not match entered address</string>\n\n  <!-- Text for the toast message for a skilled proximity match. -->\n  <string name=\"autocomplete_skipped_message\" translatable=\"false\">Address accepted without checking proximity</string>\n\n  <!-- PROGRAMMATIC AUTOCOMPLETE AND GEOCODING -->\n\n  <string name=\"search\">Search</string>\n  <string name=\"search_a_place\">Search a Place</string>\n  <string name=\"programmatic_place_predictions_instructions\">This activity demonstrates as-you-type programmatic place predictions. Tap on the search icon on the toolbar and search for a place.</string>\n\n  <!-- FIND CURRENT PLACE -->\n\n  <!-- Text for the find current place submit button. -->\n  <string name=\"find_current_place_button\" translatable=\"false\">Find Current Place</string>\n  <string name=\"icon_view_content_description\" translatable=\"false\">Icon View</string>\n  <string name=\"fetch_icon_checkbox\" translatable=\"false\">Also fetch icon?</string>\n  <string name=\"fetch_icon_missing_fields_warning\" translatable=\"false\">\\'Also fetch icon?\\' is selected, but ICON_URL Place Field is not.</string>\n  <string name=\"icon_view_image_content_description\" translatable=\"false\">Image view to display a Place Icon Image</string>\n  <string name=\"icon_with_background\" translatable=\"false\">Icon with background:</string>\n  <string name=\"place_photo\" translatable=\"false\">Place photo:</string>\n\n\n  <!-- PLACE DETAILS AND PHOTO -->\n\n  <!-- Place ID for a dining establishment, used as the default for testing in the Place and Photo screen -->\n  <string name=\"place_id_default\" translatable=\"false\">ChIJM3wn3VVYwokRvu2kbqBKUsM</string>\n\n  <!-- Hint for the place ID field. -->\n  <string name=\"place_id_field_hint\" translatable=\"false\">Enter place ID</string>\n\n  <!-- Text for the fetch place and photo button. -->\n  <string name=\"fetch_photo_checkbox\" translatable=\"false\">Also fetch photo?</string>\n\n  <!-- Hint for the fetch photo max width field. -->\n  <string name=\"photo_max_width_hint\" translatable=\"false\">Photo Max Width</string>\n\n  <!-- Text for the fetch photo max height field. -->\n  <string name=\"photo_max_height_hint\" translatable=\"false\">Photo Max Height</string>\n\n  <!-- Text for the fetch place and photo button. -->\n  <string name=\"use_custom_photo_reference\" translatable=\"false\">Use custom Photo Reference?</string>\n\n  <!-- Text for the fetch place and photo button. -->\n  <string name=\"custom_photo_reference_hint\" translatable=\"false\">Custom Photo Reference</string>\n\n  <!-- Text for checkbox to set custom list of Place Fields. -->\n  <string name=\"use_custom_fields\" translatable=\"false\">Manually set Place Fields?</string>\n\n  <!-- Text for the fetch place and photo button. -->\n  <string name=\"fetch_place_and_photo_button\" translatable=\"false\">Fetch Place and Photo</string>\n\n  <!-- PLACE IS OPEN -->\n\n  <string name=\"isOpen_fetch_place_button_text\" translatable=\"false\">Fetch Place then check IsOpen</string>\n  <string name=\"isOpen_is_open_button_text\" translatable=\"false\">Request IsOpen by Place ID</string>\n  <string name=\"isOpen_place_id_hint\" translatable=\"false\">Enter place ID</string>\n  <string name=\"isOpen_use_custom_fields_text\" translatable=\"false\">Manually set Place Fields?</string>\n  <string name=\"isOpen_spinner_description\" translatable=\"false\">Time Zone</string>\n  <string name=\"isOpen_date_hint\" translatable=\"false\">Select isOpen date</string>\n  <string name=\"isOpen_time_hint\" translatable=\"false\">Select isOpen time</string>\n  <string name=\"isOpen_use_custom_time_hint\" translatable=\"false\">Use custom isOpen time? (must set time zone, date, and time)</string>\n  <string name=\"isOpen_default_place_id\" translatable=\"false\">ChIJD3uTd9hx5kcR1IQvGfr8dbk</string>\n\n</resources>\n"
  },
  {
    "path": "demo-kotlin/build.gradle.kts",
    "content": "/*\n * Copyright 2025 Google LLC\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\nplugins {\n    id(\"places-demo.android.application\")\n    alias(libs.plugins.jetbrains.kotlin.android)\n    id(\"places-demo.secrets\")\n    alias(libs.plugins.jetbrains.kotlin.parcelize)\n    kotlin(\"kapt\")\n}\n\nandroid {\n    namespace = \"com.example.placesdemo\"\n\n    defaultConfig {\n        applicationId = \"com.example.placesdemo\"\n        versionCode = 1\n        versionName = \"1.0\"\n\n        multiDexEnabled = true\n    }\n\n    buildFeatures {\n        viewBinding = true\n        buildConfig = true\n    }\n\n    kotlin {\n        compilerOptions {\n            freeCompilerArgs.addAll(\n                \"-opt-in=kotlin.RequiresOptIn\",\n                \"-Xannotation-default-target=param-property\",\n                \"-Xskip-metadata-version-check\"\n            )\n        }\n    }\n}\n\ndependencies {\n    implementation(libs.appcompat)\n    implementation(libs.core.ktx)\n    implementation(libs.material)\n\n    implementation(libs.volley)\n    implementation(libs.glide)\n    kapt(libs.glide.compiler)\n    implementation(libs.viewbinding)\n    implementation(libs.multidex)\n\n    // Google Places\n    implementation(libs.places)\n    implementation(libs.maps.utils.ktx)\n}\n\n"
  },
  {
    "path": "demo-kotlin/local.defaults.properties",
    "content": "PLACES_API_KEY=\"YOUR_API_KEY\"\nMAPS_API_KEY=\"YOUR_API_KEY\""
  },
  {
    "path": "demo-kotlin/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n\n    <application\n        android:name=\".PlacesDemoApplication\"\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.Material3.DayNight.NoActionBar\">\n\n        <meta-data\n            android:name=\"com.google.android.gms.version\"\n            android:value=\"@integer/google_play_services_version\" />\n\n        <meta-data\n            android:name=\"com.google.android.geo.API_KEY\"\n            android:value=\"${MAPS_API_KEY}\" />\n\n        <activity\n            android:name=\".MainActivity\"\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=\".PlaceAutocompleteActivity\"\n            android:exported=\"true\"\n            />\n        <activity\n            android:name=\".AutocompleteAddressActivity\"\n            android:exported=\"true\" />\n        <activity\n            android:name=\".PlaceDetailsAndPhotosActivity\"\n            android:exported=\"true\" />\n        <activity\n            android:name=\".CurrentPlaceActivity\"\n            android:exported=\"true\" />\n        <activity\n            android:name=\".PlaceIsOpenActivity\"\n            android:exported=\"true\" />\n        <activity\n            android:name=\".programmatic_autocomplete.ProgrammaticAutocompleteGeocodingActivity\"\n            android:exported=\"true\"\n            />\n\n    </application>\n</manifest>"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/AutocompleteAddressActivity.kt",
    "content": "/*\n * Copyright 2022 Google LLC\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 */\npackage com.example.placesdemo\n\nimport android.Manifest.permission\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.content.res.Resources.NotFoundException\nimport android.location.Location\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.View\nimport android.view.ViewStub\nimport android.widget.Button\nimport android.widget.CheckBox\nimport android.widget.CompoundButton\nimport android.widget.Toast\nimport androidx.activity.result.ActivityResult\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.content.ContextCompat\nimport com.example.placesdemo.databinding.AutocompleteAddressActivityBinding\nimport com.google.android.gms.location.LocationServices\nimport com.google.android.gms.maps.*\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.MapStyleOptions\nimport com.google.android.gms.maps.model.Marker\nimport com.google.android.gms.maps.model.MarkerOptions\nimport com.google.android.libraries.places.api.model.AddressComponents\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.model.PlaceTypes\nimport com.google.android.libraries.places.widget.Autocomplete\nimport com.google.android.libraries.places.widget.model.AutocompleteActivityMode\nimport com.google.maps.android.SphericalUtil.computeDistanceBetween\nimport androidx.core.view.isGone\n\n/**\n *  Activity for using Place Autocomplete to assist filling out an address form.\n */\nclass AutocompleteAddressActivity : BaseActivity(),\n    OnMapReadyCallback {\n    private lateinit var mapPanel: View\n\n    private var mapFragment: SupportMapFragment? = null\n    private lateinit var coordinates: LatLng\n    private var map: GoogleMap? = null\n    private var marker: Marker? = null\n    private var checkProximity = false\n    private lateinit var binding: AutocompleteAddressActivityBinding\n    private val acceptedProximity = 150.0\n    private var startAutocompleteIntentListener = View.OnClickListener { view: View ->\n        view.setOnClickListener(null)\n        startAutocompleteIntent()\n    }\n\n    // [START maps_solutions_android_autocomplete_define]\n    private val startAutocomplete = registerForActivityResult(\n        ActivityResultContracts.StartActivityForResult(),\n        ActivityResultCallback { result: ActivityResult ->\n            binding.autocompleteAddress1.setOnClickListener(startAutocompleteIntentListener)\n            if (result.resultCode == RESULT_OK) {\n                val intent = result.data\n                if (intent != null) {\n                    val place = Autocomplete.getPlaceFromIntent(intent)\n\n                    // Write a method to read the address components from the Place\n                    // and populate the form with the address components\n                    Log.d(TAG, \"Place: \" + place.addressComponents)\n                    fillInAddress(place)\n                }\n            } else if (result.resultCode == RESULT_CANCELED) {\n                // The user canceled the operation.\n                Log.i(TAG, \"User canceled autocomplete\")\n            }\n        })\n    // [END maps_solutions_android_autocomplete_define]\n\n    // [START maps_solutions_android_autocomplete_intent]\n    private fun startAutocompleteIntent() {\n        // Set the fields to specify which types of place data to\n        // return after the user has made a selection.\n        val fields = listOf(\n            Place.Field.ADDRESS_COMPONENTS,\n            Place.Field.LOCATION, Place.Field.VIEWPORT\n        )\n\n        // Build the autocomplete intent with field, country, and type filters applied\n        val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)\n            .setCountries(listOf(\"US\"))\n            .setTypesFilter(listOf(PlaceTypes.ADDRESS))\n            .build(this)\n        startAutocomplete.launch(intent)\n    }\n    // [END maps_solutions_android_autocomplete_intent]\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = AutocompleteAddressActivityBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        setSupportActionBar(binding.topBar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        binding.topBar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n\n        // Attach an Autocomplete intent to the Address 1 EditText field\n        binding.autocompleteAddress1.setOnClickListener(startAutocompleteIntentListener)\n\n        // Update checkProximity when user checks the checkbox\n        val checkProximityBox = findViewById<CheckBox>(R.id.checkbox_proximity)\n        checkProximityBox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->\n            // Set the boolean to match user preference for when the Submit button is clicked\n            checkProximity = isChecked\n        }\n\n        // Submit and optionally check proximity\n        val saveButton = findViewById<Button>(R.id.autocomplete_save_button)\n        saveButton.setOnClickListener { saveForm() }\n\n        // Reset the form\n        val resetButton = findViewById<Button>(R.id.autocomplete_reset_button)\n        resetButton.setOnClickListener { clearForm() }\n    }\n\n    private fun saveForm() {\n        Log.d(TAG, \"checkProximity = $checkProximity\")\n        if (checkProximity) {\n            checkLocationPermissions()\n        } else {\n            Toast.makeText(this, R.string.autocomplete_skipped_message, Toast.LENGTH_SHORT).show()\n        }\n    }\n\n    // [START maps_solutions_android_location_permissions]\n    private fun checkLocationPermissions() {\n        if (ContextCompat.checkSelfPermission(this, permission.ACCESS_FINE_LOCATION)\n            == PackageManager.PERMISSION_GRANTED\n        ) {\n            getAndCompareLocations()\n        } else {\n            requestPermissionLauncher.launch(\n                permission.ACCESS_FINE_LOCATION\n            )\n        }\n    }\n    // [END maps_solutions_android_location_permissions]\n\n    @SuppressLint(\"MissingPermission\")\n    private fun getAndCompareLocations() {\n        // TODO: Detect and handle if user has entered or modified the address manually and update\n        // the coordinates variable to the Lat/Lng of the manually entered address. May use\n        // Geocoding API to convert the manually entered address to a Lat/Lng.\n        val enteredLocation = coordinates\n        map!!.isMyLocationEnabled = true\n\n        // [START maps_solutions_android_location_get]\n        val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)\n        fusedLocationClient.lastLocation\n            .addOnSuccessListener(this) { location: Location? ->\n                // Got last known location. In some rare situations this can be null.\n                if (location == null) {\n                    return@addOnSuccessListener\n                }\n                val currentLocation = LatLng(location.latitude, location.longitude)\n                // [START_EXCLUDE]\n                Log.d(TAG, \"device location = $currentLocation\")\n                Log.d(TAG, \"entered location = $enteredLocation\")\n\n                // [START maps_solutions_android_location_distance]\n                // Use the computeDistanceBetween function in the Maps SDK for Android Utility Library\n                // to use spherical geometry to compute the distance between two Lat/Lng points.\n                val distanceInMeters: Double =\n                    computeDistanceBetween(currentLocation, enteredLocation)\n                if (distanceInMeters <= acceptedProximity) {\n                    Log.d(TAG, \"location matched\")\n                    // TODO: Display UI based on the locations matching\n                } else {\n                    Log.d(TAG, \"location not matched\")\n                    // TODO: Display UI based on the locations not matching\n                }\n                // [END maps_solutions_android_location_distance]\n                // [END_EXCLUDE]\n            }\n    }\n    // [END maps_solutions_android_location_get]\n\n    private fun fillInAddress(place: Place) {\n        val components = place.addressComponents\n\n        // Get each component of the address from the place details,\n        // and then fill-in the corresponding field on the form.\n        // Possible AddressComponent types are documented at https://goo.gle/32SJPM1\n        if (components != null) {\n            with (components.toAddress()) {\n                binding.autocompleteAddress1.setText(streetAddress)\n                binding.autocompletePostal.setText(fullPostalCode)\n                binding.autocompleteCity.setText(locality)\n                binding.autocompleteState.setText(adminArea)\n                binding.autocompleteCountry.setText(country)\n            }\n        }\n\n        // After filling the form with address components from the Autocomplete\n        // prediction, set cursor focus on the second address line to encourage\n        // entry of sub-premise information such as apartment, unit, or floor number.\n        binding.autocompleteAddress2.requestFocus()\n\n        // Add a map for visual confirmation of the address\n        showMap(place)\n    }\n\n    // [START maps_solutions_android_autocomplete_map_add]\n    private fun showMap(place: Place) {\n        coordinates = place.location as LatLng\n\n        // It isn't possible to set a fragment's id programmatically so we set a tag instead and\n        // search for it using that.\n        mapFragment =\n            supportFragmentManager.findFragmentByTag(MAP_FRAGMENT_TAG) as SupportMapFragment?\n\n        // We only create a fragment if it doesn't already exist.\n        if (mapFragment == null) {\n            mapPanel = (findViewById<View>(R.id.stub_map) as ViewStub).inflate()\n            val mapOptions = GoogleMapOptions()\n            mapOptions.mapToolbarEnabled(false)\n\n            // To programmatically add the map, we first create a SupportMapFragment.\n            mapFragment = SupportMapFragment.newInstance(mapOptions)\n\n            // Then we add it using a FragmentTransaction.\n            supportFragmentManager\n                .beginTransaction()\n                .add(\n                    R.id.confirmation_map,\n                    mapFragment!!,\n                    MAP_FRAGMENT_TAG\n                )\n                .commit()\n            mapFragment!!.getMapAsync(this)\n        } else {\n            updateMap(coordinates)\n        }\n    }\n    // [END maps_solutions_android_autocomplete_map_add]\n\n    private fun updateMap(latLng: LatLng) {\n        marker!!.position = latLng\n        map!!.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f))\n        if (mapPanel.isGone) {\n            mapPanel.visibility = View.VISIBLE\n        }\n    }\n\n    // [START maps_solutions_android_autocomplete_map_ready]\n    override fun onMapReady(googleMap: GoogleMap) {\n        map = googleMap\n        try {\n            // Customise the styling of the base map using a JSON object defined\n            // in a string resource.\n            val success = map!!.setMapStyle(\n                MapStyleOptions.loadRawResourceStyle(this, R.raw.style_json)\n            )\n            if (!success) {\n                Log.e(TAG, \"Style parsing failed.\")\n            }\n        } catch (e: NotFoundException) {\n            Log.e(TAG, \"Can't find style. Error: \", e)\n        }\n        map!!.moveCamera(CameraUpdateFactory.newLatLngZoom(coordinates, 15f))\n        marker = map!!.addMarker(MarkerOptions().position(coordinates))\n    }\n    // [END maps_solutions_android_autocomplete_map_ready]\n\n    private fun clearForm() {\n        binding.autocompleteAddress1.setText(\"\")\n        binding.autocompleteAddress2.text.clear()\n        binding.autocompleteCity.text.clear()\n        binding.autocompleteState.text.clear()\n        binding.autocompletePostal.text.clear()\n        binding.autocompleteCountry.text.clear()\n        mapPanel.visibility = View.GONE\n        binding.autocompleteAddress1.requestFocus()\n    }\n\n    // [START maps_solutions_android_permission_request]\n    // Register the permissions callback, which handles the user's response to the\n    // system permissions dialog. Save the return value, an instance of\n    // ActivityResultLauncher, as an instance variable.\n    private val requestPermissionLauncher = registerForActivityResult(\n        ActivityResultContracts.RequestPermission()\n    ) { isGranted: Boolean ->\n        if (isGranted) {\n            // Since ACCESS_FINE_LOCATION is the only permission in this sample,\n            // run the location comparison task once permission is granted.\n            // Otherwise, check which permission is granted.\n            getAndCompareLocations()\n        } else {\n            // Fallback behavior if user denies permission\n            Log.d(TAG, \"User denied permission\")\n        }\n    }\n    // [END maps_solutions_android_permission_request]\n\n    companion object {\n        private val TAG = AutocompleteAddressActivity::class.java.simpleName\n        private const val MAP_FRAGMENT_TAG = \"MAP\"\n    }\n}\n\n/**\n * Data class representing a postal address.\n *\n * @property streetNumber The street number of the address.\n * @property locality The locality or neighborhood of the address.\n * @property route The street name or route of the address.\n * @property postCode The primary part of the postal code.\n * @property postCodeSuffix The optional suffix or extension of the postal code.\n * @property adminArea The administrative area, such as state, province, or region.\n * @property country The country of the address.\n */\nprivate data class Address(\n    val streetNumber: String,\n    val locality: String,\n    val route: String,\n    val postCode: String,\n    val postCodeSuffix: String,\n    val adminArea: String,\n    val country: String\n) {\n    val fullPostalCode: String\n        get() = listOf(postCode, postCodeSuffix).filter { it.isNotBlank() }.joinToString(\"-\")\n    val streetAddress: String\n        get() = listOf(streetNumber, route).filter { it.isNotBlank() }.joinToString(\" \")\n}\n\n/**\n * Converts an [AddressComponents] object to an [Address] object.\n *\n * This function iterates through the address components, creating a map where the key is the component type\n * (e.g., \"street_number\", \"route\") and the value is the component name. It then uses this map to populate\n * the fields of an [Address] object.\n *\n * If a specific address component type is not found in the [AddressComponents], the corresponding field in\n * the [Address] object will be set to an empty string.\n *\n * @return An [Address] object representing the address information extracted from the [AddressComponents].\n */\nprivate fun AddressComponents.toAddress(): Address {\n    val addressMap = this.asList().associate { it.types[0] to it.name }\n    return Address(\n        streetNumber = addressMap[\"street_number\"] ?: \"\",\n        route = addressMap[\"route\"] ?: \"\",\n        postCode = addressMap[\"postal_code\"] ?: \"\",\n        postCodeSuffix = addressMap[\"postal_code_suffix\"] ?: \"\",\n        locality = addressMap[\"locality\"] ?: \"\",\n        adminArea = addressMap[\"administrative_area_level_1\"] ?: \"\",\n        country = addressMap[\"country\"] ?: \"\"\n    )\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/BaseActivity.kt",
    "content": "// Copyright 2025 Google LLC\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.\npackage com.example.placesdemo\n\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport android.view.MenuItem\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowCompat\nimport androidx.core.view.WindowInsetsCompat\n\nopen class BaseActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n\n        val insetsController = WindowCompat.getInsetsController(window, window.decorView)\n        val isLightTheme = resources.configuration.uiMode and\n                Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_NO\n        insetsController.isAppearanceLightStatusBars = isLightTheme\n\n        // It's recommended to apply insets using a listener like this,\n        // especially when dealing with normal views rather than Compose.\n        // This makes sure that the insets are applied after the view is attached\n        // and ensures that the insets are applied correctly every time the\n        // view is laid out.\n        val contentView = findViewById<android.view.View>(android.R.id.content)\n        ViewCompat.setOnApplyWindowInsetsListener(contentView) { view, windowInsets ->\n            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())\n            // Apply the insets as padding to the view. Here we're setting all of the\n            // dimensions, but apply as appropriate to your layout.\n            // This is where you can adjust your UI based on the cutouts.\n            view.setPadding(insets.left, insets.top, insets.right, insets.bottom)\n\n            // Return CONSUMED if you don't want to pass the insets down to the children\n            WindowInsetsCompat.CONSUMED\n        }\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == android.R.id.home) {\n            finish()\n            return true\n        }\n        return super.onOptionsItemSelected(item)\n    }\n}\n"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/CurrentPlaceActivity.kt",
    "content": "/*\n * Copyright 2023 Google LLC\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 */\npackage com.example.placesdemo\n\nimport android.Manifest.permission\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.RequiresPermission\nimport androidx.core.content.ContextCompat\nimport com.example.placesdemo.databinding.CurrentPlaceActivityBinding\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.CircularBounds\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.android.libraries.places.api.net.SearchNearbyRequest\nimport com.google.android.libraries.places.api.net.SearchNearbyResponse\n\n/**\n * Activity to demonstrate [PlacesClient.findCurrentPlace].\n */\nclass CurrentPlaceActivity : BaseActivity() {\n    private lateinit var placesClient: PlacesClient\n    private lateinit var fieldSelector: FieldSelector\n\n    private lateinit var binding: CurrentPlaceActivityBinding\n\n    val boulderCenter = LatLng(40.01499, -105.27055)\n    val radiusMeters = 5000.0\n\n    @SuppressLint(\"MissingPermission\")\n    val requestPermissionLauncher =\n        registerForActivityResult(\n            ActivityResultContracts.RequestMultiplePermissions()\n        ) { permissions ->\n            when {\n                permissions[permission.ACCESS_FINE_LOCATION] == true || permissions[permission.ACCESS_COARSE_LOCATION] == true -> {\n                    // Only approximate location access granted.\n                    findCurrentPlaceWithPermissions()\n                }\n                else -> {\n                    Toast.makeText(\n                        this,\n                        \"Either ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permissions are required\",\n                        Toast.LENGTH_SHORT\n                    ).show()\n                }\n            }\n        }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = CurrentPlaceActivityBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.topBar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        binding.topBar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient(this)\n\n        // Set view objects\n        // Exclude fields that are not supported by search endpoints\n        val placeFields = FieldSelector.allExcept(\n            Place.Field.ADDRESS_COMPONENTS,\n            Place.Field.CURBSIDE_PICKUP,\n            Place.Field.CURRENT_OPENING_HOURS,\n            Place.Field.DELIVERY,\n            Place.Field.DINE_IN,\n            Place.Field.EDITORIAL_SUMMARY,\n            Place.Field.INTERNATIONAL_PHONE_NUMBER,\n            Place.Field.OPENING_HOURS,\n            Place.Field.NATIONAL_PHONE_NUMBER,\n            Place.Field.RESERVABLE,\n            Place.Field.SECONDARY_OPENING_HOURS,\n            Place.Field.SERVES_BEER,\n            Place.Field.SERVES_BREAKFAST,\n            Place.Field.SERVES_BRUNCH,\n            Place.Field.SERVES_DINNER,\n            Place.Field.SERVES_LUNCH,\n            Place.Field.SERVES_VEGETARIAN_FOOD,\n            Place.Field.SERVES_WINE,\n            Place.Field.TAKEOUT,\n            Place.Field.UTC_OFFSET,\n            Place.Field.WEBSITE_URI,\n            Place.Field.ACCESSIBILITY_OPTIONS,\n        )\n        fieldSelector = FieldSelector(\n            binding.useCustomFields,\n            binding.customFieldsList,\n            savedInstanceState,\n            placeFields\n        )\n        setLoading(false)\n\n        // Set listeners for programmatic Find Current Place\n        binding.findCurrentPlaceButton.setOnClickListener {\n            findCurrentPlace()\n        }\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        fieldSelector.onSaveInstanceState(outState)\n    }\n\n    /**\n     * Check whether permissions have been granted or not, and ultimately proceeds to either\n     * request them or runs {@link #findCurrentPlaceWithPermissions() findCurrentPlaceWithPermissions}\n     */\n    @SuppressLint(\"MissingPermission\")\n    private fun findCurrentPlace() {\n        if (hasOnePermissionGranted(permission.ACCESS_FINE_LOCATION, permission.ACCESS_COARSE_LOCATION)) {\n            findCurrentPlaceWithPermissions()\n            return\n        } else {\n            requestPermissionLauncher.launch(\n                arrayOf(\n                    permission.ACCESS_FINE_LOCATION,\n                    permission.ACCESS_COARSE_LOCATION\n                )\n            )\n        }\n    }\n\n    /**\n     * Fetches a list of [com.google.android.libraries.places.api.model.PlaceLikelihood] instances that represent the Places the user is\n     * most likely to be at currently.\n     */\n    @RequiresPermission(allOf = [permission.ACCESS_FINE_LOCATION, permission.ACCESS_WIFI_STATE])\n    private fun findCurrentPlaceWithPermissions() {\n        setLoading(true)\n        val currentPlaceRequest = SearchNearbyRequest.newInstance(CircularBounds.newInstance(boulderCenter, radiusMeters), placeFields)\n        val currentPlaceTask = placesClient.searchNearby(currentPlaceRequest)\n        currentPlaceTask.addOnSuccessListener { response: SearchNearbyResponse? ->\n            response?.let {\n                binding.response.text = StringUtil.stringify(it, isDisplayRawResultsChecked)\n            }\n        }\n        currentPlaceTask.addOnFailureListener { exception: Exception ->\n            exception.printStackTrace()\n            binding.response.text = exception.message\n        }\n        currentPlaceTask.addOnCompleteListener { setLoading(false) }\n    }\n\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n    private val placeFields: List<Place.Field>\n        get() = if (binding.useCustomFields.isChecked) {\n            fieldSelector.selectedFields\n        } else {\n            fieldSelector.allFields\n        }\n\n    private val isDisplayRawResultsChecked: Boolean\n        get() = binding.displayRawResults.isChecked\n\n    private fun setLoading(loading: Boolean) {\n        binding.loading.visibility = if (loading) View.VISIBLE else View.INVISIBLE\n    }\n\n    private fun hasOnePermissionGranted(vararg permissions: String): Boolean =\n        permissions.any {\n            ContextCompat.checkSelfPermission(\n                this,\n                it\n            ) == PackageManager.PERMISSION_GRANTED\n        }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/FieldSelector.kt",
    "content": "/*\n * Copyright 2018 Google LLC\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 */\npackage com.example.placesdemo\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.AdapterView\nimport android.widget.AdapterView.OnItemClickListener\nimport android.widget.ArrayAdapter\nimport android.widget.CheckBox\nimport android.widget.CheckedTextView\nimport android.widget.ListView\nimport android.widget.TextView\nimport androidx.appcompat.app.AlertDialog\nimport com.google.android.libraries.places.api.model.Place\nimport java.util.*\n\n/** Helper class for selecting [Place.Field] values.  */\nclass FieldSelector(\n    enableView: CheckBox,\n    outputView: TextView,\n    savedState: Bundle?,\n    validFields: List<Place.Field> = Place.Field.entries\n) {\n\n    private val fieldStates: MutableMap<Place.Field, State> = EnumMap(Place.Field::class.java)\n    private val outputView: TextView\n\n    /**\n     * Shows dialog to allow user to select [Place.Field] values they want.\n     */\n    private fun showDialog(context: Context?) {\n        val listView = ListView(context)\n        val adapter = PlaceFieldArrayAdapter(context, fieldStates.values.toList())\n        listView.adapter = adapter\n        listView.onItemClickListener = adapter\n        AlertDialog.Builder(context!!)\n            .setTitle(\"Select Place Fields\")\n            .setPositiveButton(\n                \"Done\"\n            ) { _, _ -> outputView.text = selectedString }\n            .setView(listView)\n            .show()\n    }\n\n    /**\n     * Returns all [Place.Field] that are selectable.\n     */\n    val allFields: List<Place.Field>\n        get() = ArrayList(fieldStates.keys)\n\n    /**\n     * Returns all [Place.Field] values the user selected.\n     */\n    val selectedFields: List<Place.Field>\n        get() {\n            val selectedList: MutableList<Place.Field> = ArrayList()\n            for ((key, value) in fieldStates) {\n                if (value.checked) {\n                    selectedList.add(key)\n                }\n            }\n            return selectedList\n        }\n\n    /**\n     * Returns a String representation of all selected [Place.Field] values. See [ ][.getSelectedFields].\n     */\n    val selectedString: String\n        get() {\n            val builder = StringBuilder()\n            for (eachField in selectedFields) {\n                builder.append(eachField).append(\"\\n\")\n            }\n            return builder.toString()\n        }\n\n    fun onSaveInstanceState(bundle: Bundle) {\n        val fields = selectedFields\n        val serializedFields = ArrayList<Int>()\n        for (field in fields) {\n            serializedFields.add(field.ordinal)\n        }\n        bundle.putIntegerArrayList(SELECTED_PLACE_FIELDS_KEY, serializedFields)\n    }\n\n    private fun restoreState(selectedFields: List<Int>) {\n        for (serializedField in selectedFields) {\n            val field = Place.Field.entries[serializedField]\n            val state = fieldStates[field]\n            if (state != null) {\n                state.checked = true\n            }\n        }\n    }\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n    /**\n     * Holds selection state for a place field.\n     */\n    class State(val field: Place.Field) {\n        var checked = false\n    }\n\n    private class PlaceFieldArrayAdapter(\n        context: Context?,\n        states: List<State>?\n    ) : ArrayAdapter<State>(\n        context!!,\n        android.R.layout.simple_list_item_multiple_choice,\n        states!!.toMutableList()\n    ), OnItemClickListener {\n        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {\n            val view = super.getView(position, convertView, parent)\n            val state = getItem(position)\n            updateView(view, state)\n            return view\n        }\n\n        override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {\n            val state = getItem(position)\n            state!!.checked = !state.checked\n            updateView(view, state)\n        }\n\n        companion object {\n            private fun updateView(view: View, state: State?) {\n                if (view is CheckedTextView && state != null) {\n                    view.text = state.field.toString()\n                    view.isChecked = state.checked\n                }\n            }\n        }\n    }\n\n    companion object {\n        private const val SELECTED_PLACE_FIELDS_KEY = \"selected_place_fields\"\n\n        /**\n         * Returns all [Place.Field] values except those passed in.\n         * <p>\n         * Convenience method for when most [Place.Field] values are desired. Useful for APIs that do\n         * no support all [Place.Field] values.\n         */\n        fun allExcept(vararg placeFieldsToOmit: Place.Field): List<Place.Field> {\n            // Arrays.asList is immutable, create a mutable list to allow removing fields\n            val placeFields: MutableList<Place.Field> = ArrayList(listOf(*Place.Field.entries.toTypedArray()))\n            placeFields.removeAll(placeFieldsToOmit)\n            return placeFields\n        }\n    }\n\n    init {\n        for (field in validFields) {\n            fieldStates[field] = State(field)\n        }\n        if (savedState != null) {\n            val selectedFields: List<Int>? = savedState.getIntegerArrayList(SELECTED_PLACE_FIELDS_KEY)\n            selectedFields?.let { restoreState(it) }\n            outputView.text = selectedString\n        }\n        outputView.setOnClickListener { v: View ->\n            if (v.isEnabled) {\n                showDialog(v.context)\n            }\n        }\n        enableView.setOnClickListener { view: View ->\n            val isChecked = enableView.isChecked\n            outputView.isEnabled = isChecked\n            if (isChecked) {\n                showDialog(view.context)\n            } else {\n                outputView.text = \"\"\n                for (state in fieldStates.values) {\n                    state.checked = false\n                }\n            }\n        }\n        this.outputView = outputView\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/MainActivity.kt",
    "content": "/*\n * Copyright 2022 Google LLC\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 */\npackage com.example.placesdemo\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.os.Bundle\nimport android.widget.Button\nimport com.example.placesdemo.databinding.ActivityMainBinding\nimport com.example.placesdemo.programmatic_autocomplete.ProgrammaticAutocompleteGeocodingActivity\n\nclass MainActivity : BaseActivity() {\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        setSupportActionBar(binding.topBar)\n        binding.topBar.setNavigationIcon(R.drawable.ic_exit)\n        binding.topBar.setNavigationOnClickListener {\n            finishAffinity() // Closes the app and all parent activities\n        }\n\n        setLaunchActivityClickListener(binding.autocompleteButton, PlaceAutocompleteActivity::class.java)\n        setLaunchActivityClickListener(binding.autocompleteAddressButton, AutocompleteAddressActivity::class.java)\n        setLaunchActivityClickListener(binding.programmaticAutocompleteButton, ProgrammaticAutocompleteGeocodingActivity::class.java)\n        setLaunchActivityClickListener(binding.currentPlaceButton, CurrentPlaceActivity::class.java)\n        setLaunchActivityClickListener(binding.placeAndPhotoButton, PlaceDetailsAndPhotosActivity::class.java)\n        setLaunchActivityClickListener(binding.isOpenButton, PlaceIsOpenActivity::class.java)\n    }\n\n    private fun setLaunchActivityClickListener(button: Button, activityClassToLaunch: Class<out Activity>) {\n        button.setOnClickListener {\n            val intent = Intent(this@MainActivity, activityClassToLaunch)\n            startActivity(intent)\n        }\n    }\n}\n"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/PlaceAutocompleteActivity.kt",
    "content": "/*\n * Copyright 2018 Google LLC\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 */\npackage com.example.placesdemo\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.view.View\nimport android.widget.CheckBox\nimport android.widget.TextView\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.IdRes\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AlertDialog\nimport com.example.placesdemo.databinding.PlaceAutocompleteActivityBinding\nimport com.google.android.gms.common.api.Status\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.AutocompleteSessionToken\nimport com.google.android.libraries.places.api.model.LocationBias\nimport com.google.android.libraries.places.api.model.LocationRestriction\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.model.RectangularBounds\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.android.libraries.places.widget.PlaceAutocompleteActivity\nimport com.google.android.libraries.places.widget.AutocompleteSupportFragment\nimport com.google.android.libraries.places.widget.PlaceAutocomplete\nimport com.google.android.libraries.places.widget.listener.PlaceSelectionListener\nimport com.google.android.libraries.places.widget.model.AutocompleteActivityMode\n\n/**\n * Activity to demonstrate Place Autocomplete (activity widget intent, fragment widget, and\n * [PlacesClient.findAutocompletePredictions]).\n */\nclass PlaceAutocompleteActivity : BaseActivity() {\n\n    private lateinit var placesClient: PlacesClient\n    private lateinit var fieldSelector: FieldSelector\n\n    private lateinit var binding: PlaceAutocompleteActivityBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = PlaceAutocompleteActivityBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        setSupportActionBar(binding.topBar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        binding.topBar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient(this)\n\n        // Set up view objects\n\n        val useTypesFilterCheckBox =\n            findViewById<CheckBox>(R.id.autocomplete_use_types_filter_checkbox)\n        useTypesFilterCheckBox.setOnCheckedChangeListener { _, isChecked: Boolean ->\n            binding.autocompleteTypesFilterEdittext.isEnabled = isChecked\n        }\n        fieldSelector = FieldSelector(\n            findViewById(R.id.use_custom_fields),\n            findViewById(R.id.custom_fields_list),\n            savedInstanceState\n        )\n        setupAutocompleteSupportFragment()\n\n        // Set listeners for Autocomplete activity\n        binding.autocompleteActivityButton\n            .setOnClickListener { startAutocompleteActivity() }\n\n        // Set listeners for programmatic Autocomplete\n        binding.fetchAutocompletePredictionsButton\n            .setOnClickListener { findAutocompletePredictions() }\n\n        // UI initialization\n        setLoading(false)\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        fieldSelector.onSaveInstanceState(outState)\n    }\n\n    private fun setupAutocompleteSupportFragment() {\n        val autocompleteSupportFragment =\n            supportFragmentManager.findFragmentById(R.id.autocomplete_support_fragment) as AutocompleteSupportFragment?\n        autocompleteSupportFragment!!.setPlaceFields(placeFields)\n        autocompleteSupportFragment.setOnPlaceSelectedListener(placeSelectionListener)\n        findViewById<View>(R.id.autocomplete_support_fragment_update_button)\n            .setOnClickListener {\n                autocompleteSupportFragment\n                    .setPlaceFields(placeFields)\n                    .setText(query)\n                    .setHint(hint)\n                    .setCountries(countries)\n                    .setLocationBias(locationBias)\n                    .setLocationRestriction(locationRestriction)\n                    .setTypesFilter(getTypesFilter())\n                    .setActivityMode(mode)\n            }\n    }\n\n    private val placeSelectionListener: PlaceSelectionListener\n        get() = object : PlaceSelectionListener {\n            override fun onPlaceSelected(place: Place) {\n                binding.response.text =\n                    StringUtil.stringifyAutocompleteWidget(place, isDisplayRawResultsChecked)\n            }\n\n            override fun onError(status: Status) {\n                binding.response.text = status.statusMessage\n            }\n        }\n\n    /**\n     * Launches Autocomplete activity and handles result\n     */\n    private var autocompleteLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {\n        result ->\n        when (result.resultCode) {\n            RESULT_OK -> {\n                val data: Intent? = result.data\n                if (data != null) {\n                    val place = PlaceAutocomplete.getPredictionFromIntent(data)\n                    binding.response.text =\n                        StringUtil.stringifyAutocompletePrediction(place, isDisplayRawResultsChecked)\n                }\n            }\n            PlaceAutocompleteActivity.RESULT_ERROR -> {\n                val status = PlaceAutocomplete.getResultStatusFromIntent(intent)\n                binding.response.text = status?.statusMessage\n            }\n            RESULT_CANCELED -> {\n                // The user canceled the operation.\n            }\n        }\n    }\n\n    private fun startAutocompleteActivity() {\n        val autocompleteIntent = PlaceAutocomplete.IntentBuilder()\n            .setInitialQuery(query)\n            .setCountries(countries)\n            .setLocationBias(locationBias)\n            .setLocationRestriction(locationRestriction)\n            .setTypesFilter(getTypesFilter())\n            .build(this)\n        autocompleteLauncher.launch(autocompleteIntent)\n    }\n\n    private fun findAutocompletePredictions() {\n        setLoading(true)\n        val requestBuilder = FindAutocompletePredictionsRequest.builder()\n            .setQuery(query)\n            .setCountries(countries)\n            .setOrigin(origin)\n            .setLocationBias(locationBias)\n            .setLocationRestriction(locationRestriction)\n            .setTypesFilter(getTypesFilter())\n        if (isUseSessionTokenChecked) {\n            requestBuilder.sessionToken = AutocompleteSessionToken.newInstance()\n        }\n        val task = placesClient.findAutocompletePredictions(requestBuilder.build())\n        task.addOnSuccessListener { response: FindAutocompletePredictionsResponse? ->\n            response?.let {\n                binding.response.text = StringUtil.stringify(it, isDisplayRawResultsChecked)\n            }\n        }\n        task.addOnFailureListener { exception: Exception ->\n            exception.printStackTrace()\n            binding.response.text = exception.message\n        }\n        task.addOnCompleteListener {\n            setLoading(\n                false\n            )\n        }\n    }\n\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n    private val placeFields: List<Place.Field>\n        get() = if ((findViewById<View>(R.id.use_custom_fields) as CheckBox).isChecked) {\n            fieldSelector.selectedFields\n        } else {\n            fieldSelector.allFields\n        }\n\n    private val query: String?\n        get() = getTextViewValue(R.id.autocomplete_query)\n\n    private val hint: String?\n        get() = getTextViewValue(R.id.autocomplete_hint)\n\n    private val countries: List<String>\n        get() {\n            val countryString = getTextViewValue(R.id.autocomplete_country) ?: return emptyList()\n            return StringUtil.countriesStringToArrayList(countryString)\n        }\n\n    private fun getTextViewValue(@IdRes textViewResId: Int): String? {\n        val value = (findViewById<View>(textViewResId) as TextView).text.toString()\n        return if (TextUtils.isEmpty(value)) null else value\n    }\n\n    private val locationBias: LocationBias?\n        get() = getBounds(\n            R.id.autocomplete_location_bias_south_west, R.id.autocomplete_location_bias_north_east\n        )\n\n    private val locationRestriction: LocationRestriction?\n        get() = getBounds(\n            R.id.autocomplete_location_restriction_south_west,\n            R.id.autocomplete_location_restriction_north_east\n        )\n\n    private fun getBounds(resIdSouthWest: Int, resIdNorthEast: Int): RectangularBounds? {\n        val southWest = findViewById<TextView>(resIdSouthWest).text.toString()\n        val northEast = findViewById<TextView>(resIdNorthEast).text.toString()\n        if (TextUtils.isEmpty(southWest) && TextUtils.isEmpty(northEast)) {\n            return null\n        }\n        val bounds = StringUtil.convertToLatLngBounds(southWest, northEast)\n        if (bounds == null) {\n            showErrorAlert(R.string.error_alert_message_invalid_bounds)\n            return null\n        }\n        return RectangularBounds.newInstance(bounds)\n    }\n\n    private val origin: LatLng?\n        get() {\n            val originStr =\n                findViewById<TextView>(R.id.autocomplete_location_origin).text.toString()\n            if (TextUtils.isEmpty(originStr)) {\n                return null\n            }\n            val origin = StringUtil.convertToLatLng(originStr)\n            if (origin == null) {\n                showErrorAlert(R.string.error_alert_message_invalid_origin)\n                return null\n            }\n            return origin\n        }\n\n    private fun getTypesFilter(): List<String> {\n        return if (binding.autocompleteTypesFilterEdittext.isEnabled)\n            binding.autocompleteTypesFilterEdittext.text.toString().split(\"[\\\\s,]+\".toRegex())\n        else emptyList()\n    }\n\n    // This Enum is deprecated, but there is no replacement. See https://developers.google.com/maps/documentation/places/android-sdk/reference/com/google/android/libraries/places/widget/model/AutocompleteActivityMode\n    private val mode: AutocompleteActivityMode\n        get() {\n            val isOverlayMode =\n                binding.autocompleteActivityOverlayMode.isChecked\n            return if (isOverlayMode) AutocompleteActivityMode.OVERLAY else AutocompleteActivityMode.FULLSCREEN\n        }\n\n    private val isDisplayRawResultsChecked: Boolean\n        get() = binding.displayRawResults.isChecked\n\n    private val isUseSessionTokenChecked: Boolean\n        get() = binding.autocompleteUseSessionToken.isChecked\n\n    private fun setLoading(loading: Boolean) {\n        findViewById<View>(R.id.loading).visibility = if (loading) View.VISIBLE else View.INVISIBLE\n    }\n\n    private fun showErrorAlert(@StringRes messageResId: Int) {\n        AlertDialog.Builder(this)\n            .setTitle(R.string.error_alert_title)\n            .setMessage(messageResId)\n            .show()\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/PlaceDetailsAndPhotosActivity.kt",
    "content": "/*\n * Copyright 2022 Google LLC\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 */\npackage com.example.placesdemo\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.CheckBox\nimport android.widget.EditText\nimport android.widget.TextView\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AlertDialog\nimport com.bumptech.glide.Glide\nimport com.example.placesdemo.databinding.PlaceDetailsAndPhotosActivityBinding\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.PhotoMetadata\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse\nimport com.google.android.libraries.places.api.net.FetchResolvedPhotoUriRequest\nimport com.google.android.libraries.places.api.net.FetchResolvedPhotoUriResponse\nimport com.google.android.libraries.places.api.net.PlacesClient\n\n/**\n * Activity to demonstrate [PlacesClient.fetchPlace].\n */\nclass PlaceDetailsAndPhotosActivity : BaseActivity() {\n    private lateinit var placesClient: PlacesClient\n    private lateinit var fieldSelector: FieldSelector\n\n    private var photo: PhotoMetadata? = null\n\n    private lateinit var binding: PlaceDetailsAndPhotosActivityBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = PlaceDetailsAndPhotosActivityBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        setSupportActionBar(binding.topBar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        binding.topBar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient(this)\n        // Restore photo from saved instance state if it exists\n        savedInstanceState?.getParcelable<PhotoMetadata>(FETCHED_PHOTO_KEY)?.let { savedPhoto ->\n            photo = savedPhoto\n        }\n\n        binding.fetchPhotoCheckbox.setOnCheckedChangeListener { _, isChecked: Boolean ->\n            setPhotoSizingEnabled(\n                isChecked\n            )\n        }\n        binding.useCustomPhotoReference.setOnCheckedChangeListener { _, isChecked: Boolean ->\n            setCustomPhotoReferenceEnabled(\n                isChecked\n            )\n        }\n        fieldSelector = FieldSelector(\n            binding.useCustomFields,\n            binding.customFieldsList,\n            savedInstanceState\n        )\n\n        // Set listeners for programmatic Fetch Place\n        binding.fetchPlaceAndPhotoButton.setOnClickListener { fetchPlace() }\n\n        // UI initialization\n        setLoading(false)\n        setPhotoSizingEnabled(binding.fetchPhotoCheckbox.isChecked)\n        setCustomPhotoReferenceEnabled(binding.useCustomPhotoReference.isChecked)\n        photo?.let {\n            fetchPhoto(it)\n        }\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        fieldSelector.onSaveInstanceState(outState)\n        outState.putParcelable(FETCHED_PHOTO_KEY, photo)\n    }\n\n    /**\n     * Fetches the [Place] specified via the UI and displays it. May also trigger [ ][.fetchPhoto] if set in the UI.\n     */\n    private fun fetchPlace() {\n        clearViews()\n\n        dismissKeyboard(binding.placeIdField)\n        val isFetchPhotoChecked = isFetchPhotoChecked\n        val isFetchIconChecked = isFetchIconChecked\n        val placeFields = placeFields\n        val customPhotoReference = customPhotoReference\n        if (!validateInputs(\n                isFetchPhotoChecked,\n                isFetchIconChecked,\n                placeFields,\n                customPhotoReference\n            )\n        ) {\n            return\n        }\n        setLoading(true)\n        val request = FetchPlaceRequest.newInstance(placeId, placeFields)\n        val placeTask = placesClient.fetchPlace(request)\n        placeTask.addOnSuccessListener { response: FetchPlaceResponse ->\n            binding.response.text = StringUtil.stringify(response, isDisplayRawResultsChecked)\n            if (isFetchPhotoChecked) {\n                attemptFetchPhoto(response.place)\n            }\n            if (isFetchIconChecked) {\n                attemptFetchIcon(response.place)\n            }\n        }\n        placeTask.addOnFailureListener { exception: Exception ->\n            exception.printStackTrace()\n            binding.response.text = exception.message\n        }\n        placeTask.addOnCompleteListener { setLoading(false) }\n    }\n\n    private fun attemptFetchPhoto(place: Place) {\n        val photoMetadatas = place.photoMetadatas\n        if (photoMetadatas != null && photoMetadatas.isNotEmpty()) {\n            fetchPhoto(photoMetadatas[0])\n        }\n    }\n\n    private fun attemptFetchIcon(place: Place) {\n        binding.icon.setImageBitmap(null)\n        place.iconBackgroundColor?.let { binding.icon.setBackgroundColor(it) }\n        val url = place.iconMaskUrl\n        Glide.with(this).load(url).into(binding.icon)\n    }\n\n    /**\n     * Fetches a Bitmap using the Places API and displays it.\n     *\n     * @param photoMetadata from a [Place] instance.\n     */\n    private fun fetchPhoto(photoMetadata: PhotoMetadata) {\n        var localPhotoMetadata: PhotoMetadata? = photoMetadata\n        photo = localPhotoMetadata\n        binding.photo.setImageBitmap(null)\n        setLoading(true)\n        val customPhotoReference = customPhotoReference\n        if (!TextUtils.isEmpty(customPhotoReference)) {\n            localPhotoMetadata = PhotoMetadata.builder(customPhotoReference).build()\n        }\n        val photoRequestBuilder = FetchResolvedPhotoUriRequest.builder(localPhotoMetadata!!)\n        val maxWidth = readIntFromTextView(binding.photoMaxWidth)\n        if (maxWidth != null) {\n            photoRequestBuilder.maxWidth = maxWidth\n        }\n        val maxHeight = readIntFromTextView(binding.photoMaxHeight)\n        if (maxHeight != null) {\n            photoRequestBuilder.maxHeight = maxHeight\n        }\n        val photoTask = placesClient.fetchResolvedPhotoUri(photoRequestBuilder.build())\n        photoTask.addOnSuccessListener { response: FetchResolvedPhotoUriResponse ->\n            val uri = response.uri\n            if (uri != null) {\n                Glide.with(binding.photo.context)\n                    .load(uri)\n                    .into(binding.photo)\n\n                StringUtil.prepend(binding.photoMetadata, StringUtil.stringify(uri))\n            } else {\n                StringUtil.prepend(binding.photoMetadata, \"No photo available\")\n            }\n        }\n        photoTask.addOnFailureListener { exception: Exception ->\n            exception.printStackTrace()\n            StringUtil.prepend(binding.response, \"Photo: \" + exception.message)\n        }\n        photoTask.addOnCompleteListener { setLoading(false) }\n    }\n\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n    private fun dismissKeyboard(focusedEditText: EditText) {\n        val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager\n        imm.hideSoftInputFromWindow(focusedEditText.windowToken, 0)\n    }\n\n    private fun validateInputs(\n        isFetchPhotoChecked: Boolean,\n        isFetchIconChecked: Boolean,\n        placeFields: List<Place.Field>,\n        customPhotoReference: String\n    ): Boolean {\n        if (isFetchPhotoChecked) {\n            if (!placeFields.contains(Place.Field.PHOTO_METADATAS)) {\n                binding.response.setText(R.string.fetch_photo_selected_but_no_metadata)\n                return false\n            }\n        } else if (!TextUtils.isEmpty(customPhotoReference)) {\n            binding.response.setText(R.string.custom_photo_reference_but_not_fetch_photo)\n            return false\n        }\n        if (isFetchIconChecked && !placeFields.contains(Place.Field.ICON_MASK_URL)) {\n            binding.response.setText(R.string.fetch_icon_missing_fields_warning)\n            return false\n        }\n        return true\n    }\n\n    private val placeId: String\n        get() = binding.placeIdField.text.toString()\n\n    private val placeFields: List<Place.Field>\n        get() = if (findViewById<CheckBox>(R.id.use_custom_fields).isChecked) {\n            fieldSelector.selectedFields\n        } else {\n            fieldSelector.allFields\n        }\n\n    private val isDisplayRawResultsChecked: Boolean\n        get() = binding.displayRawResults.isChecked\n\n    private val isFetchPhotoChecked: Boolean\n        get() = binding.fetchPhotoCheckbox.isChecked\n\n    private val isFetchIconChecked: Boolean\n        get() = binding.fetchIconCheckbox.isChecked\n\n    private val customPhotoReference: String\n        get() = binding.customPhotoReference.text.toString()\n\n    private fun setPhotoSizingEnabled(enabled: Boolean) {\n        setEnabled(binding.photoMaxWidth, enabled)\n        setEnabled(binding.photoMaxHeight, enabled)\n    }\n\n    private fun setCustomPhotoReferenceEnabled(enabled: Boolean) {\n        setEnabled(binding.customPhotoReference, enabled)\n    }\n\n    private fun setEnabled(textView: TextView, enabled: Boolean) {\n        textView.isEnabled = enabled\n        textView.text = \"\"\n    }\n\n    private fun readIntFromTextView(textView: TextView): Int? {\n        var intValue: Int? = null\n        val contents = textView.text\n        if (!TextUtils.isEmpty(contents)) {\n            try {\n                intValue = contents.toString().toInt()\n            } catch (e: NumberFormatException) {\n                showErrorAlert(R.string.error_alert_message_invalid_photo_size)\n            }\n        }\n        return intValue\n    }\n\n    private fun showErrorAlert(@StringRes messageResId: Int) {\n        AlertDialog.Builder(this)\n            .setTitle(R.string.error_alert_title)\n            .setMessage(messageResId)\n            .show()\n    }\n\n    private fun setLoading(loading: Boolean) {\n        findViewById<View>(R.id.loading).visibility = if (loading) View.VISIBLE else View.INVISIBLE\n    }\n\n    private fun clearViews() {\n        binding.response.text = null\n        binding.photo.setImageBitmap(null)\n        binding.photoMetadata.text = null\n        binding.icon.setImageBitmap(null)\n    }\n\n    companion object {\n        private const val FETCHED_PHOTO_KEY = \"photo_image\"\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/PlaceIsOpenActivity.kt",
    "content": "/*\n * Copyright 2022 Google LLC\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 */\npackage com.example.placesdemo\n\nimport android.annotation.SuppressLint\nimport android.app.DatePickerDialog\nimport android.app.TimePickerDialog\nimport android.os.Bundle\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.AdapterView\nimport android.widget.ArrayAdapter\nimport android.widget.EditText\nimport com.example.placesdemo.StringUtil.stringify\nimport com.example.placesdemo.databinding.PlaceIsOpenActivityBinding\nimport com.google.android.gms.tasks.Task\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest\nimport com.google.android.libraries.places.api.net.IsOpenRequest\nimport com.google.android.libraries.places.api.net.IsOpenResponse\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport java.text.SimpleDateFormat\nimport java.util.Calendar\nimport java.util.Locale\nimport java.util.TimeZone.getAvailableIDs\nimport java.util.TimeZone.getDefault\nimport java.util.TimeZone.getTimeZone\n\n/**\n * Activity to demonstrate [PlacesClient.isOpen].\n */\nclass PlaceIsOpenActivity : BaseActivity() {\n    private val defaultTimeZone = getDefault()\n    private val defaultTimeZoneID: String = defaultTimeZone.id\n\n    private var isOpenCalendar: Calendar = Calendar.getInstance()\n\n    private lateinit var placesClient: PlacesClient\n    private lateinit var fieldSelector: FieldSelector\n    private lateinit var binding: PlaceIsOpenActivityBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = PlaceIsOpenActivityBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        setSupportActionBar(binding.topBar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        binding.topBar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n\n        // Retrieve a PlacesClient (previously initialized - see MainActivity)\n        placesClient = Places.createClient( /* context = */this)\n\n        fieldSelector = FieldSelector(\n            binding.checkBoxUseCustomFields,\n            binding.textViewCustomFieldsList,\n            savedInstanceState\n        )\n\n        binding.buttonFetchPlace.setOnClickListener { fetchPlace() }\n        binding.buttonIsOpen.setOnClickListener { isOpenByPlaceId() }\n\n        isOpenCalendar = Calendar.getInstance(defaultTimeZone)\n\n        // UI initialization\n        setLoading(false)\n        initializeSpinnerAndAddListener()\n        addIsOpenDateSelectionListener()\n        addIsOpenTimeSelectionListener()\n        updateIsOpenDate()\n        updateIsOpenTime()\n    }\n\n    override fun onSaveInstanceState(bundle: Bundle) {\n        super.onSaveInstanceState(bundle)\n        fieldSelector.onSaveInstanceState(bundle)\n    }\n\n    /**\n     * Get details about the Place ID listed in the input field, then check if the Place is open.\n     */\n    private fun fetchPlace() {\n        clearViews()\n        dismissKeyboard(binding.editTextPlaceId)\n        setLoading(true)\n\n        val placeFields = this.placeFields\n        val request = FetchPlaceRequest.newInstance(placeId, placeFields)\n        val placeTask = placesClient.fetchPlace(request)\n        placeTask.addOnSuccessListener { response ->\n            isOpenByPlaceObject(response.place)\n        }\n        placeTask.addOnFailureListener { exception ->\n            exception.printStackTrace()\n            binding.textViewResponse.text = exception.message\n        }\n        placeTask.addOnCompleteListener { setLoading(false) }\n    }\n\n    /**\n     * Check if the place is open at the time specified in the input fields.\n     * Requires a Place object that includes Place.Field.ID\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private fun isOpenByPlaceObject(place: Place) {\n        clearViews()\n        dismissKeyboard(binding.editTextPlaceId)\n        setLoading(true)\n\n        val request: IsOpenRequest = try {\n            IsOpenRequest.newInstance(place, isOpenCalendar.timeInMillis)\n        } catch (e: IllegalArgumentException) {\n            e.printStackTrace()\n            binding.textViewResponse.text = e.message\n            setLoading(false)\n            return\n        }\n        val placeTask: Task<IsOpenResponse> = placesClient.isOpen(request)\n        placeTask.addOnSuccessListener { response ->\n            binding.textViewResponse.text =\n                \"Is place open? ${response.isOpen}\\nExtra place details: \\n${stringify(place)}\"\n        }\n        placeTask.addOnFailureListener {\n                exception ->\n            exception.printStackTrace()\n            binding.textViewResponse.text = exception.message\n        }\n        placeTask.addOnCompleteListener { setLoading(false) }\n    }\n\n    /**\n     * Check if the place is open at the time specified in the input fields.\n     * Use the Place ID in the input field for the isOpenRequest.\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private fun isOpenByPlaceId() {\n        clearViews()\n        dismissKeyboard(binding.editTextPlaceId)\n        setLoading(true)\n\n        val request: IsOpenRequest = try {\n            IsOpenRequest.newInstance(placeId, isOpenCalendar.timeInMillis)\n        } catch (e: IllegalArgumentException) {\n            e.printStackTrace()\n            binding.textViewResponse.text = e.message\n            setLoading(false)\n            return\n        }\n        val placeTask: Task<IsOpenResponse> = placesClient.isOpen(request)\n        placeTask.addOnSuccessListener { response ->\n            binding.textViewResponse.text = \"Is place open? \" + response.isOpen\n        }\n        placeTask.addOnFailureListener { exception ->\n            exception.printStackTrace()\n            binding.textViewResponse.text = exception.message\n        }\n        placeTask.addOnCompleteListener { setLoading(false) }\n    }\n\n    //////////////////////////\n    // Helper methods below //\n    //////////////////////////\n    private fun dismissKeyboard(focusedEditText: EditText) {\n        val imm: InputMethodManager =\n            getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager\n        imm.hideSoftInputFromWindow(focusedEditText.windowToken, 0)\n    }\n\n    private val placeId: String\n        get() = binding.editTextPlaceId.text.toString()\n\n    /**\n     * Fetch the fields necessary for an isOpen request, unless user has checked the box to\n     * select a custom list of fields. Also fetches name and address for display text.\n     */\n    private val placeFields: List<Place.Field>\n        get() = if (isUseCustomFieldsChecked) {\n            fieldSelector.selectedFields\n        } else {\n            listOf(\n                Place.Field.FORMATTED_ADDRESS,\n                Place.Field.BUSINESS_STATUS,\n                Place.Field.CURRENT_OPENING_HOURS,\n                Place.Field.ID,\n                Place.Field.DISPLAY_NAME,\n                Place.Field.OPENING_HOURS,\n                Place.Field.UTC_OFFSET\n            )\n        }\n\n    private fun setLoading(loading: Boolean) {\n        if (loading) {\n            binding.progressBarLoading.visibility = View.VISIBLE\n        } else {\n            binding.progressBarLoading.visibility = View.INVISIBLE\n        }\n    }\n\n    private fun clearViews() {\n        binding.textViewResponse.text = null\n    }\n\n    private fun initializeSpinnerAndAddListener() {\n        val adapter: ArrayAdapter<String> =\n            ArrayAdapter(this, android.R.layout.simple_spinner_item, getAvailableIDs())\n        binding.spinnerTimeZones.adapter = adapter\n        binding.spinnerTimeZones.setSelection(adapter.getPosition(defaultTimeZoneID))\n        binding.spinnerTimeZones.onItemSelectedListener =\n            object : AdapterView.OnItemSelectedListener {\n                override fun onItemSelected(\n                    parent: AdapterView<*>?,\n                    view: View?,\n                    position: Int,\n                    id: Long\n                ) {\n                    val timeZone: String = parent!!.getItemAtPosition(position).toString()\n                    isOpenCalendar.timeZone = getTimeZone(timeZone)\n                    updateIsOpenDate()\n                    updateIsOpenTime()\n                }\n\n                override fun onNothingSelected(parent: AdapterView<*>?) {\n                }\n            }\n    }\n\n    private fun addIsOpenDateSelectionListener() {\n        val listener: DatePickerDialog.OnDateSetListener =\n            DatePickerDialog.OnDateSetListener { _, year, month, day ->\n                isOpenCalendar.set(Calendar.YEAR, year)\n                isOpenCalendar.set(Calendar.MONTH, month)\n                isOpenCalendar.set(Calendar.DAY_OF_MONTH, day)\n                updateIsOpenDate()\n            }\n        binding.editTextIsOpenDate.setOnClickListener {\n            DatePickerDialog(\n                this@PlaceIsOpenActivity,\n                listener,\n                isOpenCalendar.get(Calendar.YEAR),\n                isOpenCalendar.get(Calendar.MONTH),\n                isOpenCalendar.get(Calendar.DAY_OF_MONTH)\n            )\n                .show()\n        }\n    }\n\n    private fun updateIsOpenDate() {\n        val dateFormat = SimpleDateFormat(\"MM/dd/yy\", Locale.US)\n        binding.editTextIsOpenDate.setText(dateFormat.format(isOpenCalendar.timeInMillis))\n    }\n\n    private fun addIsOpenTimeSelectionListener() {\n        val listener: TimePickerDialog.OnTimeSetListener =\n            TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute ->\n                isOpenCalendar.set(Calendar.HOUR_OF_DAY, hourOfDay)\n                isOpenCalendar.set(Calendar.MINUTE, minute)\n                updateIsOpenTime()\n            }\n        binding.editTextIsOpenTime.setOnClickListener {\n            TimePickerDialog(\n                this@PlaceIsOpenActivity,\n                listener,\n                isOpenCalendar.get(Calendar.HOUR_OF_DAY),\n                isOpenCalendar.get(Calendar.MINUTE),\n                true\n            )\n                .show()\n        }\n    }\n\n    private fun updateIsOpenTime() {\n        val formattedHour: String =\n            String.format(Locale.getDefault(), \"%02d\", isOpenCalendar.get(Calendar.HOUR_OF_DAY))\n        val formattedMinutes: String =\n            String.format(Locale.getDefault(), \"%02d\", isOpenCalendar.get(Calendar.MINUTE))\n        binding.editTextIsOpenTime.setText(\n            String.format(Locale.getDefault(), \"%s:%s\", formattedHour, formattedMinutes)\n        )\n    }\n\n    private val isUseCustomFieldsChecked: Boolean\n        get() = binding.checkBoxUseCustomFields.isChecked\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/PlacesDemoApplication.kt",
    "content": "// Copyright 2024 Google LLC\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.example.placesdemo\n\nimport android.app.Application\nimport android.widget.Toast\nimport com.google.android.libraries.places.api.Places\n\nclass PlacesDemoApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n\n        val apiKey = BuildConfig.PLACES_API_KEY\n        if (apiKey.isEmpty()) {\n            Toast.makeText(this, getString(R.string.error_api_key), Toast.LENGTH_LONG).show()\n            return\n        }\n\n        Places.initializeWithNewPlacesApiEnabled(applicationContext, BuildConfig.PLACES_API_KEY)\n    }\n}\n"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/PlacesDemoGlideModule.kt",
    "content": "/*\n * Copyright 2025 Google LLC\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.example.placesdemo\n\nimport com.bumptech.glide.annotation.GlideModule\nimport com.bumptech.glide.module.AppGlideModule\n\n@GlideModule\nclass PlacesDemoGlideModule : AppGlideModule()"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/StringUtil.kt",
    "content": "/*\n * Copyright 2018 Google LLC\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 */\npackage com.example.placesdemo\n\nimport android.annotation.SuppressLint\nimport android.graphics.Typeface\nimport android.net.Uri\nimport android.text.TextUtils\nimport android.text.style.StyleSpan\nimport android.widget.TextView\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.gms.maps.model.LatLngBounds\nimport com.google.android.libraries.places.api.model.AutocompletePrediction\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse\nimport com.google.android.libraries.places.api.net.SearchNearbyResponse\n\n/**\n * Utility class for converting objects to viewable strings and back.\n */\nobject StringUtil {\n    private const val RESULT_SEPARATOR = \"\\n---\\n\\t\"\n\n    @SuppressLint(\"SetTextI18n\")\n    fun prepend(textView: TextView, prefix: String) {\n        textView.text = \"\"\"\n            $prefix\n\n            ${textView.text}\n            \"\"\".trimIndent()\n    }\n\n    fun convertToLatLngBounds(\n        southWest: String?, northEast: String?): LatLngBounds? {\n        val southWestLatLng = convertToLatLng(southWest)\n        val northEastLatLng = convertToLatLng(northEast)\n        return if (southWestLatLng == null || northEastLatLng == null) {\n            null\n        } else LatLngBounds(southWestLatLng, northEastLatLng)\n    }\n\n    fun convertToLatLng(value: String?): LatLng? {\n        if (TextUtils.isEmpty(value)) {\n            return null\n        }\n        val split = value!!.split(\",\").dropLastWhile { it.isEmpty() }.toTypedArray()\n        return if (split.size != 2) {\n            null\n        } else try {\n            LatLng(split[0].toDouble(), split[1].toDouble())\n        } catch (_: NullPointerException) {\n            null\n        } catch (_: NumberFormatException) {\n            null\n        }\n    }\n\n    fun countriesStringToArrayList(countriesString: String): List<String> {\n        // Allow these delimiters: , ; | / \\\n        return listOf(*countriesString\n            .replace(\"\\\\s\".toRegex(), \"|\")\n            .split(\"[,;|/\\\\\\\\]\").dropLastWhile { it.isEmpty() }.toTypedArray())\n    }\n\n    fun stringify(response: SearchNearbyResponse, raw: Boolean): String {\n        val builder = StringBuilder()\n        val places = response.places\n        builder\n            .append(places.size)\n            .append(\" Nearby Places Results:\")\n\n        if (raw) {\n            builder.append(RESULT_SEPARATOR)\n            appendListToStringBuilder(builder, places)\n        } else {\n            for (place in places) {\n                builder\n                    .append(RESULT_SEPARATOR)\n                    .append(place.displayName ?: \"Unnamed Place\")\n                    .append(\" (\")\n                    .append(place.id ?: \"no_id\")\n                    .append(\")\")\n            }\n        }\n\n        // Optionally include routing summaries if present\n        val routingSummaries = response.routingSummaries\n        if (!routingSummaries.isNullOrEmpty()) {\n            builder.append(RESULT_SEPARATOR)\n                .append(routingSummaries.size)\n                .append(\" Routing Summaries:\")\n            if (raw) {\n                builder.append(RESULT_SEPARATOR)\n                appendListToStringBuilder(builder, routingSummaries)\n            } else {\n                for (summary in routingSummaries) {\n                    builder.append(RESULT_SEPARATOR)\n                        .append(summary.toString())\n                }\n            }\n        }\n\n        return builder.toString()\n    }\n\n\n    fun stringify(response: FindAutocompletePredictionsResponse, raw: Boolean): String {\n        val builder = StringBuilder()\n        builder\n            .append(response.autocompletePredictions.size)\n            .append(\" Autocomplete Predictions Results:\")\n        if (raw) {\n            builder.append(RESULT_SEPARATOR)\n            appendListToStringBuilder(builder, response.autocompletePredictions)\n        } else {\n            for (autocompletePrediction in response.autocompletePredictions) {\n                builder\n                    .append(RESULT_SEPARATOR)\n                    .append(autocompletePrediction.getFullText( /* matchStyle */null))\n            }\n        }\n        return builder.toString()\n    }\n\n    fun stringify(response: FetchPlaceResponse, raw: Boolean): String {\n        val builder = StringBuilder()\n        builder.append(\"Fetch Place Result:\").append(RESULT_SEPARATOR)\n        if (raw) {\n            builder.append(response.place)\n        } else {\n            builder.append(stringify(response.place))\n        }\n        return builder.toString()\n    }\n\n    fun stringify(place: Place): String {\n        return \"${place.displayName?.plus(\" (\") ?: \"\"}${place.formattedAddress?.plus(\")\") ?: \"\"}\"\n    }\n\n    fun stringify(place: AutocompletePrediction?): String {\n        val primary = place?.getPrimaryText(StyleSpan(Typeface.BOLD)).toString()\n        val secondary = place?.getSecondaryText(StyleSpan(Typeface.NORMAL)).toString()\n        return \"$primary ($secondary)\"\n    }\n\n    fun stringify(uri: Uri): String {\n        return uri.toString()\n    }\n\n    fun stringifyAutocompleteWidget(place: Place, raw: Boolean): String {\n        val builder = StringBuilder()\n        builder.append(\"Autocomplete Widget Result:\").append(RESULT_SEPARATOR)\n        if (raw) {\n            builder.append(place)\n        } else {\n            builder.append(stringify(place))\n        }\n        return builder.toString()\n    }\n\n    fun stringifyAutocompletePrediction(place: AutocompletePrediction?, raw: Boolean): String {\n        val builder = StringBuilder()\n        builder.append(\"Autocomplete Prediction Result:\").append(RESULT_SEPARATOR)\n        if (raw) {\n            builder.append(place)\n        } else {\n            builder.append(stringify(place))\n        }\n        return builder.toString()\n    }\n\n    private fun <T> appendListToStringBuilder(builder: StringBuilder, items: List<T>) {\n        if (items.isEmpty()) {\n            return\n        }\n        builder.append(items[0])\n        for (i in 1 until items.size) {\n            builder.append(RESULT_SEPARATOR)\n            builder.append(items[i])\n        }\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/model/AddressType.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model\n\n/**\n * The Address types. Please see [Address Types and\n * Address Component Types](https://developers.google.com/maps/documentation/geocoding/intro#Types) for more detail. Some addresses contain additional place categories.\n * Please see [Place Types](https://developers.google.com/places/supported_types) for\n * more detail.\n */\nenum class AddressType(private val addressType: String) {\n    /** A precise street address.  */\n    STREET_ADDRESS(\"street_address\"),\n\n    /** A precise street number.  */\n    STREET_NUMBER(\"street_number\"),\n\n    /** The floor in the address of the building.  */\n    FLOOR(\"floor\"),\n\n    /** The room in the address of the building  */\n    ROOM(\"room\"),\n\n    /** A specific mailbox.  */\n    POST_BOX(\"post_box\"),\n\n    /** A named route (such as \"US 101\").  */\n    ROUTE(\"route\"),\n\n    /** A major intersection, usually of two major roads.  */\n    INTERSECTION(\"intersection\"),\n\n    /** A continent.  */\n    CONTINENT(\"continent\"),\n\n    /** A political entity. Usually, this type indicates a polygon of some civil administration.  */\n    POLITICAL(\"political\"),\n\n    /** The national political entity, typically the highest order type returned by the Geocoder.  */\n    COUNTRY(\"country\"),\n\n    /**\n     * A first-order civil entity below the country level. Within the United States, these\n     * administrative levels are states. Not all nations exhibit these administrative levels.\n     */\n    ADMINISTRATIVE_AREA_LEVEL_1(\"administrative_area_level_1\"),\n\n    /**\n     * A second-order civil entity below the country level. Within the United States, these\n     * administrative levels are counties. Not all nations exhibit these administrative levels.\n     */\n    ADMINISTRATIVE_AREA_LEVEL_2(\"administrative_area_level_2\"),\n\n    /**\n     * A third-order civil entity below the country level. This type indicates a minor civil division.\n     * Not all nations exhibit these administrative levels.\n     */\n    ADMINISTRATIVE_AREA_LEVEL_3(\"administrative_area_level_3\"),\n\n    /**\n     * A fourth-order civil entity below the country level. This type indicates a minor civil\n     * division. Not all nations exhibit these administrative levels.\n     */\n    ADMINISTRATIVE_AREA_LEVEL_4(\"administrative_area_level_4\"),\n\n    /**\n     * A fifth-order civil entity below the country level. This type indicates a minor civil division.\n     * Not all nations exhibit these administrative levels.\n     */\n    ADMINISTRATIVE_AREA_LEVEL_5(\"administrative_area_level_5\"),\n\n    /** A commonly-used alternative name for the entity.  */\n    COLLOQUIAL_AREA(\"colloquial_area\"),\n\n    /** An incorporated city or town political entity.  */\n    LOCALITY(\"locality\"),\n\n    /**\n     * A specific type of Japanese locality, used to facilitate distinction between multiple locality\n     * components within a Japanese address.\n     */\n    WARD(\"ward\"),\n\n    /**\n     * A first-order civil entity below a locality. Some locations may receive one of the additional\n     * types: `SUBLOCALITY_LEVEL_1` to `SUBLOCALITY_LEVEL_5`. Each sublocality level is a\n     * civil entity. Larger numbers indicate a smaller geographic area.\n     */\n    SUBLOCALITY(\"sublocality\"), SUBLOCALITY_LEVEL_1(\"sublocality_level_1\"), SUBLOCALITY_LEVEL_2(\"sublocality_level_2\"), SUBLOCALITY_LEVEL_3(\"sublocality_level_3\"), SUBLOCALITY_LEVEL_4(\"sublocality_level_4\"), SUBLOCALITY_LEVEL_5(\"sublocality_level_5\"),\n\n    /** A named neighborhood.  */\n    NEIGHBORHOOD(\"neighborhood\"),\n\n    /** A named location, usually a building or collection of buildings with a common name.  */\n    PREMISE(\"premise\"),\n\n    /**\n     * A first-order entity below a named location, usually a singular building within a collection of\n     * buildings with a common name.\n     */\n    SUBPREMISE(\"subpremise\"),\n\n    /** A postal code as used to address postal mail within the country.  */\n    POSTAL_CODE(\"postal_code\"),\n\n    /** A postal code prefix as used to address postal mail within the country.  */\n    POSTAL_CODE_PREFIX(\"postal_code_prefix\"),\n\n    /** A postal code prefix as used to address postal mail within the country.  */\n    POSTAL_CODE_SUFFIX(\"postal_code_suffix\"),\n\n    /** A prominent natural feature.  */\n    NATURAL_FEATURE(\"natural_feature\"),\n\n    /** An airport.  */\n    AIRPORT(\"airport\"),\n\n    /** A university.  */\n    UNIVERSITY(\"university\"),\n\n    /** A named park.  */\n    PARK(\"park\"),\n\n    /** A museum.  */\n    MUSEUM(\"museum\"),\n\n    /**\n     * A named point of interest. Typically, these \"POI\"s are prominent local entities that don't\n     * easily fit in another category, such as \"Empire State Building\" or \"Statue of Liberty.\"\n     */\n    POINT_OF_INTEREST(\"point_of_interest\"),\n\n    /** A place that has not yet been categorized.  */\n    ESTABLISHMENT(\"establishment\"),\n\n    /** The location of a bus stop.  */\n    BUS_STATION(\"bus_station\"),\n\n    /** The location of a train station.  */\n    TRAIN_STATION(\"train_station\"),\n\n    /** The location of a subway station.  */\n    SUBWAY_STATION(\"subway_station\"),\n\n    /** The location of a transit station.  */\n    TRANSIT_STATION(\"transit_station\"),\n\n    /** The location of a light rail station.  */\n    LIGHT_RAIL_STATION(\"light_rail_station\"),\n\n    /** The location of a church.  */\n    CHURCH(\"church\"),\n\n    /** The location of a primary school.  */\n    PRIMARY_SCHOOL(\"primary_school\"),\n\n    /** The location of a secondary school.  */\n    SECONDARY_SCHOOL(\"secondary_school\"),\n\n    /** The location of a finance institute.  */\n    FINANCE(\"finance\"),\n\n    /** The location of a post office.  */\n    POST_OFFICE(\"post_office\"),\n\n    /** The location of a place of worship.  */\n    PLACE_OF_WORSHIP(\"place_of_worship\"),\n\n    /**\n     * A grouping of geographic areas, such as locality and sublocality, used for mailing addresses in\n     * some countries.\n     */\n    POSTAL_TOWN(\"postal_town\"),\n\n    /** Currently not a documented return type.  */\n    SYNAGOGUE(\"synagogue\"),\n\n    /** Currently not a documented return type.  */\n    FOOD(\"food\"),\n\n    /** Currently not a documented return type.  */\n    GROCERY_OR_SUPERMARKET(\"grocery_or_supermarket\"),\n\n    /** Currently not a documented return type.  */\n    STORE(\"store\"),\n\n    /** The location of a drugstore.  */\n    DRUGSTORE(\"drugstore\"),\n\n    /** Currently not a documented return type.  */\n    LAWYER(\"lawyer\"),\n\n    /** Currently not a documented return type.  */\n    HEALTH(\"health\"),\n\n    /** Currently not a documented return type.  */\n    INSURANCE_AGENCY(\"insurance_agency\"),\n\n    /** Currently not a documented return type.  */\n    GAS_STATION(\"gas_station\"),\n\n    /** Currently not a documented return type.  */\n    CAR_DEALER(\"car_dealer\"),\n\n    /** Currently not a documented return type.  */\n    CAR_REPAIR(\"car_repair\"),\n\n    /** Currently not a documented return type.  */\n    MEAL_TAKEAWAY(\"meal_takeaway\"),\n\n    /** Currently not a documented return type.  */\n    FURNITURE_STORE(\"furniture_store\"),\n\n    /** Currently not a documented return type.  */\n    HOME_GOODS_STORE(\"home_goods_store\"),\n\n    /** Currently not a documented return type.  */\n    SHOPPING_MALL(\"shopping_mall\"),\n\n    /** Currently not a documented return type.  */\n    GYM(\"gym\"),\n\n    /** Currently not a documented return type.  */\n    ACCOUNTING(\"accounting\"),\n\n    /** Currently not a documented return type.  */\n    MOVING_COMPANY(\"moving_company\"),\n\n    /** Currently not a documented return type.  */\n    LODGING(\"lodging\"),\n\n    /** Currently not a documented return type.  */\n    STORAGE(\"storage\"),\n\n    /** Currently not a documented return type.  */\n    CASINO(\"casino\"),\n\n    /** Currently not a documented return type.  */\n    PARKING(\"parking\"),\n\n    /** Currently not a documented return type.  */\n    STADIUM(\"stadium\"),\n\n    /** Currently not a documented return type.  */\n    TRAVEL_AGENCY(\"travel_agency\"),\n\n    /** Currently not a documented return type.  */\n    NIGHT_CLUB(\"night_club\"),\n\n    /** Currently not a documented return type.  */\n    BEAUTY_SALON(\"beauty_salon\"),\n\n    /** Currently not a documented return type.  */\n    HAIR_CARE(\"hair_care\"),\n\n    /** Currently not a documented return type.  */\n    SPA(\"spa\"),\n\n    /** Currently not a documented return type.  */\n    SHOE_STORE(\"shoe_store\"),\n\n    /** Currently not a documented return type.  */\n    BAKERY(\"bakery\"),\n\n    /** Currently not a documented return type.  */\n    PHARMACY(\"pharmacy\"),\n\n    /** Currently not a documented return type.  */\n    SCHOOL(\"school\"),\n\n    /** Currently not a documented return type.  */\n    BOOK_STORE(\"book_store\"),\n\n    /** Currently not a documented return type.  */\n    DEPARTMENT_STORE(\"department_store\"),\n\n    /** Currently not a documented return type.  */\n    RESTAURANT(\"restaurant\"),\n\n    /** Currently not a documented return type.  */\n    REAL_ESTATE_AGENCY(\"real_estate_agency\"),\n\n    /** Currently not a documented return type.  */\n    BAR(\"bar\"),\n\n    /** Currently not a documented return type.  */\n    DOCTOR(\"doctor\"),\n\n    /** Currently not a documented return type.  */\n    HOSPITAL(\"hospital\"),\n\n    /** Currently not a documented return type.  */\n    FIRE_STATION(\"fire_station\"),\n\n    /** Currently not a documented return type.  */\n    SUPERMARKET(\"supermarket\"),\n\n    /** Currently not a documented return type.  */\n    CITY_HALL(\"city_hall\"),\n\n    /** Currently not a documented return type.  */\n    LOCAL_GOVERNMENT_OFFICE(\"local_government_office\"),\n\n    /** Currently not a documented return type.  */\n    ATM(\"atm\"),\n\n    /** Currently not a documented return type.  */\n    BANK(\"bank\"),\n\n    /** Currently not a documented return type.  */\n    LIBRARY(\"library\"),\n\n    /** Currently not a documented return type.  */\n    CAR_WASH(\"car_wash\"),\n\n    /** Currently not a documented return type.  */\n    HARDWARE_STORE(\"hardware_store\"),\n\n    /** Currently not a documented return type.  */\n    AMUSEMENT_PARK(\"amusement_park\"),\n\n    /** Currently not a documented return type.  */\n    AQUARIUM(\"aquarium\"),\n\n    /** Currently not a documented return type.  */\n    ART_GALLERY(\"art_gallery\"),\n\n    /** Currently not a documented return type.  */\n    BICYCLE_STORE(\"bicycle_store\"),\n\n    /** Currently not a documented return type.  */\n    BOWLING_ALLEY(\"bowling_alley\"),\n\n    /** Currently not a documented return type.  */\n    CAFE(\"cafe\"),\n\n    /** Currently not a documented return type.  */\n    CAMPGROUND(\"campground\"),\n\n    /** Currently not a documented return type.  */\n    CAR_RENTAL(\"car_rental\"),\n\n    /** Currently not a documented return type.  */\n    CEMETERY(\"cemetery\"),\n\n    /** Currently not a documented return type.  */\n    CLOTHING_STORE(\"clothing_store\"),\n\n    /** Currently not a documented return type.  */\n    CONVENIENCE_STORE(\"convenience_store\"),\n\n    /** Currently not a documented return type.  */\n    COURTHOUSE(\"courthouse\"),\n\n    /** Currently not a documented return type.  */\n    DENTIST(\"dentist\"),\n\n    /** Currently not a documented return type.  */\n    ELECTRICIAN(\"electrician\"),\n\n    /** Currently not a documented return type.  */\n    ELECTRONICS_STORE(\"electronics_store\"),\n\n    /** Currently not a documented return type.  */\n    EMBASSY(\"embassy\"),\n\n    /** Currently not a documented return type.  */\n    FLORIST(\"florist\"),\n\n    /** Currently not a documented return type.  */\n    FUNERAL_HOME(\"funeral_home\"),\n\n    /** Currently not a documented return type.  */\n    GENERAL_CONTRACTOR(\"general_contractor\"),\n\n    /** Currently not a documented return type.  */\n    HINDU_TEMPLE(\"hindu_temple\"),\n\n    /** Currently not a documented return type.  */\n    JEWELRY_STORE(\"jewelry_store\"),\n\n    /** Currently not a documented return type.  */\n    LAUNDRY(\"laundry\"),\n\n    /** Currently not a documented return type.  */\n    LIQUOR_STORE(\"liquor_store\"),\n\n    /** Currently not a documented return type.  */\n    LOCKSMITH(\"locksmith\"),\n\n    /** Currently not a documented return type.  */\n    MEAL_DELIVERY(\"meal_delivery\"),\n\n    /** Currently not a documented return type.  */\n    MOSQUE(\"mosque\"),\n\n    /** Currently not a documented return type.  */\n    MOVIE_RENTAL(\"movie_rental\"),\n\n    /** Currently not a documented return type.  */\n    MOVIE_THEATER(\"movie_theater\"),\n\n    /** Currently not a documented return type.  */\n    PAINTER(\"painter\"),\n\n    /** Currently not a documented return type.  */\n    PET_STORE(\"pet_store\"),\n\n    /** Currently not a documented return type.  */\n    PHYSIOTHERAPIST(\"physiotherapist\"),\n\n    /** Currently not a documented return type.  */\n    PLUMBER(\"plumber\"),\n\n    /** Currently not a documented return type.  */\n    POLICE(\"police\"),\n\n    /** Currently not a documented return type.  */\n    ROOFING_CONTRACTOR(\"roofing_contractor\"),\n\n    /** Currently not a documented return type.  */\n    RV_PARK(\"rv_park\"),\n\n    /** Currently not a documented return type.  */\n    TAXI_STAND(\"taxi_stand\"),\n\n    /** Currently not a documented return type.  */\n    VETERINARY_CARE(\"veterinary_care\"),\n\n    /** Currently not a documented return type.  */\n    ZOO(\"zoo\"),\n\n    /** An archipelago.  */\n    ARCHIPELAGO(\"archipelago\"),\n\n    /** A tourist attraction  */\n    TOURIST_ATTRACTION(\"tourist_attraction\"),\n\n    /** Currently not a documented return type.  */\n    TOWN_SQUARE(\"town_square\"),\n\n    /**\n     * Indicates an unknown address type returned by the server. The Java Client for Google Maps\n     * Services should be updated to support the new value.\n     */\n    UNKNOWN(\"unknown\");\n\n    override fun toString(): String {\n        return addressType\n    }\n\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/model/AutocompleteEditText.kt",
    "content": "// Copyright 2024 Google LLC\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.example.placesdemo.model\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport androidx.appcompat.widget.AppCompatEditText\n\nclass AutocompleteEditText : AppCompatEditText {\n    constructor(context: Context?) : super(context!!)\n    constructor(context: Context?, attrs: AttributeSet?) : super(\n        context!!, attrs\n    )\n\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        super.onTouchEvent(event)\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> return true\n            MotionEvent.ACTION_UP -> {\n                performClick()\n                return true\n            }\n        }\n        return false\n    }\n\n    // Because we call this from onTouchEvent, this code will be executed for both\n    // normal touch events and for when the system calls this using Accessibility\n    override fun performClick(): Boolean {\n        super.performClick()\n        return true\n    }\n}\n"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/model/Bounds.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model\n\nimport com.google.android.gms.maps.model.LatLng\nimport java.io.Serializable\n\n/** The northeast and southwest points that delineate the outer bounds of a map.  */\nclass Bounds : Serializable {\n    /** The northeast corner of the bounding box.  */\n    var northeast: LatLng? = null\n\n    /** The southwest corner of the bounding box.  */\n    var southwest: LatLng? = null\n    override fun toString(): String {\n        return String.format(\"[%s, %s]\", northeast, southwest)\n    }\n\n    companion object {\n        private const val serialVersionUID = 1L\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/model/GeocodingResult.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model\n\nimport java.io.Serializable\n\nclass GeocodingResult : Serializable {\n    /**\n     * The human-readable address of this location.\n     *\n     *\n     * Often this address is equivalent to the \"postal address,\" which sometimes differs from\n     * country to country. (Note that some countries, such as the United Kingdom, do not allow\n     * distribution of true postal addresses due to licensing restrictions.) This address is generally\n     * composed of one or more address components. For example, the address \"111 8th Avenue, New York,\n     * NY\" contains separate address components for \"111\" (the street number, \"8th Avenue\" (the\n     * route), \"New York\" (the city) and \"NY\" (the US state). These address components contain\n     * additional information.\n     */\n    var formattedAddress: String? = null\n\n    /**\n     * All the localities contained in a postal code. This is only present when the result is a postal\n     * code that contains multiple localities.\n     */\n    var postcodeLocalities: Array<String>? = null\n\n    /** Location information for this result.  */\n    var geometry: Geometry? = null\n\n    /**\n     * The types of the returned result. This array contains a set of zero or more tags identifying\n     * the type of feature returned in the result. For example, a geocode of \"Chicago\" returns\n     * \"locality\" which indicates that \"Chicago\" is a city, and also returns \"political\" which\n     * indicates it is a political entity.\n     */\n    var types: Array<AddressType>? = null\n\n    /**\n     * Indicates that the geocoder did not return an exact match for the original request, though it\n     * was able to match part of the requested address. You may wish to examine the original request\n     * for misspellings and/or an incomplete address.\n     *\n     *\n     * Partial matches most often occur for street addresses that do not exist within the locality\n     * you pass in the request. Partial matches may also be returned when a request matches two or\n     * more locations in the same locality. For example, \"21 Henr St, Bristol, UK\" will return a\n     * partial match for both Henry Street and Henrietta Street. Note that if a request includes a\n     * misspelled address component, the geocoding service may suggest an alternate address.\n     * Suggestions triggered in this way will not be marked as a partial match.\n     */\n    var partialMatch = false\n\n    /** A unique identifier for this place.  */\n    var placeId: String? = null\n\n    /** The Plus Code identifier for this place.  */\n    var plusCode: PlusCode? = null\n    override fun toString(): String {\n        val sb = StringBuilder(\"[GeocodingResult\")\n        if (partialMatch) {\n            sb.append(\" PARTIAL MATCH\")\n        }\n        sb.append(\" placeId=\").append(placeId)\n        sb.append(\" \").append(geometry)\n        sb.append(\", formattedAddress=\").append(formattedAddress)\n        sb.append(\", types=\").append(types.contentToString())\n        if (postcodeLocalities != null && postcodeLocalities!!.isNotEmpty()) {\n            sb.append(\", postcodeLocalities=\").append(postcodeLocalities.contentToString())\n        }\n        sb.append(\"]\")\n        return sb.toString()\n    }\n\n    companion object {\n        private const val serialVersionUID = 1L\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/model/Geometry.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model\n\nimport com.google.android.gms.maps.model.LatLng\nimport java.io.Serializable\n\n/** The geometry of a Geocoding result.  */\nclass Geometry : Serializable {\n    /**\n     * The bounding box which can fully contain the returned result (optionally returned). Note that\n     * these bounds may not match the recommended viewport. (For example, San Francisco includes the\n     * Farallon islands, which are technically part of the city, but probably should not be returned\n     * in the viewport.)\n     */\n    var bounds: Bounds? = null\n\n    /**\n     * The geocoded latitude/longitude value. For normal address lookups, this field is typically the\n     * most important.\n     */\n    var location: LatLng? = null\n\n    /** The level of certainty of this geocoding result.  */\n    var locationType: LocationType? = null\n\n    /**\n     * The recommended viewport for displaying the returned result. Generally the viewport is used to\n     * frame a result when displaying it to a user.\n     */\n    var viewport: Bounds? = null\n\n    override fun toString(): String {\n        return String.format(\n            \"[Geometry: %s (%s) bounds=%s, viewport=%s]\", location, locationType, bounds, viewport)\n    }\n\n    companion object {\n        private const val serialVersionUID = 1L\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/model/LocationType.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model\n\n/**\n * Location types for a reverse geocoding request. Please see [Reverse\n * Geocoding](https://developers.google.com/maps/documentation/geocoding/start#reverse) for more detail.\n */\nenum class LocationType {\n    /**\n     * Restricts the results to addresses for which we have location information accurate down to\n     * street address precision.\n     */\n    ROOFTOP,\n\n    /**\n     * Restricts the results to those that reflect an approximation (usually on a road) interpolated\n     * between two precise points (such as intersections). An interpolated range generally indicates\n     * that rooftop geocodes are unavailable for a street address.\n     */\n    RANGE_INTERPOLATED,\n\n    /**\n     * Restricts the results to geometric centers of a location such as a polyline (for example, a\n     * street) or polygon (region).\n     */\n    GEOMETRIC_CENTER,\n\n    /** Restricts the results to those that are characterized as approximate.  */\n    APPROXIMATE,\n\n    /**\n     * Indicates an unknown location type returned by the server. The Java Client for Google Maps\n     * Services should be updated to support the new value.\n     */\n    UNKNOWN;\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/model/PlusCode.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.model\n\nimport java.io.Serializable\n\n/** A Plus Code encoded location reference.  */\nclass PlusCode : Serializable {\n    /** The global Plus Code identifier.  */\n    var globalCode: String? = null\n\n    /** The compound Plus Code identifier. May be null for locations in remote areas.  */\n    var compoundCode: String? = null\n\n    override fun toString(): String {\n        val sb = StringBuilder(\"[PlusCode: \")\n        sb.append(globalCode)\n        if (compoundCode != null) {\n            sb.append(\", compoundCode=\").append(compoundCode)\n        }\n        sb.append(\"]\")\n        return sb.toString()\n    }\n\n    companion object {\n        private const val serialVersionUID = 1L\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/programmatic_autocomplete/LatLngAdapter.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.programmatic_autocomplete\n\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.gson.TypeAdapter\nimport com.google.gson.stream.JsonReader\nimport com.google.gson.stream.JsonToken\nimport com.google.gson.stream.JsonWriter\nimport java.io.IOException\n\n/** Handle conversion from varying types of latitude and longitude representations.  */\nclass LatLngAdapter : TypeAdapter<LatLng?>() {\n    /**\n     * Reads in a JSON object and try to create a LatLng in one of the following formats.\n     *\n     * <pre>{\n     * \"lat\" : -33.8353684,\n     * \"lng\" : 140.8527069\n     * }\n     *\n     * {\n     * \"latitude\": -33.865257570508334,\n     * \"longitude\": 151.19287000481452\n     * }</pre>\n     */\n    @Throws(IOException::class)\n    override fun read(reader: JsonReader): LatLng? {\n        if (reader.peek() == JsonToken.NULL) {\n            reader.nextNull()\n            return null\n        }\n        var lat = 0.0\n        var lng = 0.0\n        var hasLat = false\n        var hasLng = false\n        reader.beginObject()\n        while (reader.hasNext()) {\n            val name = reader.nextName()\n            if (\"lat\" == name || \"latitude\" == name) {\n                lat = reader.nextDouble()\n                hasLat = true\n            } else if (\"lng\" == name || \"longitude\" == name) {\n                lng = reader.nextDouble()\n                hasLng = true\n            }\n        }\n        reader.endObject()\n        return if (hasLat && hasLng) {\n            LatLng(lat, lng)\n        } else {\n            null\n        }\n    }\n\n    /** Not supported.  */\n    @Throws(IOException::class)\n    override fun write(out: JsonWriter, value: LatLng?) {\n        throw UnsupportedOperationException(\"Unimplemented method.\")\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/programmatic_autocomplete/PlacePredictionAdapter.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.programmatic_autocomplete\n\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ViewHolder\nimport com.example.placesdemo.R\nimport com.example.placesdemo.programmatic_autocomplete.PlacePredictionAdapter.PlacePredictionViewHolder\nimport com.google.android.libraries.places.api.model.AutocompletePrediction\nimport java.util.*\n\n/**\n * A [RecyclerView.Adapter] for a [com.google.android.libraries.places.api.model.AutocompletePrediction].\n */\nclass PlacePredictionAdapter : RecyclerView.Adapter<PlacePredictionViewHolder>() {\n    private val predictions: MutableList<AutocompletePrediction> = ArrayList()\n    var onPlaceClickListener: ((AutocompletePrediction) -> Unit)? = null\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlacePredictionViewHolder {\n        val inflater = LayoutInflater.from(parent.context)\n        return PlacePredictionViewHolder(\n            inflater.inflate(R.layout.place_prediction_item, parent, false))\n    }\n\n    override fun onBindViewHolder(holder: PlacePredictionViewHolder, position: Int) {\n        val place = predictions[position]\n        holder.setPrediction(place)\n        holder.itemView.setOnClickListener {\n            onPlaceClickListener?.invoke(place)\n        }\n    }\n\n    override fun getItemCount(): Int {\n        return predictions.size\n    }\n\n    fun setPredictions(predictions: List<AutocompletePrediction>?) {\n        if (predictions != null) {\n            this.predictions.clear()\n            this.predictions.addAll(predictions)\n            notifyDataSetChanged()\n        }\n    }\n\n    class PlacePredictionViewHolder(itemView: View) : ViewHolder(itemView) {\n        private val title: TextView = itemView.findViewById(R.id.text_view_title)\n        private val address: TextView = itemView.findViewById(R.id.text_view_address)\n\n        fun setPrediction(prediction: AutocompletePrediction) {\n            title.text = prediction.getPrimaryText(null)\n            address.text = prediction.getSecondaryText(null)\n        }\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/java/com/example/placesdemo/programmatic_autocomplete/ProgrammaticAutocompleteGeocodingActivity.kt",
    "content": "// Copyright 2020 Google LLC\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.example.placesdemo.programmatic_autocomplete\n\nimport com.google.android.material.search.SearchView\nimport android.content.Context\nimport android.os.Bundle\nimport android.os.Handler\nimport android.os.Looper\nimport android.text.Editable\nimport android.text.TextWatcher\nimport android.util.Log\nimport android.util.TypedValue\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorInt\nimport androidx.appcompat.app.AlertDialog\nimport androidx.recyclerview.widget.DividerItemDecoration\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.android.volley.Request\nimport com.android.volley.RequestQueue\nimport com.android.volley.toolbox.JsonObjectRequest\nimport com.android.volley.toolbox.Volley\nimport com.example.placesdemo.BaseActivity\nimport com.example.placesdemo.BuildConfig\nimport com.example.placesdemo.R\nimport com.example.placesdemo.databinding.ActivityProgrammaticAutocompleteBinding\nimport com.example.placesdemo.model.GeocodingResult\nimport com.google.android.gms.common.api.ApiException\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.AutocompletePrediction\nimport com.google.android.libraries.places.api.model.AutocompleteSessionToken\nimport com.google.android.libraries.places.api.model.LocationBias\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.model.PlaceTypes\nimport com.google.android.libraries.places.api.model.RectangularBounds\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.gson.GsonBuilder\nimport org.json.JSONArray\nimport org.json.JSONException\n\n/**\n * Extension function to get a color from the current theme using its attribute resource ID.\n */\n@ColorInt\nfun Context.getColorFromTheme(@AttrRes colorAttributeResId: Int): Int {\n    val typedValue = TypedValue()\n    theme.resolveAttribute(colorAttributeResId, typedValue, true)\n    return typedValue.data\n}\n\n/**\n * An Activity that demonstrates programmatic as-you-type place predictions. The parameters of the\n * request are currently hard coded in this Activity, to modify these parameters (e.g. location\n * bias, place types, etc.), see [ProgrammaticAutocompleteGeocodingActivity.getPlacePredictions]\n *\n * @see https://developers.google.com/places/android-sdk/autocomplete#get_place_predictions_programmatically\n */\nclass ProgrammaticAutocompleteGeocodingActivity : BaseActivity() {\n\n    private val handler = Handler(Looper.getMainLooper())\n    private val adapter = PlacePredictionAdapter()\n    private val gson =\n        GsonBuilder().registerTypeAdapter(LatLng::class.java, LatLngAdapter()).create()\n\n    private lateinit var queue: RequestQueue\n    private lateinit var placesClient: PlacesClient\n    private var sessionToken: AutocompleteSessionToken? = null\n    private lateinit var binding: ActivityProgrammaticAutocompleteBinding\n\n    private var colorOnPrimary: Int = 0\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityProgrammaticAutocompleteBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        // In your Fragment's onViewCreated or Activity's onCreate\n        val searchBar = binding.searchBar // view.findViewById<SearchBar>(R.id.search_bar)\n        val searchView = binding.searchView // view.findViewById<SearchView>(R.id.search_view)\n\n        // This is the critical line that makes it work!\n        searchView.setupWithSearchBar(searchBar)\n\n        // Now you can initialize your SearchView listeners\n        initSearchView(searchView)\n\n        // Initialize members\n        placesClient = Places.createClient(this)\n        queue = Volley.newRequestQueue(this)\n        initRecyclerView()\n\n        colorOnPrimary = this.getColorFromTheme(com.google.android.material.R.attr.colorOnPrimary)\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu, menu)\n        val searchView =\n            menu.findItem(R.id.search).actionView as SearchView\n        initSearchView(searchView)\n        return super.onCreateOptionsMenu(menu)\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == R.id.search) {\n            sessionToken = AutocompleteSessionToken.newInstance()\n            return false\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun initSearchView(searchView: SearchView) {\n        // Add a TextWatcher to the underlying EditText to listen for changes\n        searchView.editText.addTextChangedListener(object : TextWatcher {\n            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {\n                // No action needed here\n            }\n\n            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {\n                val query = s.toString()\n\n                // Cancel any previous place prediction requests\n                handler.removeCallbacksAndMessages(null)\n\n\n                // Start a new place prediction request after a 300ms delay\n                handler.postDelayed({\n                        if (query.isNotEmpty()) binding.progressBar.visibility = View.VISIBLE\n                        getPlacePredictions(query)\n                    },\n                    300\n                )\n            }\n\n            override fun afterTextChanged(s: Editable?) {\n                // No action needed here\n            }\n        })\n    }\n\n    private fun initRecyclerView() {\n        val recyclerView = binding.placeSearchResultsView\n        val layoutManager = LinearLayoutManager(this)\n        recyclerView.layoutManager = layoutManager\n        recyclerView.adapter = adapter\n        recyclerView.addItemDecoration(DividerItemDecoration(this, layoutManager.orientation))\n        // Get just the location of the place using the Geocoding API\n        adapter.onPlaceClickListener = { geocodePlaceAndDisplay(it) }\n        // Alternative: Get more details about the place using Place Details\n        // See https://goo.gle/paaln for help choosing between Geocoding and Place Details\n        // adapter.onPlaceClickListener = { fetchPlaceAndDisplay(it) }\n    }\n\n    /**\n     * This method demonstrates the programmatic approach to getting place predictions. The\n     * parameters in this request are currently biased to Boulder, Colorado, USA.\n     *\n     * @param query the plus code query string (e.g. \"GCG2+3M K\")\n     */\n    private fun getPlacePredictions(query: String) {\n        // The value of 'bias' biases prediction results to the rectangular region provided\n        // (currently Boulder). Modify these values to get results for another area. Make sure to\n        // pass in the appropriate value/s for .setCountries() in the\n        // FindAutocompletePredictionsRequest.Builder object as well.\n        val bias: LocationBias = RectangularBounds.newInstance(\n            LatLng(39.9614, -105.3017),  // SW lat, lng (South Boulder)\n            LatLng(40.0953, -105.1843)   // NE lat, lng (Northeast Boulder)\n        )\n\n        // Create a new programmatic Place Autocomplete request in Places SDK for Android\n        val newRequest = FindAutocompletePredictionsRequest.builder()\n            .setLocationBias(bias)\n            .setCountries(\"US\")\n            .setTypesFilter(listOf(PlaceTypes.ESTABLISHMENT))\n            // Session Token only used to link related Place Details call. See https://goo.gle/paaln\n            .setSessionToken(sessionToken)\n            .setQuery(query)\n            .build()\n\n        // Perform autocomplete predictions request\n        placesClient.findAutocompletePredictions(newRequest)\n            .addOnSuccessListener { response ->\n                val predictions = response.autocompletePredictions\n                adapter.setPredictions(predictions)\n                binding.progressBar.visibility = View.INVISIBLE\n                binding.resultsViewAnimator.displayedChild =\n                    if (predictions.isEmpty() && query.isNotEmpty()) 1 else 0\n            }.addOnFailureListener { exception: Exception? ->\n                binding.progressBar.visibility = View.INVISIBLE\n                if (exception is ApiException) {\n                    Log.e(TAG, \"Place not found: ${exception.message}\")\n                }\n            }\n    }\n\n    /**\n     * Performs a Geocoding API request and displays the result in a dialog.\n     * Be sure to enable Geocoding API in your project and API key restrictions.\n     *\n     * @see https://developers.google.com/places/android-sdk/autocomplete#get_place_predictions_programmatically\n     */\n    private fun geocodePlaceAndDisplay(placePrediction: AutocompletePrediction) {\n        // Construct the request URL\n        val apiKey = BuildConfig.PLACES_API_KEY\n        val requestURL =\n            \"https://maps.googleapis.com/maps/api/geocode/json?place_id=${placePrediction.placeId}&key=$apiKey\"\n\n        // Use the HTTP request URL for Geocoding API to get geographic coordinates for the place\n        val request = JsonObjectRequest(Request.Method.GET, requestURL, null, { response ->\n            try {\n                val status: String = response.getString(\"status\")\n                if (status != \"OK\") {\n                    Log.e(TAG, \"$status \" + response.getString(\"error_message\"))\n                }\n\n                // Inspect the value of \"results\" and make sure it's not empty\n                val results: JSONArray = response.getJSONArray(\"results\")\n                if (results.length() == 0) {\n                    Log.w(TAG, \"No results from geocoding request.\")\n                    return@JsonObjectRequest\n                }\n\n                // Use Gson to convert the response JSON object to a POJO\n                val result: GeocodingResult =\n                    gson.fromJson(results.getString(0), GeocodingResult::class.java)\n                displayDialog(placePrediction, result)\n            } catch (e: JSONException) {\n                e.printStackTrace()\n            }\n        }, { error ->\n            Log.e(TAG, \"Request failed\", error)\n        })\n\n        // Add the request to the Request queue.\n        queue.add(request)\n    }\n\n    private fun displayDialog(place: AutocompletePrediction, result: GeocodingResult) {\n        AlertDialog.Builder(this)\n            .setTitle(place.getPrimaryText(null))\n            .setMessage(\"Geocoding result:\\n\" + result.geometry?.location)\n            .setPositiveButton(android.R.string.ok, null)\n            .show()\n    }\n\n    /**\n     * Performs a Place Details request and displays the result in a dialog.\n     *\n     * @see https://developers.google.com/maps/documentation/places/android-sdk/place-details#maps_places_get_place_by_id-kotlin\n     */\n    private fun fetchPlaceAndDisplay(placePrediction: AutocompletePrediction) {\n        // Specify the fields to return.\n        val placeFields = listOf(Place.Field.ID, Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS)\n\n        // Construct a request object, passing the place ID and fields array.\n        val request = FetchPlaceRequest.newInstance(placePrediction.placeId, placeFields)\n\n        placesClient.fetchPlace(request)\n            .addOnSuccessListener { response: FetchPlaceResponse ->\n                val place = response.place\n                AlertDialog.Builder(this)\n                    .setTitle(place.displayName)\n                    .setMessage(\"located at:\\n\" + place.formattedAddress)\n                    .setPositiveButton(android.R.string.ok, null)\n                    .show()\n                Log.i(TAG, \"Place found: ${place.displayName}\")\n            }.addOnFailureListener { exception: Exception ->\n                if (exception is ApiException) {\n                    Log.e(TAG, \"Place not found: ${exception.message} ${exception.statusCode}\")\n                }\n            }\n    }\n\n    companion object {\n        private val TAG = ProgrammaticAutocompleteGeocodingActivity::class.java.simpleName\n    }\n}"
  },
  {
    "path": "demo-kotlin/src/main/res/drawable/ic_exit.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorOnPrimary\">\n    <path\n        android:fillColor=\"@android:color/white\"\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</vector>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/drawable/ic_exit_to_app_black_24dp.xml",
    "content": "<!--\n Copyright 2025 Google LLC\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<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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z\"/>\n</vector>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/drawable/ic_search_black_24dp.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<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        android:tint=\"?attr/colorOnPrimary\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z\"/>\n</vector>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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=\".MainActivity\">\n\n    <com.google.android.material.appbar.MaterialToolbar\n        android:id=\"@+id/top_bar\"\n        style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"?attr/actionBarSize\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/app_name\"\n        app:titleTextColor=\"?attr/colorOnPrimary\" />\n\n    <ScrollView\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/top_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"@dimen/spacing_large\">\n\n            <Button\n                android:id=\"@+id/autocomplete_button\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"@dimen/spacing_small\"\n                android:text=\"@string/autocomplete_button\" />\n\n            <Button\n                android:id=\"@+id/autocomplete_address_button\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"@dimen/spacing_small\"\n                android:text=\"@string/autocomplete_address_button\" />\n\n            <Button\n                android:id=\"@+id/programmatic_autocomplete_button\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"@dimen/spacing_small\"\n                android:text=\"@string/programmatic_autocomplete_geocoding\" />\n\n            <Button\n                android:id=\"@+id/current_place_button\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"@dimen/spacing_small\"\n                android:text=\"@string/current_place_button\" />\n\n            <Button\n                android:id=\"@+id/place_and_photo_button\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"@dimen/spacing_small\"\n                android:text=\"@string/place_and_photo_button\" />\n\n            <Button\n                android:id=\"@+id/is_open_button\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"@dimen/spacing_small\"\n                android:text=\"@string/main_isOpenButtonText\" />\n\n        </LinearLayout>\n    </ScrollView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/activity_programmatic_autocomplete.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<androidx.coordinatorlayout.widget.CoordinatorLayout\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    tools:context=\".programmatic_autocomplete.ProgrammaticAutocompleteGeocodingActivity\"\n    >\n\n  <com.google.android.material.appbar.AppBarLayout\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\">\n    <com.google.android.material.search.SearchBar\n        android:id=\"@+id/search_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/search_a_place\" />\n  </com.google.android.material.appbar.AppBarLayout>\n\n  <androidx.core.widget.NestedScrollView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_margin=\"16dp\"\n        android:gravity=\"center\"\n        android:lineSpacingMultiplier=\"1.15\"\n        android:text=\"@string/programmatic_place_predictions_instructions\"\n        android:textSize=\"20sp\" />\n\n  </androidx.core.widget.NestedScrollView>\n\n  <com.google.android.material.search.SearchView\n      android:id=\"@+id/search_view\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:hint=\"@string/search_a_place\"\n      app:layout_anchor=\"@id/search_bar\">\n\n    <ProgressBar\n        android:id=\"@+id/progress_bar\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:indeterminate=\"true\"\n        android:paddingHorizontal=\"0dp\"\n        android:paddingVertical=\"0dp\"\n        android:layout_marginBottom=\"-8dp\"\n        android:layout_marginTop=\"-6dp\"\n        android:visibility=\"invisible\"\n        tools:visibility=\"invisible\"\n        />\n\n    <ViewAnimator\n        android:id=\"@+id/results_view_animator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n      <androidx.recyclerview.widget.RecyclerView\n          android:id=\"@+id/place_search_results_view\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"match_parent\" />\n\n      <TextView\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"match_parent\"\n          android:layout_margin=\"16dp\"\n          android:gravity=\"center\"\n          android:lineSpacingMultiplier=\"1.15\"\n          android:text=\"@string/programmatic_place_predictions_no_matches\"\n          android:textSize=\"20sp\"\n          android:visibility=\"gone\" />\n\n    </ViewAnimator>\n\n  </com.google.android.material.search.SearchView>\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/autocomplete_address_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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=\".AutocompleteAddressActivity\">\n\n  <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/top_bar\"\n      style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"?attr/actionBarSize\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:title=\"@string/autocomplete_address_button\"\n      app:titleTextColor=\"?attr/colorOnPrimary\" />\n\n  <ScrollView\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintTop_toBottomOf=\"@+id/top_bar\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\">\n\n    <LinearLayout\n        android:id=\"@+id/autocomplete_scroll_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"@dimen/spacing_large\"\n        android:orientation=\"vertical\">\n\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_address1_label\" />\n\n      <com.example.placesdemo.model.AutocompleteEditText\n          android:id=\"@+id/autocomplete_address1\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_address1_label\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\" />\n\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_address2_label\" />\n\n      <EditText\n          android:id=\"@+id/autocomplete_address2\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_address2_label\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\" />\n\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_city_label\" />\n\n      <EditText\n          android:id=\"@+id/autocomplete_city\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_city_label\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:baselineAligned=\"false\"\n          android:orientation=\"horizontal\">\n\n        <LinearLayout\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:orientation=\"vertical\">\n\n          <TextView\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:text=\"@string/autocomplete_state_label\" />\n\n          <EditText\n              android:id=\"@+id/autocomplete_state\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:autofillHints=\"\"\n              android:hint=\"@string/autocomplete_state_label\"\n              android:imeOptions=\"actionNext\"\n              android:inputType=\"textCapCharacters\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:orientation=\"vertical\">\n\n          <TextView\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:text=\"@string/autocomplete_postal_label\" />\n\n          <EditText\n              android:id=\"@+id/autocomplete_postal\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"0dp\"\n              android:layout_weight=\"1\"\n              android:autofillHints=\"\"\n              android:hint=\"@string/autocomplete_postal_label\"\n              android:imeOptions=\"actionNext\"\n              android:inputType=\"number\" />\n        </LinearLayout>\n      </LinearLayout>\n\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_country_label\" />\n\n      <EditText\n          android:id=\"@+id/autocomplete_country\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_country_label\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\" />\n\n      <CheckBox\n          android:id=\"@+id/checkbox_proximity\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_proximity_check\" />\n\n      <ViewStub\n          android:id=\"@+id/stub_map\"\n          android:inflatedId=\"@+id/panel_map\"\n          android:layout=\"@layout/autocomplete_address_map\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"200dp\"\n          android:layout_gravity=\"bottom\" />\n\n      <Button\n          android:id=\"@+id/autocomplete_save_button\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_save_button\" />\n\n      <Button\n          android:id=\"@+id/autocomplete_reset_button\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:background=\"@android:color/transparent\"\n          android:text=\"@string/autocomplete_reset_button\" />\n    </LinearLayout>\n  </ScrollView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/autocomplete_address_map.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2021 Google LLC\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<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/map_panel_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/autocomplete_map_label\"/>\n\n    <!-- A map will be added here programmatically\n    for visual confirmation of the selected address -->\n    <!-- [START maps_solutions_android_autocomplete_map_fragment] -->\n    <fragment\n        android:name=\"com.google.android.gms.maps.SupportMapFragment\"\n        android:id=\"@+id/confirmation_map\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n    <!-- [END maps_solutions_android_autocomplete_map_fragment] -->\n\n</LinearLayout>"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/current_place_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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=\".CurrentPlaceActivity\">\n\n  <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/top_bar\"\n      style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"?attr/actionBarSize\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:title=\"@string/current_place_button\"\n      app:titleTextColor=\"?attr/colorOnPrimary\" />\n\n  <ScrollView\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintTop_toBottomOf=\"@+id/top_bar\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"@dimen/spacing_large\"\n        android:orientation=\"vertical\">\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"horizontal\">\n\n        <CheckBox\n            android:id=\"@+id/use_custom_fields\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"@string/use_custom_fields\" />\n\n        <TextView\n            android:id=\"@+id/custom_fields_list\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n      </LinearLayout>\n\n      <Button\n          android:id=\"@+id/find_current_place_button\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/find_current_place_button\" />\n\n      <CheckBox\n          android:id=\"@+id/display_raw_results\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"false\"\n          android:text=\"@string/display_raw_results\" />\n\n      <ProgressBar\n          android:id=\"@+id/loading\"\n          style=\"?android:attr/progressBarStyleSmall\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:visibility=\"invisible\" />\n\n      <TextView\n          android:id=\"@+id/response\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:textIsSelectable=\"true\" />\n\n    </LinearLayout>\n  </ScrollView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/place_autocomplete_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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=\".PlaceAutocompleteActivity\">\n\n  <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/top_bar\"\n      style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"?attr/actionBarSize\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:title=\"@string/autocomplete_button\"\n      app:titleTextColor=\"?attr/colorOnPrimary\" />\n\n  <ScrollView\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintTop_toBottomOf=\"@+id/top_bar\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\">\n\n    <LinearLayout\n        android:id=\"@+id/autocomplete_scroll_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"@dimen/spacing_large\"\n        android:orientation=\"vertical\">\n\n      <!--Autocomplete parameters-->\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_location_origin_label\" />\n\n      <EditText\n          android:id=\"@+id/autocomplete_location_origin\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:digits=\"0123456789.,- \"\n          android:hint=\"@string/autocomplete_location_origin_hint\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"numberDecimal|numberSigned\" />\n\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_location_bias_label\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"horizontal\">\n\n        <EditText\n            android:id=\"@+id/autocomplete_location_bias_south_west\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:autofillHints=\"\"\n            android:digits=\"0123456789.,- \"\n            android:hint=\"@string/autocomplete_location_south_west_hint\"\n            android:imeOptions=\"actionNext\"\n            android:inputType=\"numberDecimal|numberSigned\"\n            android:minHeight=\"48dp\" />\n\n        <EditText\n            android:id=\"@+id/autocomplete_location_bias_north_east\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:autofillHints=\"\"\n            android:digits=\"0123456789.,- \"\n            android:hint=\"@string/autocomplete_location_north_east_hint\"\n            android:imeOptions=\"actionNext\"\n            android:inputType=\"numberDecimal|numberSigned\" />\n      </LinearLayout>\n\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_location_restriction_label\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"horizontal\">\n\n        <EditText\n            android:id=\"@+id/autocomplete_location_restriction_south_west\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:autofillHints=\"\"\n            android:digits=\"0123456789.,- \"\n            android:hint=\"@string/autocomplete_location_south_west_hint\"\n            android:imeOptions=\"actionNext\"\n            android:inputType=\"numberDecimal|numberSigned\"\n            android:minHeight=\"48dp\" />\n\n        <EditText\n            android:id=\"@+id/autocomplete_location_restriction_north_east\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:autofillHints=\"\"\n            android:digits=\"0123456789.,- \"\n            android:hint=\"@string/autocomplete_location_north_east_hint\"\n            android:imeOptions=\"actionNext\"\n            android:inputType=\"numberDecimal|numberSigned\" />\n      </LinearLayout>\n\n      <EditText\n          android:id=\"@+id/autocomplete_query\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_query_hint\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\" />\n\n      <!-- Autocomplete fragment only -->\n      <EditText\n          android:id=\"@+id/autocomplete_hint\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_hint_hint\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\" />\n\n      <EditText\n          android:id=\"@+id/autocomplete_country\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/autocomplete_country_hint\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"vertical\">\n\n        <CheckBox\n            android:id=\"@+id/autocomplete_use_types_filter_checkbox\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:checked=\"false\"\n            android:minHeight=\"48dp\"\n            android:text=\"@string/autocomplete_use_types_filter\" />\n\n        <EditText\n            android:id=\"@+id/autocomplete_types_filter_edittext\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:autofillHints=\"\"\n            android:enabled=\"false\"\n            android:hint=\"@string/autocomplete_types_filter_hint\"\n            android:inputType=\"text\" />\n\n      </LinearLayout>\n\n      <!-- Autocomplete predictions only -->\n      <CheckBox\n          android:id=\"@+id/autocomplete_use_session_token\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"false\"\n          android:minHeight=\"48dp\"\n          android:text=\"@string/autocomplete_use_session_token\" />\n\n      <!-- Autocomplete activity only -->\n      <CheckBox\n          android:id=\"@+id/autocomplete_activity_overlay_mode\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"false\"\n          android:minHeight=\"48dp\"\n          android:text=\"@string/autocomplete_activity_overlay_mode\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"horizontal\">\n\n        <CheckBox\n            android:id=\"@+id/use_custom_fields\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:minHeight=\"48dp\"\n            android:text=\"@string/use_custom_fields\" />\n\n        <TextView\n            android:id=\"@+id/custom_fields_list\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n      </LinearLayout>\n\n      <Button\n          android:id=\"@+id/fetch_autocomplete_predictions_button\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/fetch_autocomplete_predictions_button\" />\n\n      <Button\n          android:id=\"@+id/autocomplete_activity_button\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_activity_button\" />\n\n      <!-- Autocomplete support fragment -->\n      <TextView\n          android:id=\"@+id/autocomplete_support_fragment_text_label\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_support_fragment_text_label\" />\n\n      <androidx.fragment.app.FragmentContainerView\n          android:id=\"@+id/autocomplete_support_fragment\"\n          android:name=\"com.google.android.libraries.places.widget.AutocompleteSupportFragment\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          tools:layout=\"@layout/places_autocomplete_fragment\" />\n\n      <Button\n          android:id=\"@+id/autocomplete_support_fragment_update_button\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/autocomplete_support_fragment_update_button\" />\n\n      <!-- Results -->\n      <CheckBox\n          android:id=\"@+id/display_raw_results\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"false\"\n          android:text=\"@string/display_raw_results\" />\n\n      <ProgressBar\n          android:id=\"@+id/loading\"\n          style=\"?android:attr/progressBarStyleSmall\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:visibility=\"invisible\" />\n\n      <TextView\n          android:id=\"@+id/response\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:textIsSelectable=\"true\" />\n\n    </LinearLayout>\n  </ScrollView>\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/place_details_and_photos_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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=\".PlaceDetailsAndPhotosActivity\">\n\n  <com.google.android.material.appbar.MaterialToolbar\n      android:id=\"@+id/top_bar\"\n      style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n      android:layout_width=\"0dp\"\n      android:layout_height=\"?attr/actionBarSize\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\"\n      app:layout_constraintTop_toTopOf=\"parent\"\n      app:title=\"@string/place_and_photo_button\"\n      app:titleTextColor=\"?attr/colorOnPrimary\" />\n\n  <ScrollView\n      android:layout_width=\"0dp\"\n      android:layout_height=\"0dp\"\n      app:layout_constraintTop_toBottomOf=\"@+id/top_bar\"\n      app:layout_constraintBottom_toBottomOf=\"parent\"\n      app:layout_constraintEnd_toEndOf=\"parent\"\n      app:layout_constraintStart_toStartOf=\"parent\">\n\n    <LinearLayout\n        android:id=\"@+id/place_scroll_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"@dimen/spacing_large\"\n        android:orientation=\"vertical\">\n\n      <EditText\n          android:id=\"@+id/place_id_field\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/place_id_field_hint\"\n          android:imeOptions=\"actionGo\"\n          android:inputType=\"text\"\n          android:text=\"@string/place_id_default\" />\n\n      <CheckBox\n          android:id=\"@+id/fetch_photo_checkbox\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"true\"\n          android:text=\"@string/fetch_photo_checkbox\" />\n\n      <CheckBox\n          android:id=\"@+id/fetch_icon_checkbox\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"false\"\n          android:text=\"@string/fetch_icon_checkbox\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"horizontal\">\n\n        <EditText\n            android:id=\"@+id/photo_max_width\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:autofillHints=\"\"\n            android:hint=\"@string/photo_max_width_hint\"\n            android:imeOptions=\"actionNext\"\n            android:inputType=\"number\" />\n\n        <EditText\n            android:id=\"@+id/photo_max_height\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:autofillHints=\"\"\n            android:hint=\"@string/photo_max_height_hint\"\n            android:imeOptions=\"actionNext\"\n            android:inputType=\"number\" />\n\n      </LinearLayout>\n\n      <CheckBox\n          android:id=\"@+id/use_custom_photo_reference\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"false\"\n          android:text=\"@string/use_custom_photo_reference\" />\n\n      <EditText\n          android:id=\"@+id/custom_photo_reference\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:autofillHints=\"\"\n          android:hint=\"@string/custom_photo_reference_hint\"\n          android:imeOptions=\"actionNext\"\n          android:inputType=\"text\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"horizontal\">\n\n        <CheckBox\n            android:id=\"@+id/use_custom_fields\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"@string/use_custom_fields\" />\n\n        <TextView\n            android:id=\"@+id/custom_fields_list\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n      </LinearLayout>\n\n      <Button\n          android:id=\"@+id/fetch_place_and_photo_button\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/fetch_place_and_photo_button\" />\n\n      <CheckBox\n          android:id=\"@+id/display_raw_results\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:checked=\"false\"\n          android:text=\"@string/display_raw_results\" />\n\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/icon_with_background\" />\n\n      <ImageView\n          android:id=\"@+id/icon\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:background=\"@color/material_grey_300\"\n          android:contentDescription=\"@string/icon_view_image_content_description\"\n          android:minWidth=\"48dp\"\n          android:minHeight=\"48dp\"\n          android:padding=\"4dp\"\n          app:tint=\"@android:color/white\" />\n\n      <TextView\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:text=\"@string/place_photo\" />\n\n      <LinearLayout\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          android:orientation=\"horizontal\">\n\n        <ImageView\n            android:id=\"@+id/photo\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/material_grey_300\"\n            android:minWidth=\"48dp\"\n            android:minHeight=\"48dp\" />\n\n        <ProgressBar\n            android:id=\"@+id/loading\"\n            style=\"?android:attr/progressBarStyleSmall\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"invisible\" />\n\n      </LinearLayout>\n\n      <TextView\n          android:id=\"@+id/photo_metadata\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:textIsSelectable=\"true\" />\n\n      <TextView\n          android:id=\"@+id/response\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:freezesText=\"true\"\n          android:textIsSelectable=\"true\" />\n\n    </LinearLayout>\n  </ScrollView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/place_is_open_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<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=\".PlaceIsOpenActivity\">\n\n    <com.google.android.material.appbar.MaterialToolbar\n        android:id=\"@+id/top_bar\"\n        style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"?attr/actionBarSize\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/main_isOpenButtonText\"\n        app:titleTextColor=\"?attr/colorOnPrimary\" />\n\n    <ScrollView\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/top_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"10dp\">\n\n            <EditText\n                android:id=\"@+id/editText_placeId\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:autofillHints=\"\"\n                android:hint=\"@string/isOpen_place_id_hint\"\n                android:imeOptions=\"actionGo\"\n                android:inputType=\"text\"\n                android:minHeight=\"48dp\"\n                android:text=\"@string/isOpen_default_place_id\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <CheckBox\n                    android:id=\"@+id/checkBox_use_custom_fields\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:minHeight=\"48dp\"\n                    android:text=\"@string/isOpen_use_custom_fields_text\" />\n\n                <TextView\n                    android:id=\"@+id/textView_custom_fields_list\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\" />\n\n            </LinearLayout>\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:minHeight=\"48dp\"\n                android:text=\"@string/isOpen_use_custom_time_hint\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\"\n                android:weightSum=\"10\">\n\n                <TextView\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"3\"\n                    android:minHeight=\"48dp\"\n                    android:text=\"@string/isOpen_spinner_description\" />\n\n                <Spinner\n                    android:id=\"@+id/spinner_time_zones\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"7\"\n                    android:minHeight=\"48dp\"\n                    android:padding=\"10dp\" />\n\n            </LinearLayout>\n\n            <EditText\n                android:id=\"@+id/editText_is_open_date\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:autofillHints=\"\"\n                android:clickable=\"false\"\n                android:cursorVisible=\"false\"\n                android:focusable=\"false\"\n                android:focusableInTouchMode=\"false\"\n                android:hint=\"@string/isOpen_date_hint\"\n                android:inputType=\"date\"\n                android:longClickable=\"false\"\n                android:minHeight=\"48dp\" />\n\n            <EditText\n                android:id=\"@+id/editText_is_open_time\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:autofillHints=\"\"\n                android:clickable=\"false\"\n                android:cursorVisible=\"false\"\n                android:focusable=\"false\"\n                android:focusableInTouchMode=\"false\"\n                android:hint=\"@string/isOpen_time_hint\"\n                android:inputType=\"time\"\n                android:longClickable=\"false\"\n                android:minHeight=\"48dp\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <Button\n                    android:id=\"@+id/button_fetchPlace\"\n                    style=\"?android:attr/buttonBarButtonStyle\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:text=\"@string/isOpen_fetch_place_button_text\" />\n\n                <Button\n                    android:id=\"@+id/button_isOpen\"\n                    style=\"?android:attr/buttonBarButtonStyle\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:text=\"@string/isOpen_is_open_button_text\" />\n\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <ProgressBar\n                    android:id=\"@+id/progressBar_loading\"\n                    style=\"?android:attr/progressBarStyleSmall\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:visibility=\"invisible\" />\n\n            </LinearLayout>\n\n            <TextView\n                android:id=\"@+id/textView_response\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:freezesText=\"true\"\n                android:textIsSelectable=\"true\" />\n\n        </LinearLayout>\n    </ScrollView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/layout/place_prediction_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<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=\"wrap_content\"\n  android:padding=\"16dp\"\n  android:orientation=\"vertical\">\n\n  <TextView\n    android:id=\"@+id/text_view_title\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:textStyle=\"bold\"\n    android:textSize=\"18sp\"\n    tools:text=\"Place Name\" />\n\n  <TextView\n    android:id=\"@+id/text_view_address\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:textSize=\"16sp\"\n    android:layout_marginTop=\"2dp\"\n    tools:text=\"123 Main Street, San Francisco, CA USA\" />\n\n</LinearLayout>"
  },
  {
    "path": "demo-kotlin/src/main/res/menu/main_activity_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2025 Google LLC\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<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n  <item\n      android:id=\"@+id/action_exit\"\n      android:icon=\"@drawable/ic_exit_to_app_black_24dp\"\n      android:title=\"@string/action_exit\"\n      app:showAsAction=\"always\" />\n</menu>\n"
  },
  {
    "path": "demo-kotlin/src/main/res/menu/menu.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n  <item\n    android:id=\"@+id/search\"\n    android:icon=\"@drawable/ic_search_black_24dp\"\n    android:title=\"@string/search\"\n    app:actionViewClass=\"com.google.android.material.search.SearchView\"\n    app:showAsAction=\"collapseActionView|ifRoom\" />\n</menu>"
  },
  {
    "path": "demo-kotlin/src/main/res/raw/style_json.json",
    "content": "[\n  {\n    \"featureType\": \"poi\",\n    \"elementType\": \"all\",\n    \"stylers\": [\n      {\n        \"visibility\": \"off\"\n      }\n    ]\n  },\n  {\n    \"featureType\": \"transit\",\n    \"elementType\": \"labels.icon\",\n    \"stylers\": [\n      {\n        \"visibility\": \"off\"\n      }\n    ]\n  }\n]"
  },
  {
    "path": "demo-kotlin/src/main/res/values/dimens.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<resources>\n  <dimen name=\"spacing_large\">4dp</dimen>\n  <dimen name=\"spacing_small\">2dp</dimen>\n</resources>"
  },
  {
    "path": "demo-kotlin/src/main/res/values/strings.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<resources>\n  <!-- Title of the app. -->\n  <string name=\"app_name\" translatable=\"false\">Android Places Demo</string>\n\n  <!-- title for error alerts. -->\n  <string name=\"error_alert_title\" translatable=\"false\">Uh-Oh!</string>\n  <!-- message to show for error alerts when location origin is malformed. -->\n  <string name=\"error_alert_message_invalid_origin\" translatable=\"false\">Unable to parse Location Origin. Expected format: \"33, 128\"</string>\n  <!-- message to show for error alerts when location bias or restriction is malformed. -->\n  <string name=\"error_alert_message_invalid_bounds\" translatable=\"false\">Unable to parse Location Bias or Location Restriction. Expected format: \"33, 128\"</string>\n  <!-- message to show for error alerts when a photo size field is malformed. -->\n  <string name=\"error_alert_message_invalid_photo_size\" translatable=\"false\">Unable to parse Photo size. Expected format is integers</string>\n\n  <string name=\"error_api_key\" translatable=\"false\">No API key defined in gradle.properties</string>\n\n  <!-- Text for exit menu item. -->\n  <string name=\"action_exit\" translatable=\"false\">Exit</string>\n\n  <!-- Text for raw results checkbox. When checked raw return values will be displayed. -->\n  <string name=\"display_raw_results\" translatable=\"false\">Display raw results?</string>\n\n\n  <!-- MAIN ACTIVITY -->\n\n  <!-- Button for launching autocomplete test activity. -->\n  <string name=\"autocomplete_button\" translatable=\"false\">Place Autocomplete</string>\n\n  <!-- Button for launching autocomplete address form activity. -->\n  <string name=\"autocomplete_address_button\" translatable=\"false\">Place Autocomplete Address Form</string>\n\n  <!-- Button for launching current place test activity. -->\n  <string name=\"current_place_button\" translatable=\"false\">Current Place</string>\n\n  <!-- Button for launching place and photo test activity. -->\n  <string name=\"place_and_photo_button\" translatable=\"false\">Place Details and Place Photos</string>\n\n  <!-- Button for launching is open test activity. -->\n  <string name=\"main_isOpenButtonText\" translatable=\"false\">Place is Open?</string>\n\n\n  <!-- AUTOCOMPLETE -->\n\n  <!-- Hint for the autocomplete widget's initial query and autocomplete prediction's query field. -->\n  <string name=\"autocomplete_query_hint\" translatable=\"false\">(Initial) Query</string>\n\n  <!-- Hint for the autocomplete widget's query field. -->\n  <string name=\"autocomplete_hint_hint\" translatable=\"false\">Hint</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's countries field. -->\n  <string name=\"autocomplete_country_hint\" translatable=\"false\">Countries (e.g CH, US, RO)</string>\n\n  <!-- Text for enabling autocomplete prediction's and widget's types filter field. -->\n  <string name=\"autocomplete_use_types_filter\" translatable=\"false\">Use TypesFilter?</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's types filter field. -->\n  <string name=\"autocomplete_types_filter_hint\" translatable=\"false\">\n    <font size=\"16\">\n      Comma separated list of up to 5 place types\n    </font>\n  </string>\n\n  <!-- Text for the fetch autocomplete predictions submit button. -->\n  <string name=\"fetch_autocomplete_predictions_button\" translatable=\"false\">Fetch Predictions</string>\n\n  <!-- AUTOCOMPLETE ADDRESS -->\n\n  <!-- Label for the address form's first address line field. -->\n  <string name=\"autocomplete_address1_label\" translatable=\"false\">Address Line 1</string>\n\n  <!-- Label for the address form's second address line field. -->\n  <string name=\"autocomplete_address2_label\" translatable=\"false\">Apartment, unit, suite, or floor #</string>\n\n  <!-- Label for the address form's city field. -->\n  <string name=\"autocomplete_city_label\" translatable=\"false\">City</string>\n\n  <!-- Label for the address form's state field. -->\n  <string name=\"autocomplete_state_label\" translatable=\"false\">State/Province</string>\n\n  <!-- Label for the address form's postal code field. -->\n  <string name=\"autocomplete_postal_label\" translatable=\"false\">Postal code</string>\n\n  <!-- Label for the address form's country field. -->\n  <string name=\"autocomplete_country_label\" translatable=\"false\">Country/Region</string>\n\n  <!-- Label for the address form's proximity check setting. -->\n  <string name=\"autocomplete_proximity_check\" translatable=\"false\">Check device proximity</string>\n\n  <!-- Label for the address form's confirmation map. -->\n  <string name=\"autocomplete_map_label\" translatable=\"false\">Map view of the selected address</string>\n\n  <!-- Text for the address form submit button. -->\n  <string name=\"autocomplete_save_button\" translatable=\"false\">Submit address</string>\n\n  <!-- Text for the address form reset button. -->\n  <string name=\"autocomplete_reset_button\" translatable=\"false\">Clear form</string>\n\n  <!-- Label for the autocomplete prediction's and widget's location origin field. -->\n  <string name=\"autocomplete_location_origin_label\" translatable=\"false\">Location Origin (format: -33.2, 128.2)</string>\n\n  <!-- Label for the autocomplete prediction's and widget's location bias related fields. -->\n  <string name=\"autocomplete_location_bias_label\" translatable=\"false\">Location Bias (format: -33.2, 128.2)</string>\n\n  <!-- Label for the autocomplete prediction's and widget's location restriction related fields. -->\n  <string name=\"autocomplete_location_restriction_label\" translatable=\"false\">Location Restriction (format: -33.2, 128.2)</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's location origin field. -->\n  <string name=\"autocomplete_location_origin_hint\" translatable=\"false\">Origin (lat, lng)</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's location southwest field. -->\n  <string name=\"autocomplete_location_south_west_hint\" translatable=\"false\">South West (lat, lng)</string>\n\n  <!-- Hint for the autocomplete prediction's and widget's location northeast field. -->\n  <string name=\"autocomplete_location_north_east_hint\" translatable=\"false\">North East (lat, lng)</string>\n\n  <!-- Text for enabling autocomplete prediction's use of session tokens. -->\n  <string name=\"autocomplete_use_session_token\" translatable=\"false\">(Predictions-only) Use session token?</string>\n\n  <!-- Text for autocomplete activity mode (overlay or fullscreen) checkbox. -->\n  <string name=\"autocomplete_activity_overlay_mode\" translatable=\"false\">Overlay mode?</string>\n\n  <!-- Text for the autocomplete support fragment section label. -->\n  <string name=\"autocomplete_support_fragment_text_label\" translatable=\"false\">Autocomplete Support Fragment:</string>\n\n  <!-- Text for the autocomplete fragment section label. -->\n  <string name=\"autocomplete_fragment_text_label\" translatable=\"false\">Autocomplete Fragment:</string>\n\n  <!-- Text for the autocomplete support fragment update button. -->\n  <string name=\"autocomplete_support_fragment_update_button\" translatable=\"false\">Update Support Fragment</string>\n\n  <!-- Text for the autocomplete fragment update button. -->\n  <string name=\"autocomplete_fragment_update_button\" translatable=\"false\">Update Fragment</string>\n\n  <!-- Text for starting the autocomplete activity button. -->\n  <string name=\"autocomplete_activity_button\" translatable=\"false\">Open Activity</string>\n\n  <!-- Text for the toast message for a skilled proximity match. -->\n  <string name=\"autocomplete_skipped_message\" translatable=\"false\">Address accepted without checking proximity</string>\n\n  <!-- PROGRAMMATIC AUTOCOMPLETE AND GEOCODING -->\n\n  <string name=\"programmatic_autocomplete_geocoding\">Programmatic Autocomplete + Geocoding</string>\n  <string name=\"search\">Search</string>\n  <string name=\"search_a_place\">Search for a Place</string>\n  <string name=\"programmatic_place_predictions_instructions\" translatable=\"false\">This activity demonstrates as-you-type programmatic place predictions. Tap on the search icon on the toolbar and search for a place.</string>\n  <string name=\"programmatic_place_predictions_no_matches\" translatable=\"false\">No matching search results</string>\n  <string name=\"programmatic_place_predictions_error\" translatable=\"false\">Error fetching place search results</string>\n\n  <!-- FIND CURRENT PLACE -->\n\n  <!-- Text for the find current place submit button. -->\n  <string name=\"find_current_place_button\" translatable=\"false\">Find Current Place</string>\n  <string name=\"icon_view_content_description\" translatable=\"false\">Icon View</string>\n  <string name=\"fetch_icon_checkbox\" translatable=\"false\">Also fetch icon?</string>\n  <string name=\"fetch_icon_missing_fields_warning\" translatable=\"false\">\\'Also fetch icon?\\' is selected, but ICON_URL Place Field is not.</string>\n  <string name=\"fetch_photo_selected_but_no_metadata\">\\'Also fetch photo?\\' is selected, but PHOTO_METADATAS Place Field is not.</string>\n  <string name=\"custom_photo_reference_but_not_fetch_photo\">Using \\'Custom photo reference\\', but \\'Also fetch photo?\\' is not selected.</string>\n  <string name=\"icon_view_image_content_description\" translatable=\"false\">Image view to display a Place Icon Image</string>\n  <string name=\"icon_with_background\" translatable=\"false\">Icon with background:</string>\n  <string name=\"place_photo\" translatable=\"false\">Place photo:</string>\n\n  <!-- PLACE DETAILS AND PHOTO -->\n\n  <!-- Place ID for a dining establishment, used as the default for testing in the Place and Photo screen -->\n  <string name=\"place_id_default\" translatable=\"false\">ChIJM3wn3VVYwokRvu2kbqBKUsM</string>\n\n  <!-- Hint for the place ID field. -->\n  <string name=\"place_id_field_hint\" translatable=\"false\">Enter place ID</string>\n\n  <!-- Text for the fetch place and photo button. -->\n  <string name=\"fetch_photo_checkbox\" translatable=\"false\">Also fetch photo?</string>\n\n  <!-- Hint for the fetch photo max width field. -->\n  <string name=\"photo_max_width_hint\" translatable=\"false\">Photo Max Width</string>\n\n  <!-- Text for the fetch photo max height field. -->\n  <string name=\"photo_max_height_hint\" translatable=\"false\">Photo Max Height</string>\n\n  <!-- Text for the fetch place and photo button. -->\n  <string name=\"use_custom_photo_reference\" translatable=\"false\">Use custom Photo Reference?</string>\n\n  <!-- Text for the fetch place and photo button. -->\n  <string name=\"custom_photo_reference_hint\" translatable=\"false\">Custom Photo Reference</string>\n\n  <!-- Text for checkbox to set custom list of Place Fields. -->\n  <string name=\"use_custom_fields\" translatable=\"false\">Manually set Place Fields?</string>\n\n  <!-- Text for the fetch place and photo button. -->\n  <string name=\"fetch_place_and_photo_button\" translatable=\"false\">Fetch Place and Photo</string>\n\n  <!-- PLACE IS OPEN -->\n\n  <string name=\"isOpen_fetch_place_button_text\" translatable=\"false\">Fetch Place then check IsOpen</string>\n  <string name=\"isOpen_is_open_button_text\" translatable=\"false\">Request IsOpen by Place ID</string>\n  <string name=\"isOpen_place_id_hint\" translatable=\"false\">Enter place ID</string>\n  <string name=\"isOpen_use_custom_fields_text\" translatable=\"false\">Manually set Place Fields?</string>\n  <string name=\"isOpen_spinner_description\" translatable=\"false\">Time Zone</string>\n  <string name=\"isOpen_date_hint\" translatable=\"false\">Select isOpen date</string>\n  <string name=\"isOpen_time_hint\" translatable=\"false\">Select isOpen time</string>\n  <string name=\"isOpen_use_custom_time_hint\" translatable=\"false\">Use custom isOpen time? (must set time zone, date, and time)</string>\n  <string name=\"isOpen_default_place_id\" translatable=\"false\">ChIJD3uTd9hx5kcR1IQvGfr8dbk</string>\n\n</resources>\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nagp = \"8.13.2\"\nkotlin = \"2.3.10\"\ncoreKtx = \"1.17.0\"\nappcompat = \"1.7.1\"\nactivity = \"1.12.4\"\nfragment = \"1.8.9\"\nlifecycle = \"2.10.0\"\nmaterial = \"1.13.0\"\nconstraintlayout = \"2.2.1\"\nmultidex = \"2.0.1\"\ncomposeBom = \"2026.02.01\"\nui = \"1.10.0\"\nmaterialIconsExtended = \"1.7.8\"\ngoogleMaps = \"20.0.0\"\nplaces = \"5.1.1\"\nplayServicesLocation = \"21.3.0\"\nmapsCompose = \"8.2.2\"\nmapsUtilsKtx = \"6.0.1\"\nandroidMapsUtils = \"4.1.1\"\nplayServicesMaps3d = \"0.2.0\"\nvolley = \"1.2.1\"\nglide = \"5.0.5\"\nnavigationFragment = \"2.9.5\"\nviewbinding = \"8.13.2\"\ntruth = \"1.4.5\"\nkotlinxDatetime = \"0.7.1\"\ncoil = \"2.7.0\"\nhilt = \"2.57.2\"\njunit = \"4.13.2\"\njunitVersion = \"1.3.0\"\nespressoCore = \"3.7.0\"\ntestRules = \"1.7.0\"\nksp = \"2.3.6\"\nkotlinParcelize = \"2.3.10\"\nmapsSecretsGradlePlugin = \"2.0.1\"\n\n[libraries]\nandroid-gradlePlugin = { group = \"com.android.tools.build\", name = \"gradle\", version.ref = \"agp\" }\nkotlin-gradlePlugin = { group = \"org.jetbrains.kotlin\", name = \"kotlin-gradle-plugin\", version.ref = \"kotlin\" }\nkotlin-metadata-jvm = { group = \"org.jetbrains.kotlin\", name = \"kotlin-metadata-jvm\", version.ref = \"kotlin\" }\nsecrets-gradlePlugin = { group = \"com.google.android.libraries.mapsplatform.secrets-gradle-plugin\", name = \"secrets-gradle-plugin\", version.ref = \"mapsSecretsGradlePlugin\" }\n\n# Core Android & Kotlin\nandroidx-core-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"coreKtx\" }\ncore-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"coreKtx\" }\nandroidx-appcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version.ref = \"appcompat\" }\nappcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version.ref = \"appcompat\" }\nandroidx-activity = { group = \"androidx.activity\", name = \"activity\", version.ref = \"activity\" }\nactivity = { group = \"androidx.activity\", name = \"activity\", version.ref = \"activity\" }\nandroidx-lifecycle-viewmodel-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-viewmodel-ktx\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-viewmodel-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-viewmodel-compose\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-runtime-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-ktx\", version.ref = \"lifecycle\" }\nandroidx-lifecycle-runtime-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-compose\", version.ref = \"lifecycle\" }\nandroidx-fragment-ktx = { group = \"androidx.fragment\", name = \"fragment-ktx\", version.ref = \"fragment\" }\nandroidx-fragment-compose = { group = \"androidx.fragment\", name = \"fragment-compose\", version.ref = \"fragment\" }\nfragment = { group = \"androidx.fragment\", name = \"fragment\", version.ref = \"fragment\" }\nmaterial = { group = \"com.google.android.material\", name = \"material\", version.ref = \"material\" }\nandroidx-constraintlayout = { group = \"androidx.constraintlayout\", name = \"constraintlayout\", version.ref = \"constraintlayout\" }\nconstraintlayout = { group = \"androidx.constraintlayout\", name = \"constraintlayout\", version.ref = \"constraintlayout\" }\nandroidx-multidex = { group = \"androidx.multidex\", name = \"multidex\", version.ref = \"multidex\" }\nmultidex = { group = \"androidx.multidex\", name = \"multidex\", version.ref = \"multidex\" }\n\n# Jetpack Compose\nandroidx-compose-bom = { group = \"androidx.compose\", name = \"compose-bom\", version.ref = \"composeBom\" }\nandroidx-activity-compose = { group = \"androidx.activity\", name = \"activity-compose\", version.ref = \"activity\" }\nandroidx-material3 = { group = \"androidx.compose.material3\", name = \"material3\" }\nandroidx-compose-material3 = { group = \"androidx.compose.material3\", name = \"material3\" }\nandroidx-compose-ui = { group = \"androidx.compose.ui\", name = \"ui\" }\nandroidx-compose-ui-tooling = { group = \"androidx.compose.ui\", name = \"ui-tooling\" }\nandroidx-compose-ui-tooling-preview = { group = \"androidx.compose.ui\", name = \"ui-tooling-preview\" }\nandroidx-ui = { group = \"androidx.compose.ui\", name = \"ui\" }\nandroidx-ui-graphics = { group = \"androidx.compose.ui\", name = \"ui-graphics\" }\nandroidx-ui-tooling = { group = \"androidx.compose.ui\", name = \"ui-tooling\" }\nandroidx-ui-tooling-preview = { group = \"androidx.compose.ui\", name = \"ui-tooling-preview\" }\nandroidx-ui-viewbinding = { group = \"androidx.compose.ui\", name = \"ui-viewbinding\", version.ref = \"ui\" }\nandroidx-compose-runtime-livedata = { group = \"androidx.compose.runtime\", name = \"runtime-livedata\" }\nandroidx-compose-material-icons-extended = { group = \"androidx.compose.material\", name = \"material-icons-extended\", version.ref = \"materialIconsExtended\" }\nandroidx-material-icons-extended = { group = \"androidx.compose.material\", name = \"material-icons-extended\", version.ref = \"materialIconsExtended\" }\nandroidx-compose-material-icons-core = { group = \"androidx.compose.material\", name = \"material-icons-core\" }\n\n# Google Services (Maps, Places, Location)\ngoogle-maps-services = { group = \"com.google.android.gms\", name = \"play-services-maps\", version.ref = \"googleMaps\"}\nplay-services-maps = { group = \"com.google.android.gms\", name = \"play-services-maps\", version.ref = \"googleMaps\" }\nplay-services-maps3d = { group = \"com.google.android.gms\", name = \"play-services-maps3d\", version.ref = \"playServicesMaps3d\" }\nplaces = { group = \"com.google.android.libraries.places\", name = \"places\", version.ref = \"places\" }\nplay-services-location = { group = \"com.google.android.gms\", name = \"play-services-location\", version.ref = \"playServicesLocation\" }\nmaps-compose = { group = \"com.google.maps.android\", name = \"maps-compose\", version.ref = \"mapsCompose\" }\nmaps-compose-widgets = { group = \"com.google.maps.android\", name = \"maps-compose-widgets\", version.ref = \"mapsCompose\" }\nmaps-utils-ktx = { group = \"com.google.maps.android\", name = \"maps-utils-ktx\", version.ref = \"mapsUtilsKtx\" }\nandroid-maps-utils = { group = \"com.google.maps.android\", name = \"android-maps-utils\", version.ref = \"androidMapsUtils\" }\n\n# Dependency Injection\ndagger = { group = \"com.google.dagger\", name = \"dagger\", version.ref = \"hilt\" }\nhilt-android = { group = \"com.google.dagger\", name = \"hilt-android\", version.ref = \"hilt\" }\nhilt-android-compiler = { group = \"com.google.dagger\", name = \"hilt-android-compiler\", version.ref = \"hilt\" }\n\n# Miscellaneous\nvolley = { group = \"com.android.volley\", name = \"volley\", version.ref = \"volley\" }\nglide = { group = \"com.github.bumptech.glide\", name = \"glide\", version.ref = \"glide\" }\nglide-compiler = { group = \"com.github.bumptech.glide\", name = \"compiler\", version.ref = \"glide\" }\nnavigation-fragment = { group = \"androidx.navigation\", name = \"navigation-fragment\", version.ref = \"navigationFragment\" }\nnavigation-ui = { group = \"androidx.navigation\", name = \"navigation-ui\", version.ref = \"navigationFragment\" }\nviewbinding = { group = \"com.android.databinding\", name = \"viewbinding\", version.ref = \"viewbinding\" }\nkotlinx-datetime = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-datetime\", version.ref = \"kotlinxDatetime\" }\ncoil-compose = { group = \"io.coil-kt\", name = \"coil-compose\", version.ref = \"coil\" }\n\n# Testing\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" }\nandroidx-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"junitVersion\" }\nandroidx-espresso-core = { group = \"androidx.test.espresso\", name = \"espresso-core\", version.ref = \"espressoCore\" }\ngoogle-truth = { group = \"com.google.truth\", name = \"truth\", version.ref = \"truth\" }\nandroidx-test-rules = { group = \"androidx.test\", name = \"rules\", version.ref = \"testRules\" }\nandroidx-test-runner = { group = \"androidx.test\", name = \"runner\", version.ref = \"testRules\" }\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-compose-ui-test-manifest = { group = \"androidx.compose.ui\", name = \"ui-test-manifest\" }\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\nkotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\njetbrains-kotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\nkotlin-compose = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }\nkotlin-kapt = { id = \"org.jetbrains.kotlin.kapt\", version.ref = \"kotlin\" }\nsecrets-gradle-plugin = { id = \"com.google.android.libraries.mapsplatform.secrets-gradle-plugin\", version.ref = \"mapsSecretsGradlePlugin\" }\njetbrains-kotlin-parcelize = { id = \"org.jetbrains.kotlin.plugin.parcelize\", version.ref = \"kotlinParcelize\" }\nhilt-android = { id = \"com.google.dagger.hilt.android\", version.ref = \"hilt\" }\nhilt-android-plugin = { id = \"com.google.dagger.hilt.android\", version.ref = \"hilt\" }\nksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.1.0-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "android.useAndroidX=true\norg.gradle.jvmargs=-Xmx4096m\norg.gradle.caching=true\norg.gradle.configuration-cache=true\nandroid.defaults.buildfeatures.resvalues=true\nandroid.enableAppCompileTimeRClass=false\nandroid.r8.strictFullModeForKeepRules=false\nandroid.r8.optimizedResourceShrinking=false"
  },
  {
    "path": "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#      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##\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# 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, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\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=$((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\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "kotlin-demos/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "kotlin-demos/build.gradle.kts",
    "content": "/*\n * Copyright 2026 Google LLC\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\nplugins {\n    id(\"places-demo.android.application\")\n    alias(libs.plugins.kotlin.android)\n    alias(libs.plugins.kotlin.compose)\n    alias(libs.plugins.ksp)\n    alias(libs.plugins.hilt.android.plugin)\n    id(\"places-demo.secrets\")\n}\n\nandroid {\n    namespace = \"com.google.places.android.ktx.demo\"\n\n    defaultConfig {\n        applicationId = \"com.google.maps.android.ktx.demo\"\n        versionCode = 1\n        versionName = \"1.0\"\n        multiDexEnabled = true\n    }\n\n    buildFeatures {\n        buildConfig = true\n        compose = true\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = false\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n\n    kotlin {\n        compilerOptions {\n            freeCompilerArgs.addAll(\n                \"-opt-in=kotlin.RequiresOptIn\",\n                \"-Xannotation-default-target=param-property\"\n            )\n        }\n    }\n}\n\ndemoApp {\n    mainActivity.set(\".DemoActivity\")\n}\n\ndependencies {\n    implementation(platform(libs.androidx.compose.bom))\n\n    // Core Compose libraries\n    implementation(libs.androidx.compose.ui)\n    implementation(libs.androidx.compose.ui.tooling.preview)\n    implementation(libs.androidx.compose.material3)\n    implementation(libs.androidx.activity.compose)\n    implementation(libs.androidx.compose.runtime.livedata)\n    implementation(libs.androidx.compose.material.icons.extended)\n\n    debugImplementation(libs.androidx.compose.ui.tooling)\n    debugImplementation(libs.androidx.compose.ui.test.manifest)\n\n    implementation(libs.androidx.appcompat)\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.multidex)\n    implementation(libs.play.services.maps)\n    implementation(libs.androidx.fragment.ktx)\n    implementation(libs.androidx.lifecycle.runtime.ktx)\n    implementation(libs.androidx.lifecycle.runtime.compose)\n    implementation(libs.material)\n    implementation(libs.volley)\n\n    // Hilt\n    implementation(libs.hilt.android)\n    ksp(libs.hilt.android.compiler)\n\n    // Coil\n    implementation(libs.coil.compose)\n\n    implementation(libs.places)\n}\n\n\n"
  },
  {
    "path": "kotlin-demos/local.defaults.properties",
    "content": "PLACES_API_KEY=\"YOUR_API_KEY\"\nMAPS_API_KEY=\"YOUR_API_KEY\""
  },
  {
    "path": "kotlin-demos/proguard-rules.pro",
    "content": "# Copyright 2026 Google LLC\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# 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\n"
  },
  {
    "path": "kotlin-demos/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Copyright 2026 Google LLC\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<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\" >\n\n    <!-- Remove ACCESS_FINE_LOCATION from the merged manifest since this sample does not use\n    `PlacesClient.findCurrentPlace()` -->\n    <uses-permission\n        android:name=\"android.permission.ACCESS_FINE_LOCATION\"\n        tools:node=\"remove\" />\n    <uses-permission\n        android:name=\"android.permission.ACCESS_COARSE_LOCATION\"\n        tools:node=\"remove\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n\n    <application\n        android:name=\".DemoApplication\"\n        android:allowBackup=\"true\"\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/AppTheme\">\n        <activity\n            android:name=\".DemoActivity\"\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=\".AutocompleteDemoActivity\" />\n\n        <activity\n            android:name=\".PlacesSearchDemoActivity\"\n            android:theme=\"@style/Theme.AppCompat.DayNight.NoActionBar\" />\n    <activity\n        android:name=\".PlacesPhotoDemoActivity\"\n        android:exported=\"false\"\n        android:label=\"@string/places_photo_demo_title\"\n        android:theme=\"@style/Theme.AppCompat.DayNight.NoActionBar\" />\n</application>\n</manifest>\n"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/AutocompleteDemoActivity.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport android.graphics.Typeface\nimport android.os.Bundle\nimport android.text.style.StyleSpan\nimport android.widget.Toast\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.compose.setContent\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.android.libraries.places.api.net.kotlin.awaitFetchPlace\nimport com.google.android.libraries.places.widget.PlaceAutocomplete\nimport com.google.android.libraries.places.widget.PlaceAutocompleteActivity\nimport kotlinx.coroutines.launch\nimport com.google.places.android.ktx.demo.ui.DemoTheme\n\nclass AutocompleteDemoActivity : ComponentActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        setContent {\n            DemoTheme {\n                AutocompleteDemoScreen(onBackPressed = { finish() })\n            }\n        }\n    }\n\n    @OptIn(ExperimentalMaterial3Api::class)\n    @Composable\n    private fun AutocompleteDemoScreen(onBackPressed: () -> Unit) {\n        val placesClient = remember { Places.createClient(this) }\n        val scope = rememberCoroutineScope()\n        var placeDetails by remember { mutableStateOf<String?>(null) }\n        var isFetching by remember { mutableStateOf(false) }\n\n        val startAutocomplete =\n            rememberLauncherForActivityResult(\n                contract = ActivityResultContracts.StartActivityForResult()\n            ) { result ->\n                if (result.resultCode == RESULT_OK) {\n                    val intent = result.data\n                    if (intent != null) {\n                        val prediction = PlaceAutocomplete.getPredictionFromIntent(intent)\n                        if (prediction != null) {\n                            placeDetails = \"Fetching details for '${prediction.getPrimaryText(null)}'...\"\n                            isFetching = true\n\n                            // Modern Pattern: Use the widget to get a prediction, then fetch full details via KTX\n                            scope.launch {\n                                try {\n                                    val fields = listOf(Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS)\n                                    val response = placesClient.awaitFetchPlace(prediction.placeId, fields)\n                                    val place = response.place\n                                    placeDetails = \"Got place '${place.displayName}' (${place.formattedAddress})\"\n                                } catch (e: Exception) {\n                                    placeDetails = \"Error fetching details: ${e.message}\"\n                                } finally {\n                                    isFetching = false\n                                }\n                            }\n                        }\n                    }\n                } else if (result.resultCode == PlaceAutocompleteActivity.RESULT_ERROR) {\n                    val intent = result.data\n                    if (intent != null) {\n                        val status = PlaceAutocomplete.getResultStatusFromIntent(intent)\n                        Toast.makeText(\n                            this,\n                            \"Autocomplete error: ${status?.statusMessage}\",\n                            Toast.LENGTH_SHORT\n                        ).show()\n                    }\n                }\n            }\n\n        Scaffold(\n            topBar = {\n                TopAppBar(\n                    title = { Text(\"Autocomplete Demo\") },\n                    navigationIcon = {\n                        IconButton(onClick = onBackPressed) {\n                            Icon(\n                                Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = \"Back\"\n                            )\n                        }\n                    }\n                )\n            }\n        ) { padding ->\n            Column(\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(padding)\n                    .padding(24.dp),\n                verticalArrangement = Arrangement.Center,\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                Card(\n                    modifier = Modifier.fillMaxWidth(),\n                    colors = CardDefaults.cardColors(\n                        containerColor = MaterialTheme.colorScheme.surfaceVariant\n                    )\n                ) {\n                    Column(\n                        modifier = Modifier.padding(24.dp),\n                        horizontalAlignment = Alignment.CenterHorizontally\n                    ) {\n                        Text(\n                            text = placeDetails ?: \"No place selected yet\",\n                            style = MaterialTheme.typography.bodyLarge,\n                            fontWeight = FontWeight.Medium,\n                            color = when {\n                                isFetching -> MaterialTheme.colorScheme.secondary\n                                placeDetails != null -> MaterialTheme.colorScheme.primary\n                                else -> MaterialTheme.colorScheme.onSurfaceVariant\n                            }\n                        )\n\n                        Spacer(modifier = Modifier.height(32.dp))\n\n                        Button(\n                            onClick = {\n                                val fields = listOf(Place.Field.ID, Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS)\n                                val intent = PlaceAutocomplete.createIntent(this@AutocompleteDemoActivity) {\n                                    // Specify any builder parameters here (e.g. setCountries, setLocationBias, etc.)\n                                }\n                                startAutocomplete.launch(intent)\n                            },\n                            modifier = Modifier.fillMaxWidth(),\n                            shape = MaterialTheme.shapes.medium\n                        ) {\n                            Text(\"Start Autocomplete\")\n                        }\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/Demo.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport android.app.Activity\nimport androidx.annotation.StringRes\n\nenum class Demo(\n    @StringRes val title: Int,\n    @StringRes val description: Int,\n    val activity: Class<out Activity>\n) {\n    AUTOCOMPLETE_FRAGMENT_DEMO(\n        R.string.autocomplete_fragment_demo_title,\n        R.string.autocomplete_fragment_demo_description,\n        AutocompleteDemoActivity::class.java\n    ),\n    PLACES_SEARCH_DEMO(\n        R.string.places_demo_title,\n        R.string.places_demo_description,\n        PlacesSearchDemoActivity::class.java\n    ),\n    PLACES_PHOTO_DEMO(\n        R.string.places_photo_demo_title,\n        R.string.places_photo_demo_description,\n        PlacesPhotoDemoActivity::class.java\n    )\n}"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/DemoActivity.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.google.places.android.ktx.demo.ui.DemoTheme\n\nclass DemoActivity : ComponentActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContent {\n            DemoTheme {\n                DemoListScreen(\n                    onDemoSelected = { demo ->\n                        startActivity(Intent(this, demo.activity))\n                    }\n                )\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun DemoListScreen(onDemoSelected: (Demo) -> Unit) {\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = {\n                    Text(\n                        stringResource(R.string.app_name),\n                        style = MaterialTheme.typography.headlineSmall,\n                        fontWeight = FontWeight.Bold\n                    )\n                },\n                colors = TopAppBarDefaults.topAppBarColors(\n                    containerColor = MaterialTheme.colorScheme.primaryContainer,\n                    titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer\n                )\n            )\n        }\n    ) { padding ->\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(padding),\n            contentPadding = PaddingValues(16.dp),\n            verticalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            item {\n                Text(\n                    text = \"Places KTX Demo\",\n                    style = MaterialTheme.typography.titleLarge,\n                    modifier = Modifier.padding(bottom = 8.dp)\n                )\n            }\n            items(Demo.entries) { demo ->\n                DemoItemCard(demo = demo, onClick = { onDemoSelected(demo) })\n            }\n        }\n    }\n}\n\n@Composable\nfun DemoItemCard(demo: Demo, onClick: () -> Unit) {\n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable(onClick = onClick),\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Column(\n            modifier = Modifier.padding(16.dp)\n        ) {\n            Text(\n                text = stringResource(demo.title),\n                style = MaterialTheme.typography.titleMedium,\n                fontWeight = FontWeight.Bold,\n                color = MaterialTheme.colorScheme.primary\n            )\n            Spacer(modifier = Modifier.height(4.dp))\n            Text(\n                text = stringResource(demo.description),\n                style = MaterialTheme.typography.bodyMedium,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    }\n}"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/DemoApplication.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport android.app.Application\nimport com.google.android.libraries.places.api.Places\nimport dagger.hilt.android.HiltAndroidApp\n\n@HiltAndroidApp\nclass DemoApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n\n        // Initialize the Places SDK. Note that the string value of `maps_api_key` will be generated\n        // at build-time (see app/build.gradle). The technique used here allows you to provide your\n        // API key such that the key is not checked in source control.\n        //\n        // See API Key Best Practices for more information on how to secure your API key:\n        // https://developers.google.com/maps/api-key-best-practices\n        // Initialize the Places SDK with the new API engine enabled\n        Places.initializeWithNewPlacesApiEnabled(this, BuildConfig.PLACES_API_KEY)\n    }\n}\n"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/PlacesPhotoDemoActivity.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.viewModels\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.MyLocation\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport coil.compose.AsyncImage\nimport com.google.android.libraries.places.api.model.AutocompletePrediction\nimport com.google.places.android.ktx.demo.ui.DemoTheme\nimport dagger.hilt.android.AndroidEntryPoint\n\n/**\n * A demo activity showcasing the use of the Places KTX library for photo fetching.\n *\n * This activity demonstrates:\n * 1. Searching for places using [PlacesPhotoViewModel.searchResults].\n * 2. Fetching place details to obtain photo metadata.\n * 3. Resolving a photo URI using [com.google.android.libraries.places.api.net.kotlin.awaitFetchResolvedPhotoUri].\n * 4. Displaying the resolved photo URI using the Coil library.\n *\n * The UI is built using Jetpack Compose and follows a standard MVI-lite pattern with a ViewModel\n * exposing state via [StateFlow].\n */\n@AndroidEntryPoint\nclass PlacesPhotoDemoActivity : ComponentActivity() {\n\n    private val viewModel: PlacesPhotoViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContent {\n            DemoTheme {\n                PlacesPhotoScreen(\n                    viewModel = viewModel,\n                    onBackPressed = { finish() }\n                )\n            }\n        }\n    }\n}\n\n/**\n * The main screen for the Places Photo Demo.\n *\n * This composable manages the high-level state of the screen, switching between a search result\n * list and a detailed photo display.\n *\n * @param viewModel The ViewModel providing state and handling interactions.\n * @param onBackPressed Callback for when the user wants to navigate back.\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun PlacesPhotoScreen(\n    viewModel: PlacesPhotoViewModel,\n    onBackPressed: () -> Unit\n) {\n    // Collect the search results and photo state from the ViewModel.\n    // Using collectAsStateWithLifecycle ensures that collection stops when the app is in the background.\n    val searchEvent by viewModel.searchResults.collectAsStateWithLifecycle()\n    val photoState by viewModel.photoState.collectAsStateWithLifecycle()\n    \n    // searchQuery is local UI state used only for the text field input.\n    var searchQuery by rememberSaveable { mutableStateOf(\"\") }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(\"Places Photo Demo\") },\n                navigationIcon = {\n                    IconButton(onClick = onBackPressed) {\n                        Icon(\n                            Icons.AutoMirrored.Filled.ArrowBack,\n                            contentDescription = \"Back\"\n                        )\n                    }\n                }\n            )\n        }\n    ) { padding ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(padding)\n        ) {\n            // Search Input: Real-time search with a debounce applied in the ViewModel.\n            TextField(\n                value = searchQuery,\n                onValueChange = {\n                    searchQuery = it\n                    viewModel.onSearchQueryChanged(it)\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(16.dp),\n                placeholder = { Text(\"Search for a place with photos...\") },\n                leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },\n                trailingIcon = {\n                    IconButton(onClick = { \n                        searchQuery = \"\"\n                        viewModel.searchNearby() \n                    }) {\n                        Icon(\n                            imageVector = Icons.Default.MyLocation,\n                            contentDescription = \"Search Nearby\",\n                            tint = MaterialTheme.colorScheme.primary\n                        )\n                    }\n                },\n                shape = MaterialTheme.shapes.medium,\n                singleLine = true\n            )\n\n            // Content Area: Switch between search results and the photo display based on state.\n            Box(modifier = Modifier.fillMaxSize()) {\n                if (photoState.uri != null || photoState.isLoading || photoState.error != null) {\n                    // When a photo is being fetched or displayed, show the PhotoDisplay.\n                    PhotoDisplay(\n                        state = photoState,\n                        onBackPressed = { viewModel.onSearchQueryChanged(searchQuery) }\n                    )\n                } else {\n                    // Otherwise, show the interactive list of autocomplete predictions.\n                    SearchResultsList(\n                        event = searchEvent,\n                        onSearchNearbyClick = { \n                            searchQuery = \"\"\n                            viewModel.searchNearby() \n                        },\n                        onPredictionClick = { viewModel.onPredictionClicked(it) }\n                    )\n                }\n            }\n        }\n    }\n}\n\n/**\n * A prominent informational section shown when the app is in the idle state.\n *\n * This \"Hero\" card explicitly demonstrates that [com.google.android.libraries.places.api.net.kotlin.awaitSearchNearby]\n * is the modern, recommended replacement for the deprecated [PlacesClient.findCurrentPlace] API.\n */\n@Composable\nfun SearchNearbyHero(onSearchNearbyClick: () -> Unit) {\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n            .padding(24.dp),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center\n    ) {\n        Card(\n            modifier = Modifier.fillMaxWidth(),\n            colors = CardDefaults.cardColors(\n                containerColor = MaterialTheme.colorScheme.primaryContainer\n            )\n        ) {\n            Column(Modifier.padding(24.dp)) {\n                Text(\n                    \"Need Nearby Places?\",\n                    style = MaterialTheme.typography.headlineSmall,\n                    color = MaterialTheme.colorScheme.onPrimaryContainer\n                )\n                Spacer(Modifier.height(8.dp))\n                Text(\n                    \"The legacy 'findCurrentPlace' API has been removed. \" +\n                    \"Use 'searchNearby' with explicit location bounds instead.\",\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onPrimaryContainer\n                )\n                \n                Spacer(Modifier.height(24.dp))\n                \n                Button(\n                    onClick = onSearchNearbyClick,\n                    modifier = Modifier.fillMaxWidth(),\n                    contentPadding = PaddingValues(16.dp)\n                ) {\n                    Icon(Icons.Default.MyLocation, contentDescription = null)\n                    Spacer(Modifier.width(8.dp))\n                    Text(\"Search Near Googleplex\")\n                }\n            }\n        }\n        \n        Spacer(Modifier.height(32.dp))\n        \n        Text(\n            \"Or search for a specific place above\",\n            style = MaterialTheme.typography.labelLarge,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n    }\n}\n\n/**\n * Displays a list of autocomplete predictions as search results.\n *\n * @param event The current [PhotoDemoEvent] from the ViewModel.\n * @param onPredictionClick Callback when a prediction is tapped.\n */\n@Composable\nfun SearchResultsList(\n    event: PhotoDemoEvent,\n    onSearchNearbyClick: () -> Unit,\n    onPredictionClick: (AutocompletePrediction) -> Unit\n) {\n    when (event) {\n        is PhotoDemoEventIdle -> {\n            SearchNearbyHero(onSearchNearbyClick)\n        }\n        is PhotoDemoEventLoading -> {\n            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {\n                CircularProgressIndicator()\n            }\n        }\n        is PhotoDemoEventResults -> {\n            LazyColumn(\n                contentPadding = PaddingValues(16.dp),\n                verticalArrangement = Arrangement.spacedBy(8.dp)\n            ) {\n                items(event.predictions) { prediction ->\n                    Card(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .clickable { onPredictionClick(prediction) }\n                    ) {\n                        Column(Modifier.padding(16.dp)) {\n                            Text(prediction.getPrimaryText(null).toString(), fontWeight = FontWeight.Bold)\n                            Text(prediction.getSecondaryText(null).toString(), style = MaterialTheme.typography.bodySmall)\n                        }\n                    }\n                }\n            }\n        }\n        is PhotoDemoEventError -> {\n            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {\n                Text(\"Error: ${event.exception.message}\", color = MaterialTheme.colorScheme.error)\n            }\n        }\n    }\n}\n\n/**\n * Displays the resolved photo URI or the loading/error state during resolution.\n *\n * This component showcases the integration with Coil's [AsyncImage] to display the photo\n * once the URI is successfully fetched.\n *\n * @param state The current [PhotoState] containing the URI, loading status, or error.\n * @param onBackPressed Callback to return to search results.\n */\n@Composable\nfun PhotoDisplay(\n    state: PhotoState,\n    onBackPressed: () -> Unit\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n            .padding(16.dp),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center\n    ) {\n        if (state.isLoading) {\n            CircularProgressIndicator()\n            Spacer(Modifier.height(16.dp))\n            Text(\"Resolving Photo URI...\")\n        } else if (state.error != null) {\n            Text(\"Error\", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.error)\n            Text(state.error)\n            Button(onClick = onBackPressed, Modifier.padding(top = 16.dp)) {\n                Text(\"Back to Results\")\n            }\n        } else if (state.uri != null) {\n            Text(\"Fetched Photo URI\", style = MaterialTheme.typography.headlineSmall)\n            Text(\n                text = state.uri.toString(),\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.primary,\n                modifier = Modifier.padding(8.dp)\n            )\n            \n            Spacer(Modifier.height(16.dp))\n            \n            // This is the core of the demo: Using Coil's AsyncImage with the fetched URI.\n            // Coil handles the network fetching and caching of the actual image data from the URI.\n            AsyncImage(\n                model = state.uri,\n                contentDescription = \"Place Photo\",\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .height(300.dp)\n                    .background(Color.LightGray, MaterialTheme.shapes.medium),\n                contentScale = ContentScale.Crop\n            )\n\n            Button(onClick = onBackPressed, Modifier.padding(top = 24.dp)) {\n                Text(\"Search Another Photo\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/PlacesPhotoViewModel.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport android.net.Uri\nimport android.util.Log\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.android.libraries.places.api.model.AutocompletePrediction\nimport com.google.android.libraries.places.api.model.AutocompleteSessionToken\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.android.libraries.places.api.net.kotlin.awaitFetchPlace\nimport com.google.android.libraries.places.api.net.kotlin.awaitFetchResolvedPhotoUri\nimport com.google.android.libraries.places.api.net.kotlin.awaitFindAutocompletePredictions\nimport com.google.android.libraries.places.api.net.kotlin.awaitSearchNearby\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.FlowPreview\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.debounce\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.mapLatest\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.libraries.places.api.model.CircularBounds\nimport com.google.android.libraries.places.api.model.PhotoMetadata\nimport kotlinx.coroutines.CancellationException\nimport javax.inject.Inject\n\n/**\n * State definitions for the photo fetching demo search flow.\n */\nsealed interface PhotoDemoEvent\nobject PhotoDemoEventIdle : PhotoDemoEvent\nobject PhotoDemoEventLoading : PhotoDemoEvent\ndata class PhotoDemoEventResults(val predictions: List<AutocompletePrediction>) : PhotoDemoEvent\ndata class PhotoDemoEventError(val exception: Exception) : PhotoDemoEvent\n\n/**\n * State for the photo resolution and display phase.\n */\ndata class PhotoState(\n    val uri: Uri? = null,\n    val isLoading: Boolean = false,\n    val error: String? = null\n)\n\n/**\n * ViewModel for the Places Photo Demo.\n *\n * This ViewModel manages two main flows:\n * 1. An reactive search flow using [searchResults] which debounces user input and fetches predictions.\n * 2. A manual photo resolution flow triggered by [onPredictionClicked].\n *\n * It showcases the use of Places KTX suspending extensions like [awaitFindAutocompletePredictions],\n * [awaitFetchPlace], and [awaitFetchResolvedPhotoUri].\n */\n@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)\n@HiltViewModel\nclass PlacesPhotoViewModel @Inject constructor(\n    private val placesClient: PlacesClient\n) : ViewModel() {\n\n    // Internal state for the search query, used to drive the searchResults flow.\n    private val _searchQuery = MutableStateFlow(\"\")\n    \n    // An autocomplete session token used to group multiple requests into a single billing session.\n    private var sessionToken: AutocompleteSessionToken? = null\n\n    // State for the photo fetching phase.\n    private val _photoState = MutableStateFlow(PhotoState())\n    val photoState: StateFlow<PhotoState> = _photoState\n\n    /**\n     * A [StateFlow] exposing the search results based on the current query.\n     *\n     * This flow uses [debounce] to avoid flooding the API while the user is typing,\n     * and [mapLatest] to ensure that if a new search starts, the previous one is cancelled.\n     */\n    val searchResults: StateFlow<PhotoDemoEvent> = _searchQuery\n        .debounce(300)\n        .distinctUntilChanged()\n        .mapLatest { query ->\n            if (query.isBlank()) return@mapLatest PhotoDemoEventIdle\n\n            try {\n                // Initialize a session token if it doesn't exist for this specific search session.\n                if (sessionToken == null) {\n                    sessionToken = AutocompleteSessionToken.newInstance()\n                }\n\n                // Call the Places KTX suspending extension for autocomplete.\n                val response = placesClient.awaitFindAutocompletePredictions {\n                    sessionToken = this@PlacesPhotoViewModel.sessionToken\n                    this.query = query\n                }\n                PhotoDemoEventResults(response.autocompletePredictions)\n            } catch (e: Exception) {\n                // Standard coroutine cancellation must be propagated.\n                if (e is CancellationException) throw e\n                PhotoDemoEventError(e)\n            }\n        }\n        .stateIn(\n            scope = viewModelScope,\n            started = SharingStarted.WhileSubscribed(5000),\n            initialValue = PhotoDemoEventIdle\n        )\n\n    /**\n     * Updates the current search query and resets the photo state.\n     */\n    fun onSearchQueryChanged(query: String) {\n        _searchQuery.value = query\n        // Reset photo state when starting a new search or clearing the query.\n        _photoState.value = PhotoState()\n    }\n\n    /**\n     * Triggers the photo resolution flow for a specific prediction.\n     *\n     * This method demonstrates the multi-step process required to get a photo URI:\n     * 1. Fetch place details (specifically [Place.Field.PHOTO_METADATAS]).\n     * 2. Use the metadata to request a resolved photo URI.\n     *\n     * @param prediction The selected autocomplete prediction.\n     */\n    fun onPredictionClicked(prediction: AutocompletePrediction) {\n        viewModelScope.launch {\n            _photoState.value = PhotoState(isLoading = true)\n            try {\n                val currentToken = sessionToken\n                // 1. Fetch place details to get photo metadata.\n                // We request only the PHOTO_METADATAS field to minimize data usage.\n                val placeResponse = placesClient.awaitFetchPlace(\n                    prediction.placeId,\n                    listOf(Place.Field.PHOTO_METADATAS)\n                ) {\n                    sessionToken = currentToken\n                }\n\n                val metadata = placeResponse.place.photoMetadatas?.firstOrNull()\n                if (metadata == null) {\n                    _photoState.value = PhotoState(error = \"No photo metadata found for this place.\")\n                    return@launch\n                }\n\n                // 2. Fetch the resolved photo URI using the new KTX extension.\n                // This API returns a Uri that can be directly used by image loading libraries like Coil.\n                val photoResponse = placesClient.awaitFetchResolvedPhotoUri(metadata)\n                _photoState.value = PhotoState(uri = photoResponse.uri)\n\n                Log.d(\"PlacesPhotoViewModel\", \"Successfully fetched photo URI: ${photoResponse.uri}\")\n            } catch (e: Exception) {\n                if (e is CancellationException) throw e\n                Log.e(\"PlacesPhotoViewModel\", \"Error fetching photo\", e)\n                _photoState.value = PhotoState(error = \"Failed to fetch photo: ${e.message}\")\n            } finally {\n                // End the billing session by clearing the token after the final detail request.\n                sessionToken = null\n            }\n        }\n    }\n\n    /**\n     * Demonstrates the use of [com.google.android.libraries.places.api.net.kotlin.awaitSearchNearby] \n     * as a replacement for the removed [PlacesClient.findCurrentPlace] API.\n     *\n     * This implementation uses a fixed location (Googleplex) for demonstration purposes.\n     * In a real application, you would pass the user's current location here.\n     */\n    fun searchNearby() {\n        viewModelScope.launch {\n            _searchQuery.value = \"\" // Clear textual search when doing nearby search\n            _photoState.value = PhotoState(isLoading = true)\n            \n            try {\n                // Define a location restriction (e.g., 500m around Googleplex)\n                val googleplex = LatLng(37.4220656, -122.0840897)\n                val locationRestriction = CircularBounds.newInstance(googleplex, 500.0)\n                \n                // Call the Places KTX suspending extension for SearchNearby.\n                // We request only the ID and PHOTO_METADATAS fields.\n                val response = placesClient.awaitSearchNearby(\n                    locationRestriction,\n                    listOf(Place.Field.ID, Place.Field.PHOTO_METADATAS)\n                ) {\n                    // Optional: filter by types or adjust other parameters\n                    maxResultCount = 10\n                }\n\n                // For the demo, we take the first place found that has a photo.\n                val placeWithPhoto: Place? = response.places.firstOrNull { place: Place -> \n                    (place.photoMetadatas?.size ?: 0) > 0 \n                }\n                val metadata: PhotoMetadata? = placeWithPhoto?.photoMetadatas?.firstOrNull()\n\n                if (metadata == null) {\n                    _photoState.value = PhotoState(error = \"No nearby places with photos found.\")\n                    return@launch\n                }\n\n                // Resolve the photo URI\n                val photoResponse = placesClient.awaitFetchResolvedPhotoUri(metadata)\n                _photoState.value = PhotoState(uri = photoResponse.uri)\n                \n                Log.d(\"PlacesPhotoViewModel\", \"Successfully found nearby place and fetched photo URI: ${photoResponse.uri}\")\n            } catch (e: Exception) {\n                if (e is CancellationException) throw e\n                Log.e(\"PlacesPhotoViewModel\", \"Error searching nearby\", e)\n                _photoState.value = PhotoState(error = \"Failed to search nearby: ${e.message}\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/PlacesSearchDemoActivity.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.viewModels\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport com.google.android.libraries.places.api.model.AutocompletePrediction\nimport com.google.places.android.ktx.demo.ui.DemoTheme\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass PlacesSearchDemoActivity : ComponentActivity() {\n\n    private val viewModel: PlacesSearchViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContent {\n            DemoTheme {\n                PlacesSearchScreen(\n                    viewModel = viewModel,\n                    onBackPressed = { finish() }\n                )\n            }\n        }\n    }\n}\n\n@Composable\nfun PlacesSearchScreen(\n    viewModel: PlacesSearchViewModel,\n    onBackPressed: () -> Unit\n) {\n    val searchEvent by viewModel.searchEvents.collectAsStateWithLifecycle()\n    val transientError by viewModel.transientError.collectAsStateWithLifecycle()\n    var searchQuery by rememberSaveable { mutableStateOf(\"\") }\n    val snackbarHostState = remember { SnackbarHostState() }\n\n    // Show snackbar when a transient error occurs\n    LaunchedEffect(transientError) {\n        transientError?.let {\n            snackbarHostState.showSnackbar(it)\n            viewModel.clearTransientError()\n        }\n    }\n\n    @OptIn(ExperimentalMaterial3Api::class)\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = { Text(\"Search Places\") },\n                navigationIcon = {\n                    IconButton(onClick = onBackPressed) {\n                        Icon(\n                            Icons.AutoMirrored.Filled.ArrowBack,\n                            contentDescription = \"Back\"\n                        )\n                    }\n                }\n            )\n        },\n        snackbarHost = { SnackbarHost(snackbarHostState) }\n    ) { padding ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(padding)\n        ) {\n            SearchInput(\n                query = searchQuery,\n                onQueryChange = {\n                    searchQuery = it\n                    viewModel.onSearchQueryChanged(it)\n                }\n            )\n\n            HorizontalDivider(\n                modifier = Modifier.padding(horizontal = 16.dp),\n                color = MaterialTheme.colorScheme.outlineVariant\n            )\n\n            Box(modifier = Modifier.fillMaxSize()) {\n                when (val event = searchEvent) {\n                    is PlacesSearchEventIdle -> {\n                        Text(\n                            text = \"Start typing to search...\",\n                            modifier = Modifier.align(Alignment.Center),\n                            color = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                    is PlacesSearchEventLoading -> {\n                        CircularProgressIndicator(\n                            modifier = Modifier.align(Alignment.Center)\n                        )\n                    }\n                    is PlacesSearchEventError -> {\n                        Text(\n                            text = \"Error: ${event.exception.message}\",\n                            modifier = Modifier\n                                .align(Alignment.Center)\n                                .padding(24.dp),\n                            color = MaterialTheme.colorScheme.error\n                        )\n                    }\n                    is PlacesSearchEventFound -> {\n                        if (event.places.isEmpty()) {\n                            Text(\n                                text = \"No results found for \\\"$searchQuery\\\"\",\n                                modifier = Modifier.align(Alignment.Center),\n                                color = MaterialTheme.colorScheme.onSurfaceVariant\n                            )\n                        } else {\n                            LazyColumn(\n                                modifier = Modifier.fillMaxSize(),\n                                contentPadding = PaddingValues(16.dp),\n                                verticalArrangement = Arrangement.spacedBy(8.dp)\n                            ) {\n                                items(event.places) { prediction ->\n                                    PredictionCard(\n                                        prediction = prediction,\n                                        onClick = { viewModel.onAutocompletePredictionClicked(prediction) }\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun SearchInput(\n    query: String,\n    onQueryChange: (String) -> Unit\n) {\n    TextField(\n        value = query,\n        onValueChange = onQueryChange,\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(16.dp),\n        placeholder = { Text(\"Search for a place...\") },\n        leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },\n        colors = TextFieldDefaults.colors(\n            focusedIndicatorColor = Color.Transparent,\n            unfocusedIndicatorColor = Color.Transparent,\n            disabledIndicatorColor = Color.Transparent,\n            focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,\n            unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant\n        ),\n        shape = MaterialTheme.shapes.medium,\n        singleLine = true\n    )\n}\n\n@Composable\nfun PredictionCard(\n    prediction: AutocompletePrediction,\n    onClick: () -> Unit\n) {\n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable(onClick = onClick),\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surface\n        ),\n        border = CardDefaults.outlinedCardBorder()\n    ) {\n        Column(\n            modifier = Modifier.padding(16.dp)\n        ) {\n            Text(\n                text = prediction.getPrimaryText(null).toString(),\n                style = MaterialTheme.typography.bodyLarge,\n                fontWeight = FontWeight.Bold\n            )\n            Text(\n                text = prediction.getSecondaryText(null).toString(),\n                style = MaterialTheme.typography.bodySmall,\n                color = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/PlacesSearchEvent.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport com.google.android.libraries.places.api.model.AutocompletePrediction\n\nsealed interface PlacesSearchEvent\n\ndata object PlacesSearchEventIdle : PlacesSearchEvent\n\ndata object PlacesSearchEventLoading : PlacesSearchEvent\n\ndata class PlacesSearchEventError(\n    val exception: Throwable\n) : PlacesSearchEvent\n\ndata class PlacesSearchEventFound(\n    val places: List<AutocompletePrediction>\n) : PlacesSearchEvent\n"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/PlacesSearchViewModel.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo\n\nimport android.util.Log\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.libraries.places.api.model.AutocompletePrediction\nimport com.google.android.libraries.places.api.model.AutocompleteSessionToken\nimport com.google.android.libraries.places.api.model.LocationBias\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.model.PlaceTypes\nimport com.google.android.libraries.places.api.model.RectangularBounds\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.android.libraries.places.api.net.kotlin.awaitFetchPlace\nimport com.google.android.libraries.places.api.net.kotlin.awaitFindAutocompletePredictions\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.FlowPreview\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.debounce\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.mapLatest\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.CancellationException\nimport javax.inject.Inject\n\n@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)\n@HiltViewModel\nclass PlacesSearchViewModel @Inject constructor(\n    private val placesClient: PlacesClient\n) : ViewModel() {\n\n    private val _searchQuery = MutableStateFlow(\"\")\n    private var sessionToken: AutocompleteSessionToken? = null\n\n    /**\n     * Exposes a StateFlow of [PlacesSearchEvent] based on the current search query.\n     * Uses [debounce(300)] to strike a balance between real-time feedback and\n     * minimizing redundant network calls (and costs) while the user is typing.\n     */\n    val searchEvents: StateFlow<PlacesSearchEvent> = _searchQuery\n        .debounce(300)\n        .distinctUntilChanged()\n        .mapLatest { query ->\n            if (query.isBlank()) {\n                return@mapLatest PlacesSearchEventIdle\n            }\n\n            try {\n                val bias: LocationBias = RectangularBounds.newInstance(\n                    LatLng(37.7576948, -122.4727051), // SW lat, lng\n                    LatLng(37.808300, -122.391338) // NE lat, lng\n                )\n\n                // Using a session token to group autocomplete queries and place fetches for billing\n                if (sessionToken == null) {\n                    sessionToken = AutocompleteSessionToken.newInstance()\n                }\n\n                // Using the official SDK-provided awaitFindAutocompletePredictions extension\n                val response = placesClient.awaitFindAutocompletePredictions {\n                    locationBias = bias\n                    typesFilter = listOf(PlaceTypes.ESTABLISHMENT)\n                    sessionToken = this@PlacesSearchViewModel.sessionToken\n                    this.query = query\n                    countries = listOf(\"US\")\n                }\n\n                PlacesSearchEventFound(response.autocompletePredictions)\n            } catch (e: Exception) {\n                if (e is CancellationException) throw e\n                PlacesSearchEventError(e)\n            }\n        }\n        .stateIn(\n            scope = viewModelScope,\n            started = SharingStarted.WhileSubscribed(5000),\n            initialValue = PlacesSearchEventIdle\n        )\n\n    private val _transientError = MutableStateFlow<String?>(null)\n    val transientError: StateFlow<String?> = _transientError\n\n    fun onSearchQueryChanged(query: String) {\n        _searchQuery.value = query\n    }\n\n    fun onAutocompletePredictionClicked(prediction: AutocompletePrediction) {\n        viewModelScope.launch {\n            try {\n                val currentToken = sessionToken\n                // Using the official SDK-provided awaitFetchPlace extension\n                val response = placesClient.awaitFetchPlace(\n                    prediction.placeId,\n                    listOf(\n                        Place.Field.DISPLAY_NAME,\n                        Place.Field.FORMATTED_ADDRESS,\n                        Place.Field.LOCATION,\n                        Place.Field.BUSINESS_STATUS\n                    )\n                ) {\n                    sessionToken = currentToken\n                }\n\n                Log.d(\"PlacesSearchViewModel\", \"Got place ${response.place}\")\n            } catch (e: Exception) {\n                if (e is CancellationException) throw e\n                Log.e(\"PlacesSearchViewModel\", \"Error fetching place details\", e)\n                _transientError.value = \"Failed to fetch details for ${prediction.getPrimaryText(null)}\"\n            } finally {\n                // Success or failure! Reset session token after a selection to start a new billing session\n                sessionToken = null\n            }\n        }\n    }\n\n    fun clearTransientError() {\n        _transientError.value = null\n    }\n}"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/inject/DemoModule.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo.inject\n\nimport android.content.Context\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject DemoModule {\n\n    @Singleton\n    @Provides\n    fun providePlacesClient(@ApplicationContext context: Context): PlacesClient =\n        Places.createClient(context)\n}"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/ui/Color.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo.ui\n\nimport androidx.compose.ui.graphics.Color\n\n// Premium dark-themed palette\nval Primary = Color(0xFFD0BCFF)\nval Secondary = Color(0xFFCCC2DC)\nval Tertiary = Color(0xFFEFB8C8)\nval Background = Color(0xFF1C1B1F)\nval Surface = Color(0xFF49454F)\nval OnPrimary = Color(0xFF381E72)\nval OnSecondary = Color(0xFF332D41)\nval OnTertiary = Color(0xFF492532)\nval OnBackground = Color(0xFFE6E1E5)\nval OnSurface = Color(0xFFE6E1E5)\n\n// Light-themed palette for compatibility\nval PrimaryLight = Color(0xFF6750A4)\nval SecondaryLight = Color(0xFF625B71)\nval TertiaryLight = Color(0xFF7D5260)\nval BackgroundLight = Color(0xFFFFFBFE)\nval SurfaceLight = Color(0xFFFFFBFE)\nval OnPrimaryLight = Color(0xFFFFFFFF)\nval OnSecondaryLight = Color(0xFFFFFFFF)\nval OnTertiaryLight = Color(0xFFFFFFFF)\nval OnBackgroundLight = Color(0xFF1C1B1F)\nval OnSurfaceLight = Color(0xFF1C1B1F)\n"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/ui/Theme.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo.ui\n\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\n\nprivate val DarkColorScheme = darkColorScheme(\n    primary = Primary,\n    secondary = Secondary,\n    tertiary = Tertiary,\n    background = Background,\n    surface = Surface,\n    onPrimary = OnPrimary,\n    onSecondary = OnSecondary,\n    onTertiary = OnTertiary,\n    onBackground = OnBackground,\n    onSurface = OnSurface\n)\n\nprivate val LightColorScheme = lightColorScheme(\n    primary = PrimaryLight,\n    secondary = SecondaryLight,\n    tertiary = TertiaryLight,\n    background = BackgroundLight,\n    surface = SurfaceLight,\n    onPrimary = OnPrimaryLight,\n    onSecondary = OnSecondaryLight,\n    onTertiary = OnTertiaryLight,\n    onBackground = OnBackgroundLight,\n    onSurface = OnSurfaceLight\n)\n\n@Composable\nfun DemoTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    content: @Composable () -> Unit\n) {\n    val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}\n"
  },
  {
    "path": "kotlin-demos/src/main/java/com/google/places/android/ktx/demo/ui/Type.kt",
    "content": "// Copyright 2026 Google LLC\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.places.android.ktx.demo.ui\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\nval Typography = Typography(\n    headlineLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Bold,\n        fontSize = 32.sp,\n        lineHeight = 40.sp,\n        letterSpacing = 0.sp\n    ),\n    headlineMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Bold,\n        fontSize = 28.sp,\n        lineHeight = 36.sp,\n        letterSpacing = 0.sp\n    ),\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.SemiBold,\n        fontSize = 22.sp,\n        lineHeight = 28.sp,\n        letterSpacing = 0.sp\n    ),\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    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": "kotlin-demos/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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=\"#008577\"\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": "kotlin-demos/src/main/res/drawable/ic_launcher_foreground.xml",
    "content": "<!--\n Copyright 2026 Google LLC\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<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=\"108\"\n    android:viewportHeight=\"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:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\">\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:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>\n"
  },
  {
    "path": "kotlin-demos/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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": "kotlin-demos/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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": "kotlin-demos/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<resources>\n    <color name=\"colorPrimary\">#008577</color>\n    <color name=\"colorPrimaryDark\">#00574B</color>\n    <color name=\"colorSecondaryVariant\">#0097a7</color>\n    <color name=\"colorAccent\">#D81B60</color>\n    <color name=\"colorGray\">#dedede</color>\n</resources>\n"
  },
  {
    "path": "kotlin-demos/src/main/res/values/strings.xml",
    "content": "<!--\n Copyright 2026 Google LLC\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<resources>\n    <string name=\"app_name\">Android Places KTX Demo</string>\n    <string name=\"places_demo_title\">Places Search Demo</string>\n    <string name=\"places_demo_description\">Demonstrates usages of Places KTX coroutines and DSL builder features.</string>\n\n    <string name=\"autocomplete_fragment_demo_title\">Autocomplete Widget Demo</string>\n    <string name=\"autocomplete_fragment_demo_description\">Demonstrates using the Autocomplete Widget (Intent) via the Places SDK.</string>\n    <string name=\"places_photo_demo_title\">Places Photo Demo</string>\n    <string name=\"places_photo_demo_description\">Demonstrates fetching and displaying place photos using awaitFetchResolvedPhotoUri and Coil.</string>\n</resources>\n"
  },
  {
    "path": "kotlin-demos/src/main/res/values/styles.xml",
    "content": "<!--\n Copyright 2026 Google LLC\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<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.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": "local.defaults.properties",
    "content": "# ==============================================================================\n# Required Keys\n# These keys are mandatory. A missing or invalid key will fail the build process.\n# ==============================================================================\nPLACES_API_KEY=YOUR_API_KEY\nMAPS_API_KEY=YOUR_API_KEY\n\n# ==============================================================================\n# Optional Keys\n# These keys are not strictly required to build the repository, but certain \n# demos will fail at runtime if the key is not provided.\n# ==============================================================================\n\n# MAP_ID: If missing, the 'PlaceDetailsCompose' demo will fail to load custom map styling at runtime.\nMAP_ID=YOUR_MAP_ID\n\n# MAPS3D_API_KEY: If missing, the 'PlacesUIKit3D' demo will fail to load 3D maps at runtime.\nMAPS3D_API_KEY=YOUR_API_KEY\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "/*\n * Copyright 2026 Google LLC\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\npluginManagement {\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}\n\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nincludeBuild(\"build-logic\")\nrootProject.name = \"Android Places Demos\"\n\ninclude(\":PlaceDetailsCompose\")\ninclude(\":PlaceDetailsUIKit\")\ninclude(\":PlacesUIKit3D\")\ninclude(\":demo-java\")\ninclude(\":demo-kotlin\")\ninclude(\":kotlin-demos\")\ninclude(\":snippets\")\n"
  },
  {
    "path": "snippets/.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\n"
  },
  {
    "path": "snippets/build.gradle.kts",
    "content": "/*\n * Copyright 2026 Google LLC\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\nplugins {\n    id(\"places-demo.android.application\")\n    alias(libs.plugins.jetbrains.kotlin.android)\n    id(\"places-demo.secrets\")\n}\n\nandroid {\n    namespace = \"com.google.places\"\n\n    defaultConfig {\n        applicationId = \"com.google.places\"\n        versionCode = 1\n        versionName = \"1.0\"\n\n        multiDexEnabled = true\n    }\n\n    buildFeatures {\n        viewBinding = true\n        buildConfig = true\n    }\n\n    kotlin {\n        compilerOptions {\n            freeCompilerArgs.addAll(\n                \"-opt-in=kotlin.RequiresOptIn\",\n                \"-Xannotation-default-target=param-property\"\n            )\n        }\n    }\n}\n\ndemoApp {\n    mainActivity.set(\".kotlin.KotlinMainActivity\")\n}\n\n// [START maps_android_places_install_snippet]\ndependencies {\n    // [START_EXCLUDE silent]\n    implementation(libs.constraintlayout)\n    implementation(libs.activity)\n    implementation(libs.fragment)\n    implementation(libs.navigation.fragment)\n    implementation(libs.navigation.ui)\n    implementation(libs.core.ktx)\n\n    implementation(libs.appcompat)\n    implementation(libs.material)\n\n    implementation(libs.volley)\n    implementation(libs.glide)\n    implementation(libs.viewbinding)\n    implementation(libs.multidex)\n    // [END_EXCLUDE]\n\n    // Places and Maps SDKs\n    // [START maps_android_places_upgrade_snippet]\n    implementation(\"com.google.android.libraries.places:places:5.1.1\")\n    // [END maps_android_places_upgrade_snippet]\n}\n// [END maps_android_places_install_snippet]\n\n\n"
  },
  {
    "path": "snippets/local.defaults.properties",
    "content": "PLACES_API_KEY=DEFAULT_API_KEY\n"
  },
  {
    "path": "snippets/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"
  },
  {
    "path": "snippets/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\"/>\n\n    <application\n        android:name=\".kotlin.MainApplication\"\n        android:allowBackup=\"true\"\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/AppTheme\">\n\n        <!--\n        This is a demo application and all activities are exported so that they can be individually\n        launched from Android Studio. In a production application, it is best practice to only export\n        activities that are intended to be launched by other applications.\n        -->\n\n        <activity\n            android:name=\".kotlin.KotlinMainActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/main_activity_title\">\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        <activity\n            android:name=\".JavaMainActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/main_activity_title\" />\n        <activity\n            android:name=\".PlaceIsOpenActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/place_is_open\" />\n        <activity\n            android:name=\".CurrentPlaceActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/current_place_title\" />\n        <activity\n            android:name=\".PlaceAutocompleteActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/place_autocomplete_title\" />\n        <activity\n            android:name=\".PlaceDetailsActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/place_details_title\" />\n        <activity\n            android:name=\".PlacePhotosActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/place_photos_title\" />\n        <activity\n            android:name=\".PlacesIconActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/places_icon_title\" />\n        <activity\n            android:name=\".kotlin.CurrentPlaceActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/current_place_title\" />\n        <activity\n            android:name=\".kotlin.PlaceAutocompleteActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/place_autocomplete_title\" />\n        <activity\n            android:name=\".kotlin.PlaceDetailsActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/place_details_title\" />\n        <activity\n            android:name=\".kotlin.PlacePhotosActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/place_photos_title\" />\n        <activity\n            android:name=\".kotlin.PlacesIconActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/places_icon_title\" />\n        <activity\n            android:name=\".kotlin.PlaceIsOpenActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/place_is_open\"\n            />\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/CurrentPlaceActivity.java",
    "content": "// Copyright 2023 Google LLC\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.places;\n\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.content.ContextCompat;\nimport androidx.core.view.WindowCompat;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.google.android.gms.common.api.ApiException;\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.libraries.places.api.model.LocationRestriction;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.model.PlaceLikelihood;\nimport com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;\nimport com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;\nimport com.google.android.libraries.places.api.net.PlacesClient;\nimport com.google.android.libraries.places.api.net.SearchNearbyRequest;\nimport com.google.places.databinding.ActivityCurrentPlaceBinding;\nimport com.google.places.databinding.ListItemPlaceBinding;\nimport com.google.places.kotlin.MainApplication;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Locale;\n\nimport static android.Manifest.permission.ACCESS_FINE_LOCATION;\n\npublic class CurrentPlaceActivity extends AppCompatActivity {\n\n    private ActivityCurrentPlaceBinding binding;\n    private PlacesClient placesClient;\n    private PlacesAdapter adapter;\n\n    private final ActivityResultLauncher<String> requestPermissionLauncher =\n            registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {\n                if (isGranted) {\n                    findCurrentPlace();\n                } else {\n                    // Handle permission denied\n                }\n            });\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);\n        binding = ActivityCurrentPlaceBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        setSupportActionBar(binding.toolbar);\n        if (getSupportActionBar() != null) {\n            getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n            getSupportActionBar().setTitle(getTitle() + \" (Java)\");\n        }\n\n        placesClient = ((MainApplication) getApplication()).getPlacesClient();\n        binding.currentPlaceButton.setOnClickListener(v -> findCurrentPlace());\n\n        // Set up the RecyclerView\n        adapter = new PlacesAdapter();\n        binding.placesRecyclerView.setLayoutManager(new LinearLayoutManager(this));\n        binding.placesRecyclerView.setAdapter(adapter);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == android.R.id.home) {\n            finish();\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private void findCurrentPlace() {\n        binding.progressBar.setVisibility(View.VISIBLE);\n\n        // [START maps_places_current_place]\n        // Use fields to define the data types to return.\n        List<Place.Field> placeFields = Arrays.asList(Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS, Place.Field.LOCATION);\n\n        // Use the builder to create a FindCurrentPlaceRequest.\n        FindCurrentPlaceRequest request = FindCurrentPlaceRequest.newInstance(placeFields);\n\n        // Call findCurrentPlace and handle the response (first check that the user has granted permission).\n        if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {\n            Task<FindCurrentPlaceResponse> placeResponse = placesClient.findCurrentPlace(request);\n            placeResponse.addOnCompleteListener(task -> {\n                binding.progressBar.setVisibility(View.GONE);\n                if (task.isSuccessful()) {\n                    FindCurrentPlaceResponse response = task.getResult();\n                    adapter.setPlaceLikelihoods(response.getPlaceLikelihoods());\n                } else {\n                    Exception exception = task.getException();\n                    if (exception instanceof ApiException apiException) {\n                        Log.e(\"CurrentPlace\", \"Place not found: \" + apiException.getStatusCode());\n                    }\n                }\n            });\n        } else {\n            // [START_EXCLUDE silent]\n            binding.progressBar.setVisibility(View.GONE);\n            // [END_EXCLUDE]\n            // A local method to request required permissions;\n            // See https://developer.android.com/training/permissions/requesting\n            getLocationPermission();\n        }\n        // [END maps_places_current_place]\n    }\n\n    private void getLocationPermission() {\n        requestPermissionLauncher.launch(ACCESS_FINE_LOCATION);\n    }\n\n    // Adapter for the RecyclerView\n    private static class PlacesAdapter extends RecyclerView.Adapter<PlacesAdapter.ViewHolder> {\n\n        private List<PlaceLikelihood> placeLikelihoods = new ArrayList<>();\n\n        @NonNull\n        @Override\n        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {\n            ListItemPlaceBinding binding = ListItemPlaceBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);\n            return new ViewHolder(binding);\n        }\n\n        @Override\n        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {\n            PlaceLikelihood placeLikelihood = placeLikelihoods.get(position);\n            holder.bind(placeLikelihood);\n        }\n\n        @Override\n        public int getItemCount() {\n            return placeLikelihoods.size();\n        }\n\n        public void setPlaceLikelihoods(List<PlaceLikelihood> placeLikelihoods) {\n            this.placeLikelihoods = placeLikelihoods;\n            notifyDataSetChanged();\n        }\n\n        static class ViewHolder extends RecyclerView.ViewHolder {\n            private final ListItemPlaceBinding binding;\n\n            ViewHolder(ListItemPlaceBinding binding) {\n                super(binding.getRoot());\n                this.binding = binding;\n            }\n\n            void bind(PlaceLikelihood placeLikelihood) {\n                Place place = placeLikelihood.getPlace();\n                binding.placeName.setText(place.getDisplayName());\n                binding.placeAddress.setText(place.getFormattedAddress());\n                binding.placeLikelihood.setText(String.format(Locale.getDefault(), \"Likelihood: %.2f\", placeLikelihood.getLikelihood()));\n            }\n        }\n    }\n}"
  },
  {
    "path": "snippets/src/main/java/com/google/places/GetStartedActivity.java",
    "content": "// Copyright 2020 Google LLC\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.places;\n\nimport android.os.Bundle;\nimport android.widget.Toast;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport com.google.android.libraries.places.api.Places;\nimport com.google.android.libraries.places.api.net.PlacesClient;\n\npublic class GetStartedActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        final String apiKey = BuildConfig.PLACES_API_KEY;\n        if (apiKey.equals(\"DEFAULT_API_KEY\")) {\n            Toast.makeText(this, \"PLACES_API_KEY has not been configured\", Toast.LENGTH_SHORT).show();\n            finish();\n            return;\n        }\n\n        // [START maps_places_get_started]\n        // Initialize the SDK\n        Places.initialize(getApplicationContext(), apiKey);\n\n        // Create a new PlacesClient instance\n        PlacesClient placesClient = Places.createClient(this);\n        // [END maps_places_get_started]\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/JavaMainActivity.java",
    "content": "// Copyright 2026 Google LLC\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.places;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.ViewGroup;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.graphics.Insets;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.view.WindowCompat;\nimport androidx.core.view.WindowInsetsCompat;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport com.google.places.databinding.ActivityMainBinding;\nimport com.google.places.databinding.ListItemActivityBinding;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\n\npublic class JavaMainActivity extends AppCompatActivity {\n\n    private ActivityMainBinding binding;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Enable edge-to-edge display to draw behind the system bars for a more immersive experience.\n        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);\n\n        binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        setSupportActionBar(binding.toolbar);\n        Objects.requireNonNull(getSupportActionBar()).setTitle(getTitle() + \" (Java)\");\n\n        // Apply window insets to the root view.\n        ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, windowInsets) -> {\n            Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());\n            v.setPadding(insets.left, insets.top, insets.right, insets.bottom);\n            return WindowInsetsCompat.CONSUMED;\n        });\n\n        List<ActivityInfo> activities = Arrays.asList(\n            new ActivityInfo(\n                \"Current Place\",\n                getString(R.string.current_place_description),\n                CurrentPlaceActivity.class\n            ),\n            new ActivityInfo(\n                \"Place Autocomplete\",\n                getString(R.string.place_autocomplete_description),\n                PlaceAutocompleteActivity.class\n            ),\n            new ActivityInfo(\n                \"Place Details\",\n                getString(R.string.place_details_description),\n                PlaceDetailsActivity.class\n            ),\n            new ActivityInfo(\n                \"Place Photos\",\n                getString(R.string.place_photos_description),\n                PlacePhotosActivity.class\n            ),\n            new ActivityInfo(\n                \"Places Icon\",\n                getString(R.string.places_icon_description),\n                PlacesIconActivity.class\n            ),\n            new ActivityInfo(\n                \"Place Is Open\",\n                getString(R.string.place_is_open_description),\n                PlaceIsOpenActivity.class\n            )\n        );\n\n        binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));\n        binding.recyclerView.setAdapter(new ActivitiesAdapter(activities));\n    }\n\n    private static class ActivityInfo {\n        private final String name;\n        private final String description;\n        private final Class<? extends AppCompatActivity> activityClass;\n\n        ActivityInfo(String name, String description, Class<? extends AppCompatActivity> activityClass) {\n            this.name = name;\n            this.description = description;\n            this.activityClass = activityClass;\n        }\n    }\n\n    private static class ActivitiesAdapter extends RecyclerView.Adapter<ActivitiesAdapter.ViewHolder> {\n\n        private final List<ActivityInfo> activities;\n\n        ActivitiesAdapter(List<ActivityInfo> activities) {\n            this.activities = activities;\n        }\n\n        @NonNull\n        @Override\n        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {\n            ListItemActivityBinding binding = ListItemActivityBinding.inflate(\n                LayoutInflater.from(parent.getContext()),\n                parent,\n                false\n            );\n            return new ViewHolder(binding);\n        }\n\n        @Override\n        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {\n            ActivityInfo activityInfo = activities.get(position);\n            holder.binding.activityName.setText(activityInfo.name);\n            holder.binding.activityDescription.setText(activityInfo.description);\n            holder.itemView.setOnClickListener(v -> {\n                Intent intent = new Intent(v.getContext(), activityInfo.activityClass);\n                v.getContext().startActivity(intent);\n            });\n        }\n\n        @Override\n        public int getItemCount() {\n            return activities.size();\n        }\n\n        static class ViewHolder extends RecyclerView.ViewHolder {\n            private final ListItemActivityBinding binding;\n\n            ViewHolder(ListItemActivityBinding binding) {\n                super(binding.getRoot());\n                this.binding = binding;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/PlaceAutocompleteActivity.java",
    "content": "// Copyright 2020 Google LLC\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.places;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.MenuItem;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.view.WindowCompat;\n\nimport com.google.android.gms.common.api.ApiException;\nimport com.google.android.gms.common.api.Status;\nimport com.google.android.gms.maps.model.LatLng;\nimport com.google.android.libraries.places.api.model.AutocompletePrediction;\nimport com.google.android.libraries.places.api.model.AutocompleteSessionToken;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.model.PlaceTypes;\nimport com.google.android.libraries.places.api.model.RectangularBounds;\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest;\nimport com.google.android.libraries.places.api.net.PlacesClient;\nimport com.google.android.libraries.places.widget.Autocomplete;\nimport com.google.android.libraries.places.widget.AutocompleteSupportFragment;\nimport com.google.android.libraries.places.widget.listener.PlaceSelectionListener;\nimport com.google.android.libraries.places.widget.model.AutocompleteActivityMode;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport com.google.places.databinding.ActivityPlaceAutocompleteBinding;\nimport com.google.places.kotlin.MainApplication;\n\npublic class PlaceAutocompleteActivity extends AppCompatActivity {\n    private static final String TAG = PlaceAutocompleteActivity.class.getSimpleName();\n\n    private PlacesClient placesClient;\n    private ActivityPlaceAutocompleteBinding binding;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Enable edge-to-edge display.\n        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);\n\n        binding = ActivityPlaceAutocompleteBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        setSupportActionBar(binding.toolbar);\n        if (getSupportActionBar() != null) {\n            getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n            getSupportActionBar().setTitle(getTitle() + \" (Java)\");\n        }\n\n        placesClient = ((MainApplication) getApplication()).getPlacesClient();\n\n        binding.useRestrictionSwitch.setOnCheckedChangeListener(\n                (buttonView, isChecked) -> initAutocompleteSupportFragment()\n        );\n        initAutocompleteSupportFragment();\n        binding.autocompleteIntentButton.setOnClickListener(v -> startAutocompleteIntent());\n        binding.programmaticAutocompleteButton.setOnClickListener(\n                v -> programmaticPlacePredictions(binding.autocompleteQuery.getText().toString())\n        );\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == android.R.id.home) {\n            finish();\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private void initAutocompleteSupportFragment() {\n        // [START maps_places_autocomplete_support_fragment]\n        // Initialize the AutocompleteSupportFragment.\n        AutocompleteSupportFragment autocompleteFragment = (AutocompleteSupportFragment)\n                getSupportFragmentManager().findFragmentById(R.id.autocomplete_fragment);\n\n        // Specify the types of place data to return.\n        assert autocompleteFragment != null;\n        autocompleteFragment.setPlaceFields(Arrays.asList(Place.Field.ID, Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS));\n\n        // Set up a PlaceSelectionListener to handle the response.\n        autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {\n            @Override\n            public void onPlaceSelected(@NonNull Place place) {\n                binding.autocompleteResult.setText(\n                        getString(\n                                R.string.place_selection,\n                                place.getDisplayName(),\n                                place.getId(),\n                                place.getFormattedAddress()\n                        )\n                );\n                Log.i(TAG, \"Place: \" + place.getDisplayName() + \", \" + place.getId());\n            }\n\n\n            @Override\n            public void onError(@NonNull Status status) {\n                binding.autocompleteResult.setText(getString(R.string.an_error_occurred, status));\n                Log.e(TAG, \"An error occurred: \" + status);\n            }\n        });\n        // [END maps_places_autocomplete_support_fragment]\n\n        // Since we are reusing the AutocompleteSupportFragment, we need to remove the previous restrictions\n        autocompleteFragment.setLocationBias(null);\n        autocompleteFragment.setLocationRestriction(null);\n\n        if (binding.useRestrictionSwitch.isChecked()) {\n            // [START maps_places_autocomplete_location_restriction]\n            autocompleteFragment.setLocationRestriction(\n                    RectangularBounds.newInstance(\n                            new LatLng(-33.880490, 151.184363),\n                            new LatLng(-33.858754, 151.229596)\n                    )\n            );\n            // [END maps_places_autocomplete_location_restriction]\n        } else {\n            // [START maps_places_autocomplete_location_bias]\n            autocompleteFragment.setLocationBias(\n                    RectangularBounds.newInstance(\n                            new LatLng(-33.880490, 151.184363),\n                            new LatLng(-33.858754, 151.229596)\n                    )\n            );\n            // [END maps_places_autocomplete_location_bias]\n        }\n\n        // [START maps_places_autocomplete_type_filter]\n        autocompleteFragment.setTypesFilter(List.of(PlaceTypes.ESTABLISHMENT));\n        // [END maps_places_autocomplete_type_filter]\n\n        // [START maps_places_autocomplete_type_filter_multiple]\n        autocompleteFragment.setTypesFilter(List.of(\"landmark\", \"restaurant\", \"store\"));\n        // [END maps_places_autocomplete_type_filter_multiple]\n\n        // [START maps_places_autocomplete_country_filter]\n        autocompleteFragment.setCountries(\"AU\", \"NZ\");\n        // [END maps_places_autocomplete_country_filter]\n    }\n\n    // [START maps_places_autocomplete_intent]\n\n    // [START_EXCLUDE silent]\n    private void startAutocompleteIntent() {\n        // [END_EXCLUDE]\n        // Set the fields to specify which types of place data to\n        // return after the user has made a selection.\n        List<Place.Field> fields = Arrays.asList(Place.Field.ID, Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS);\n\n        // Start the autocomplete intent.\n        // [START maps_places_intent_type_filter]\n        Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.FULLSCREEN, fields)\n                .setTypesFilter(List.of(PlaceTypes.ESTABLISHMENT))\n                .build(this);\n        // [END maps_places_intent_type_filter]\n        startAutocomplete.launch(intent);\n        // [END maps_places_autocomplete_intent]\n    }\n\n    // [START maps_places_on_activity_result]\n    private final ActivityResultLauncher<Intent> startAutocomplete = registerForActivityResult(\n            new ActivityResultContracts.StartActivityForResult(),\n            result -> {\n                if (result.getResultCode() == Activity.RESULT_OK) {\n                    Intent intent = result.getData();\n                    if (intent != null) {\n                        Place place = Autocomplete.getPlaceFromIntent(intent);\n                        binding.autocompleteResult.setText(\n                                getString(\n                                        R.string.place_selection,\n                                        place.getDisplayName(),\n                                        place.getId(),\n                                        place.getFormattedAddress()\n                                )\n                        );\n                        Log.i(TAG, \"Place: \" + place.getDisplayName() + \", \" + place.getId());\n                    }\n                } else if (result.getResultCode() == Activity.RESULT_CANCELED) {\n                    // The user canceled the operation.\n                    binding.autocompleteResult.setText(R.string.user_canceled_autocomplete);\n                    Log.i(TAG, \"User canceled autocomplete\");\n                }\n            });\n    // [END maps_places_on_activity_result]\n\n    private void programmaticPlacePredictions(String query) {\n        // [START maps_places_programmatic_place_predictions]\n        // Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest,\n        // and once again when the user makes a selection (for example when calling fetchPlace()).\n        AutocompleteSessionToken token = AutocompleteSessionToken.newInstance();\n\n        // Create a RectangularBounds object.\n        RectangularBounds bounds = RectangularBounds.newInstance(\n                new LatLng(-33.880490, 151.184363),\n                new LatLng(-33.858754, 151.229596));\n        // Use the builder to create a FindAutocompletePredictionsRequest.\n        FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()\n                // Call either setLocationBias() OR setLocationRestriction().\n                .setLocationBias(bounds)\n                //.setLocationRestriction(bounds)\n                .setOrigin(new LatLng(-33.8749937, 151.2041382))\n                .setCountries(\"AU\", \"NZ\")\n                .setTypesFilter(List.of(PlaceTypes.ESTABLISHMENT))\n                .setSessionToken(token)\n                .setQuery(query)\n                .build();\n\n        placesClient.findAutocompletePredictions(request).addOnSuccessListener((response) -> {\n            StringBuilder builder = new StringBuilder();\n            for (AutocompletePrediction prediction : response.getAutocompletePredictions()) {\n                builder.append(prediction.getPrimaryText(null).toString()).append(\"\\n\");\n                Log.i(TAG, prediction.getPlaceId());\n                Log.i(TAG, prediction.getPrimaryText(null).toString());\n            }\n            binding.autocompleteResult.setText(builder.toString());\n        }).addOnFailureListener((exception) -> {\n            if (exception instanceof ApiException apiException) {\n                Log.e(TAG, \"Place not found: \" + apiException.getStatusCode());\n                binding.autocompleteResult.setText(getString(R.string.place_not_found, apiException.getMessage()));\n            }\n        });\n        // [END maps_places_programmatic_place_predictions]\n    }\n}"
  },
  {
    "path": "snippets/src/main/java/com/google/places/PlaceDetailsActivity.java",
    "content": "// Copyright 2023 Google LLC\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.places;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.MenuItem;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.view.WindowCompat;\n\nimport com.google.android.gms.common.api.ApiException;\nimport com.google.android.gms.maps.model.LatLng;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest;\nimport com.google.android.libraries.places.api.net.PlacesClient;\nimport com.google.places.data.PlaceIdProvider;\nimport com.google.places.databinding.ActivityPlaceDetailsBinding;\nimport com.google.places.kotlin.MainApplication;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class PlaceDetailsActivity extends AppCompatActivity {\n\n    private static final String TAG = PlaceDetailsActivity.class.getSimpleName();\n    private PlacesClient placesClient;\n    private ActivityPlaceDetailsBinding binding;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Enable edge-to-edge display.\n        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);\n\n        binding = ActivityPlaceDetailsBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        setSupportActionBar(binding.toolbar);\n        if (getSupportActionBar() != null) {\n            getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n            getSupportActionBar().setTitle(getTitle() + \" (Java)\");\n        }\n\n        placesClient = ((MainApplication) getApplication()).getPlacesClient();\n\n        getPlaceById();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == android.R.id.home) {\n            finish();\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private void getPlaceById() {\n        // [START maps_places_get_place_by_id]\n        // Define a Place ID.\n        final String placeId = PlaceIdProvider.getRandomPlaceId();\n\n        // Specify the fields to return.\n        final List<Place.Field> placeFields =\n                Arrays.asList(\n                        Place.Field.ID,\n                        Place.Field.DISPLAY_NAME,\n                        Place.Field.FORMATTED_ADDRESS,\n                        Place.Field.LOCATION\n                );\n\n        // Construct a request object, passing the place ID and fields array.\n        final FetchPlaceRequest request = FetchPlaceRequest.newInstance(placeId, placeFields);\n\n        placesClient.fetchPlace(request).addOnSuccessListener((response) -> {\n            Place place = response.getPlace();\n\n            // [START maps_places_place_details_simple]\n            final CharSequence name = place.getDisplayName();\n            final CharSequence address = place.getFormattedAddress();\n            final LatLng location = place.getLocation();\n            // [END maps_places_place_details_simple]\n\n            binding.placeName.setText(name);\n            binding.placeAddress.setText(address);\n            if (location != null) {\n                binding.placeLocation.setText(\n                        getString(R.string.place_location, location.latitude, location.longitude)\n                );\n            } else {\n                binding.placeLocation.setText(null);\n            }\n\n            Log.i(TAG, \"Place found: \" + place.getDisplayName());\n        }).addOnFailureListener((exception) -> {\n            if (exception instanceof ApiException apiException) {\n                final String message = getString(R.string.place_not_found, apiException.getMessage());\n                binding.placeName.setText(message);\n                Log.e(TAG, \"Place not found: \" + exception.getMessage());\n                final int statusCode = apiException.getStatusCode();\n                // TODO: Handle error with given status code.\n            }\n        });\n        // [END maps_places_get_place_by_id]\n    }\n}"
  },
  {
    "path": "snippets/src/main/java/com/google/places/PlaceIsOpenActivity.java",
    "content": "// Copyright 2023 Google LLC\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.places;\n\nimport android.annotation.SuppressLint;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.MenuItem;\n\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.view.WindowCompat;\n\nimport com.google.android.gms.tasks.Task;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest;\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse;\nimport com.google.android.libraries.places.api.net.IsOpenRequest;\nimport com.google.android.libraries.places.api.net.IsOpenResponse;\nimport com.google.android.libraries.places.api.net.PlacesClient;\nimport com.google.places.data.PlaceIdProvider;\nimport com.google.places.databinding.ActivityPlaceIsOpenBinding;\nimport com.google.places.kotlin.MainApplication;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.List;\n\npublic class PlaceIsOpenActivity extends AppCompatActivity {\n    private PlacesClient placesClient;\n    private ActivityPlaceIsOpenBinding binding;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Enable edge-to-edge display.\n        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);\n\n        binding = ActivityPlaceIsOpenBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        setSupportActionBar(binding.toolbar);\n        if (getSupportActionBar() != null) {\n            getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n            getSupportActionBar().setTitle(getTitle() + \" (Java)\");\n        }\n\n\n\n        placesClient = ((MainApplication) getApplication()).getPlacesClient();\n\n        binding.isOpenByObjectButton.setOnClickListener(v -> isOpenByPlaceObject());\n        binding.isOpenByIdButton.setOnClickListener(v -> isOpenByPlaceId());\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == android.R.id.home) {\n            finish();\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    /**\n     * Check if the place is open at the time specified in the input fields.\n     * Requires a Place object that includes Place.Field.ID\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private void isOpenByPlaceObject() {\n        // [START maps_places_place_is_open]\n        @NonNull\n        Calendar isOpenCalendar = Calendar.getInstance();\n        String placeId = PlaceIdProvider.getRandomPlaceId();\n        // Specify the required fields for an isOpen request.\n        List<Place.Field> placeFields = new ArrayList<>(Arrays.asList(\n                Place.Field.BUSINESS_STATUS,\n                Place.Field.CURRENT_OPENING_HOURS,\n                Place.Field.ID,\n                Place.Field.OPENING_HOURS,\n                Place.Field.DISPLAY_NAME\n        ));\n\n        FetchPlaceRequest request = FetchPlaceRequest.newInstance(placeId, placeFields);\n        Task<FetchPlaceResponse> placeTask = placesClient.fetchPlace(request);\n\n        placeTask.addOnSuccessListener(\n                (placeResponse) -> {\n                    Place place = placeResponse.getPlace();\n                    IsOpenRequest isOpenRequest;\n\n                    try {\n                        isOpenRequest = IsOpenRequest.newInstance(place, isOpenCalendar.getTimeInMillis());\n                    } catch (IllegalArgumentException e) {\n                        Log.e(\"PlaceIsOpen\", \"Error: \" + e.getMessage());\n                        return;\n                    }\n                    Task<IsOpenResponse> isOpenTask = placesClient.isOpen(isOpenRequest);\n\n                    isOpenTask.addOnSuccessListener(\n                            (isOpenResponse) -> {\n                                final boolean isOpen = Boolean.TRUE.equals(isOpenResponse.isOpen());\n                                binding.isOpenByObjectResult.setText(getString(R.string.is_open_by_object, place.getDisplayName(), String.valueOf(isOpen)));\n                                Log.d(\"PlaceIsOpen\", \"Is open by object: \" + isOpen);\n                            });\n                    isOpenTask.addOnFailureListener(\n                            (exception) -> { // also update the result text field\n                                binding.isOpenByObjectResult.setText(getString(R.string.is_open_by_object, place.getDisplayName(), \"Error: \" + exception.getMessage()));\n                                Log.e(\"PlaceIsOpen\", \"Error: \" + exception.getMessage());\n                            });\n                });\n        placeTask.addOnFailureListener(\n                (exception) -> {\n                    binding.isOpenByObjectResult.setText(\"Error: \" + exception.getMessage());\n                    Log.e(\"PlaceIsOpen\", \"Error: \" + exception.getMessage());\n                });        // [END maps_places_place_is_open]\n    }\n\n    /**\n     * Check if the place is open at the time specified in the input fields.\n     * Use the Place ID in the input field for the isOpenRequest.\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private void isOpenByPlaceId() {\n        // [START maps_places_id_is_open]\n        @NonNull\n        Calendar isOpenCalendar = Calendar.getInstance();\n        String placeId = PlaceIdProvider.getRandomPlaceId();\n        IsOpenRequest isOpenRequest;\n\n        try {\n            isOpenRequest = IsOpenRequest.newInstance(placeId, isOpenCalendar.getTimeInMillis());\n        } catch (IllegalArgumentException e) {\n            Log.e(\"PlaceIsOpen\", \"Error: \" + e.getMessage());\n            return;\n        }\n\n        Task<IsOpenResponse> placeTask = placesClient.isOpen(isOpenRequest);\n\n        placeTask.addOnSuccessListener(\n                (response) -> {\n                    final boolean isOpen = Boolean.TRUE.equals(response.isOpen());\n                    binding.isOpenByIdResult.setText(getString(R.string.is_open_by_id, String.valueOf(isOpen)));\n                    Log.d(\"PlaceIsOpen\", \"Is open by ID: \" + isOpen);\n                });\n        placeTask.addOnFailureListener((exception) -> {\n            binding.isOpenByIdResult.setText(getString(R.string.is_open_by_id, \"Error: \" + exception.getMessage()));\n            Log.e(\"PlaceIsOpen\", \"Error: \" + exception.getMessage());\n        });\n        // [END maps_places_id_is_open]\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/PlacePhotosActivity.java",
    "content": "// Copyright 2023 Google LLC\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.places;\n\nimport android.graphics.Bitmap;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.MenuItem;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.view.WindowCompat;\n\nimport com.google.android.gms.common.api.ApiException;\nimport com.google.android.libraries.places.api.model.PhotoMetadata;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.android.libraries.places.api.net.FetchResolvedPhotoUriRequest;\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest;\nimport com.google.android.libraries.places.api.net.PlacesClient;\nimport com.google.places.data.PlaceIdProvider;\nimport com.bumptech.glide.Glide;\nimport com.google.places.databinding.ActivityPlacePhotosBinding;\nimport com.google.places.kotlin.MainApplication;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class PlacePhotosActivity extends AppCompatActivity {\n    private static final String TAG = PlacePhotosActivity.class.getSimpleName();\n\n    private PlacesClient placesClient;\n    private ActivityPlacePhotosBinding binding;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Enable edge-to-edge display.\n        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);\n\n        binding = ActivityPlacePhotosBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        setSupportActionBar(binding.toolbar);\n        if (getSupportActionBar() != null) {\n            getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n            getSupportActionBar().setTitle(getTitle() + \" (Java)\");\n        }\n\n        placesClient = ((MainApplication) getApplication()).getPlacesClient();\n\n        binding.placePhotosButton.setOnClickListener(v -> getPlacePhoto());\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == android.R.id.home) {\n            finish();\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private void getPlacePhoto() {\n        // [START maps_places_get_place_photos]\n        // Define a Place ID.\n        final String placeId = PlaceIdProvider.getRandomPlaceId();\n\n        // Specify fields. Requests for photos must always have the PHOTO_METADATAS field.\n        final List<Place.Field> fields = Collections.singletonList(Place.Field.PHOTO_METADATAS);\n\n        // Get a Place object (this example uses fetchPlace(), but you can also use findCurrentPlace())\n        final FetchPlaceRequest placeRequest = FetchPlaceRequest.newInstance(placeId, fields);\n\n        placesClient.fetchPlace(placeRequest).addOnSuccessListener((response) -> {\n            final Place place = response.getPlace();\n\n            // Get the photo metadata.\n            final List<PhotoMetadata> metadata = place.getPhotoMetadatas();\n            if (metadata == null || metadata.isEmpty()) {\n                Log.w(TAG, \"No photo metadata.\");\n                return;\n            }\n            final PhotoMetadata photoMetadata = metadata.get(0);\n\n            // Get the attribution text.\n            final String attributions = photoMetadata.getAttributions();\n            binding.placePhotosAttributions.setText(attributions);\n\n            // Create a FetchResolvedPhotoUriRequest.\n            final FetchResolvedPhotoUriRequest photoRequest = FetchResolvedPhotoUriRequest.builder(photoMetadata)\n                .setMaxWidth(500) // Optional.\n                .setMaxHeight(300) // Optional.\n                .build();\n            placesClient.fetchResolvedPhotoUri(photoRequest).addOnSuccessListener((fetchPhotoResponse) -> {\n                Glide.with(this)\n                        .load(fetchPhotoResponse.getUri())\n                        .into(binding.placePhotosResult);\n            }).addOnFailureListener((exception) -> {\n                if (exception instanceof ApiException) {\n                    ApiException apiException = (ApiException) exception;\n                    Log.e(TAG, \"Place not found: \" + exception.getMessage());\n                    final int statusCode = apiException.getStatusCode();\n                    // TODO: Handle error with given status code.\n                }\n            });\n        });\n        // [END maps_places_get_place_photos]\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/PlacesIconActivity.java",
    "content": "// Copyright 2023 Google LLC\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.places;\n\nimport android.graphics.Color;\nimport android.os.Bundle;\nimport android.view.MenuItem;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.view.WindowCompat;\n\nimport com.bumptech.glide.Glide;\nimport com.google.android.libraries.places.api.model.Place;\nimport com.google.places.databinding.ActivityPlacesIconBinding;\n\npublic class PlacesIconActivity extends AppCompatActivity {\n    private ActivityPlacesIconBinding binding;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Enable edge-to-edge display.\n        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);\n\n        binding = ActivityPlacesIconBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        setSupportActionBar(binding.toolbar);\n        if (getSupportActionBar() != null) {\n            getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n            getSupportActionBar().setTitle(getTitle() + \" (Java)\");\n        }\n\n        binding.placesIconButton.setOnClickListener(v -> {\n            // In a real app, you would get a Place object from a Place Details request or similar.\n            // For this snippet, we'll create a dummy Place object.\n            Place place = Place.builder()\n                .setIconBackgroundColor(Color.BLUE)\n                .setIconMaskUrl(\"https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/generic_business-71.png\")\n                .build();\n            getPlacesIcon(place);\n        });\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == android.R.id.home) {\n            finish();\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private void getPlacesIcon(Place place) {\n        // [START maps_places_places_icon_and_bg_color]\n        // It's recommended to retrieve the icon_background_color and icon_mask_base_uri fields from a\n        // FetchPlaceRequest and pass them to the Place object.\n        // Set the image view's background color to match the place's icon background color\n        Integer iconBackgroundColor = place.getIconBackgroundColor();\n        if (iconBackgroundColor == null) {\n            iconBackgroundColor = Color.TRANSPARENT;\n        }\n        binding.placesIconResult.setBackgroundColor(iconBackgroundColor);\n\n        // Fetch the icon using Glide and set the result in the image view\n        Glide.with(this)\n            .load(place.getIconMaskUrl())\n            .into(binding.placesIconResult);\n        // [END maps_places_places_icon_and_bg_color]\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/data/PlaceIdProvider.java",
    "content": "// Copyright 2026 Google LLC\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.\npackage com.google.places.data;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Random;\n\n/**\n * Provides a list of sample Google Place IDs for testing.\n * The list is parsed from a raw string and lazily initialized on first access.\n * <p>\n * To generate a list of nearby places, you can use the following script with curl:\n * <pre>\n * #!/bin/bash\n *\n * API_KEY=$(cat ~/google_api_key.txt)\n *\n * curl -X POST -d '{\n *   \"locationRestriction\": {\n *     \"circle\": {\n *       \"center\": {\n *         \"latitude\": 40.0150,\n *         \"longitude\": -105.2705\n *       },\n *       \"radius\": 1500.0\n *     }\n *   }\n * }' \\\n * -H 'Content-Type: application/json' \\\n * -H 'X-Goog-FieldMask: places.id' \\\n * \"https://places.googleapis.com/v1/places:searchNearby?key=$API_KEY\"\n * </pre>\n */\npublic class PlaceIdProvider {\n\n    // The raw data is stored in a private static final string.\n    // Using \\n makes it compatible with older Java versions that lack text blocks.\n    private static final String RAW_PLACE_IDS = \"\"\"\n            ChIJwR6cajTsa4cR2TH0qKTVKAM\n            ChIJiTEGLibsa4cRepH7ZMFEcJ8\n            ChIJ01j9ptfta4cRIKZGWw-Gkq4\n            ChIJHzUT3tbta4cRGIqqS1UAjkE\n            ChIJ4bbaBcnta4cR0LKO770ALRQ\n            ChIJ4-dlTy_sa4cRd978vIqVG1Y\n            ChIJvw3XCdHta4cREEupPNyRDg0\n            ChIJ6bmRoybsa4cRF2M_QGtaSYY\n            ChIJvQ9WKSnta4cR7n55uCAYPL4\n            ChIJE6YJGNDta4cRF2x0W8c8DAI\n            ChIJH68pxyfsa4cR-EIpOWL5Umc\n            ChIJG3SvINLta4cR3PNcgxz9lLk\n            ChIJ5TW3jDDsa4cRkSewKsCsNSE\n            ChIJgbfb4dPta4cRlixOyQ-DOUo\n            ChIJ2yMKRSTsa4cRSCSGwX1rRUI\n            ChIJ4xyxGbTta4cRu-XnmlmC5EI\n            ChIJWbbcvijsa4cR7bHu3lilcFA\n            ChIJ00Gjeyjsa4cRvLYGmRIQ92o\n            ChIJA8ksXNHta4cR8MkYLipiZL0\n            ChIJc0Gm1tbta4cRxmXvsBRtU7M\"\"\";\n\n    // The list is declared as 'volatile' and starts as null.\n    // 'volatile' ensures changes are visible across all threads.\n    private static volatile List<String> placeIds = null;\n\n    // A single Random instance is more efficient than creating one each time.\n    private static final Random RANDOM = new Random();\n\n    /**\n     * Gets the list of place IDs, initializing it only on the first call.\n     * This method is thread-safe.\n     *\n     * @return A non-null, unmodifiable list of place IDs.\n     */\n    public static List<String> getPlaceIds() {\n        // Use a common pattern called \"double-checked locking\" for lazy initialization.\n        if (placeIds == null) {\n            synchronized (PlaceIdProvider.class) {\n                // Check again inside the lock in case another thread initialized it.\n                if (placeIds == null) {\n                    // This is the compatible way for older Java.\n                    // The \"\\\\R\" regex splits on any universal newline sequence.\n                    placeIds = Arrays.asList(RAW_PLACE_IDS.split(\"\\\\R\"));\n                }\n            }\n        }\n        return placeIds;\n    }\n\n    /**\n     * Gets a single random place ID from the list.\n     *\n     * @return A random place ID string.\n     */\n    public static String getRandomPlaceId() {\n        List<String> ids = getPlaceIds(); // This ensures the list is initialized.\n        if (ids.isEmpty()) {\n            throw new IllegalStateException(\"Place ID list is empty.\");\n        }\n        // Get a random element from the list.\n        return ids.get(RANDOM.nextInt(ids.size()));\n    }\n}"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/CurrentPlaceActivity.kt",
    "content": "// Copyright 2023 Google LLC\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.places.kotlin\n\nimport android.Manifest.permission\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.WindowCompat\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.gms.common.api.ApiException\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.model.PlaceLikelihood\nimport com.google.android.libraries.places.api.net.FindCurrentPlaceRequest\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.places.databinding.ActivityCurrentPlaceBinding\nimport com.google.places.databinding.ListItemPlaceBinding\nimport java.util.Locale\n\nclass CurrentPlaceActivity : AppCompatActivity() {\n\n    private lateinit var placesClient: PlacesClient\n    private lateinit var binding: ActivityCurrentPlaceBinding\n    private lateinit var adapter: PlacesAdapter\n\n    private val requestPermissionLauncher =\n        registerForActivityResult(\n            androidx.activity.result.contract.ActivityResultContracts.RequestPermission()\n        ) { isGranted: Boolean ->\n            if (isGranted) {\n                findCurrentPlace()\n            } else {\n                // Handle permission denied\n            }\n        }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityCurrentPlaceBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        supportActionBar?.title = \"$title (Kotlin)\"\n\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n\n        placesClient = (application as MainApplication).getPlacesClient()\n\n        binding.currentPlaceButton.setOnClickListener { findCurrentPlace() }\n\n        // Set up the RecyclerView\n        adapter = PlacesAdapter()\n        binding.placesRecyclerView.layoutManager = LinearLayoutManager(this)\n        binding.placesRecyclerView.adapter = adapter\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == android.R.id.home) {\n            finish()\n            return true\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun findCurrentPlace() {\n        binding.progressBar.visibility = View.VISIBLE\n\n        // [START maps_places_current_place]\n        // Use fields to define the data types to return.\n        val placeFields = listOf(Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS, Place.Field.LOCATION)\n        val request = FindCurrentPlaceRequest.newInstance(placeFields)\n\n        // Call findCurrentPlace and handle the response (first check that the user has granted permission).\n        if (ContextCompat.checkSelfPermission(this, permission.ACCESS_FINE_LOCATION) ==\n            PackageManager.PERMISSION_GRANTED\n        ) {\n            placesClient.findCurrentPlace(request).addOnSuccessListener { response ->\n                binding.progressBar.visibility = View.GONE\n                adapter.setPlaceLikelihoods(response.placeLikelihoods)\n            }.addOnFailureListener { exception ->\n                binding.progressBar.visibility = View.GONE\n                if (exception is ApiException) {\n                    Log.e(TAG, \"Place not found: ${exception.statusCode}\")\n                }\n            }\n        } else {\n            // [START_EXCLUDE silent]\n            binding.progressBar.visibility = View.GONE\n            // [END_EXCLUDE]\n            // A local method to request required permissions;\n            // See https://developer.android.com/training/permissions/requesting\n            getLocationPermission()\n        }\n        // [END maps_places_current_place]\n    }\n\n    private fun getLocationPermission() {\n        requestPermissionLauncher.launch(permission.ACCESS_FINE_LOCATION)\n    }\n\n    // Adapter for the RecyclerView\n    private class PlacesAdapter : RecyclerView.Adapter<PlacesAdapter.ViewHolder>() {\n\n        private var placeLikelihoods: List<PlaceLikelihood> = emptyList()\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n            val binding = ListItemPlaceBinding.inflate(LayoutInflater.from(parent.context), parent, false)\n            return ViewHolder(binding)\n        }\n\n        override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n            val placeLikelihood = placeLikelihoods[position]\n            holder.bind(placeLikelihood)\n        }\n\n        override fun getItemCount(): Int = placeLikelihoods.size\n\n        fun setPlaceLikelihoods(placeLikelihoods: List<PlaceLikelihood>) {\n            this.placeLikelihoods = placeLikelihoods\n            notifyDataSetChanged()\n        }\n\n        class ViewHolder(private val binding: ListItemPlaceBinding) : RecyclerView.ViewHolder(binding.root) {\n            fun bind(placeLikelihood: PlaceLikelihood) {\n                val place = placeLikelihood.place\n                binding.placeName.text = place.displayName\n                binding.placeAddress.text = place.formattedAddress\n                binding.placeLikelihood.text = String.format(Locale.getDefault(), \"Likelihood: %.2f\", placeLikelihood.likelihood)\n            }\n        }\n    }\n\n    companion object {\n        private val TAG = CurrentPlaceActivity::class.java.simpleName\n    }\n}"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/GetStartedActivity.kt",
    "content": "// Copyright 2020 Google LLC\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.places.kotlin\n\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport com.google.android.libraries.places.api.Places\nimport com.google.places.BuildConfig\nimport com.google.places.R\n\nclass GetStartedActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        val apiKey = BuildConfig.PLACES_API_KEY\n        if (apiKey == \"DEFAULT_API_KEY\") {\n            Toast.makeText(this, \"PLACES_API_KEY has not been configured\", Toast.LENGTH_SHORT).show()\n            finish()\n            return\n        }\n\n        // [START maps_places_get_started]\n        // Initialize the SDK\n        Places.initialize(applicationContext, apiKey)\n\n        // Create a new PlacesClient instance\n        val placesClient = Places.createClient(this)\n        // [END maps_places_get_started]\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/KotlinMainActivity.kt",
    "content": "// Copyright 2026 Google LLC\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.places.kotlin\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.WindowCompat\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.places.R\nimport com.google.places.databinding.ActivityMainBinding\nimport com.google.places.databinding.ListItemActivityBinding\n\nclass KotlinMainActivity : 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        setSupportActionBar(binding.toolbar)\n        supportActionBar?.title = \"$title (Kotlin)\"\n\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n\n        val activities = listOf(\n            ActivityInfo(\n                \"Current Place\",\n                getString(R.string.current_place_description),\n                CurrentPlaceActivity::class.java\n            ),\n            ActivityInfo(\n                \"Place Autocomplete\",\n                getString(R.string.place_autocomplete_description),\n                PlaceAutocompleteActivity::class.java\n            ),\n            ActivityInfo(\n                \"Place Details\",\n                getString(R.string.place_details_description),\n                PlaceDetailsActivity::class.java\n            ),\n            ActivityInfo(\n                \"Place Photos\",\n                getString(R.string.place_photos_description),\n                PlacePhotosActivity::class.java\n            ),\n            ActivityInfo(\n                \"Places Icon\",\n                getString(R.string.places_icon_description),\n                PlacesIconActivity::class.java\n            ),\n            ActivityInfo(\n                \"Place Is Open\",\n                getString(R.string.place_is_open_description),\n                PlaceIsOpenActivity::class.java\n            )\n        )\n\n        binding.recyclerView.layoutManager = LinearLayoutManager(this)\n        binding.recyclerView.adapter = ActivitiesAdapter(activities)\n    }\n\n    private data class ActivityInfo(\n        val name: String,\n        val description: String,\n        val activityClass: Class<out AppCompatActivity>\n    )\n\n    private class ActivitiesAdapter(private val activities: List<ActivityInfo>) :\n        RecyclerView.Adapter<ActivitiesAdapter.ViewHolder>() {\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n            val binding = ListItemActivityBinding.inflate(\n                LayoutInflater.from(parent.context),\n                parent,\n                false\n            )\n            return ViewHolder(binding)\n        }\n\n        override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n            val activityInfo = activities[position]\n            holder.binding.activityName.text = activityInfo.name\n            holder.binding.activityDescription.text = activityInfo.description\n            holder.itemView.setOnClickListener {\n                val intent = Intent(holder.itemView.context, activityInfo.activityClass)\n                holder.itemView.context.startActivity(intent)\n            }\n        }\n\n        override fun getItemCount(): Int = activities.size\n\n        class ViewHolder(val binding: ListItemActivityBinding) : RecyclerView.ViewHolder(binding.root)\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/MainApplication.kt",
    "content": "// Copyright 2026 Google LLC\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.places.kotlin\n\nimport android.app.Application\nimport android.util.Log\nimport android.widget.Toast\nimport com.google.android.libraries.places.api.Places\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.places.BuildConfig\n\nclass MainApplication : Application() {\n    private lateinit var placesClient: PlacesClient\n\n    override fun onCreate() {\n        super.onCreate()\n\n        val apiKey = BuildConfig.PLACES_API_KEY\n        if (apiKey == \"DEFAULT_API_KEY\") {\n            Toast.makeText(this, \"PLACES_API_KEY has not been configured\", Toast.LENGTH_SHORT).show()\n            Log.e(\"GetStartedActivity\", \"PLACES_API_KEY has not been configured. See app/build.gradle.kts\")\n            return\n        }\n\n        // [START maps_places_get_started]\n        // Initialize the SDK\n        Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)\n\n        // Create a new PlacesClient instance\n        placesClient = Places.createClient(this)\n        // [END maps_places_get_started]\n    }\n\n    fun getPlacesClient(): PlacesClient {\n        return placesClient\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/PlaceAutocompleteActivity.kt",
    "content": "// Copyright 2020 Google LLC\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.places.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.MenuItem\nimport androidx.activity.result.ActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.WindowCompat\nimport com.google.android.gms.common.api.ApiException\nimport com.google.android.gms.common.api.Status\nimport com.google.android.gms.maps.model.LatLng\nimport com.google.android.libraries.places.api.model.AutocompleteSessionToken\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.model.PlaceTypes\nimport com.google.android.libraries.places.api.model.RectangularBounds\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest\nimport com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.android.libraries.places.widget.Autocomplete\nimport com.google.android.libraries.places.widget.AutocompleteSupportFragment\nimport com.google.android.libraries.places.widget.listener.PlaceSelectionListener\nimport com.google.android.libraries.places.widget.model.AutocompleteActivityMode\nimport com.google.places.R\nimport com.google.places.databinding.ActivityPlaceAutocompleteBinding\n\nclass PlaceAutocompleteActivity : AppCompatActivity() {\n    private lateinit var placesClient: PlacesClient\n    private lateinit var binding: ActivityPlaceAutocompleteBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityPlaceAutocompleteBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        supportActionBar?.title = \"$title (Kotlin)\"\n\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n\n        placesClient = (application as MainApplication).getPlacesClient()\n\n        binding.useRestrictionSwitch.setOnCheckedChangeListener { _, _ ->\n            initAutocompleteSupportFragment()\n        }\n        initAutocompleteSupportFragment()\n        binding.autocompleteIntentButton.setOnClickListener { startAutocompleteIntent() }\n        binding.programmaticAutocompleteButton.setOnClickListener {\n            programmaticPlacePredictions(binding.autocompleteQuery.text.toString())\n        }\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == android.R.id.home) {\n            finish()\n            return true\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun initAutocompleteSupportFragment() {\n        // [START maps_places_autocomplete_support_fragment]\n        // Initialize the AutocompleteSupportFragment.\n        val autocompleteFragment =\n            supportFragmentManager.findFragmentById(R.id.autocomplete_fragment)\n                    as AutocompleteSupportFragment\n\n        // Specify the types of place data to return.\n        autocompleteFragment.setPlaceFields(listOf(Place.Field.ID, Place.Field.DISPLAY_NAME))\n\n        // Set up a PlaceSelectionListener to handle the response.\n        autocompleteFragment.setOnPlaceSelectedListener(object : PlaceSelectionListener {\n            override fun onPlaceSelected(place: Place) {\n                binding.autocompleteResult.text = getString(\n                    R.string.place_selection,\n                    place.displayName,\n                    place.id,\n                    place.formattedAddress\n                )\n                Log.i(TAG, \"Place: ${place.displayName}, ${place.id}\")\n            }\n\n            override fun onError(status: Status) {\n                binding.autocompleteResult.text = getString(R.string.an_error_occurred, status)\n                Log.i(TAG, \"An error occurred: $status\")\n            }\n        })\n        // [END maps_places_autocomplete_support_fragment]\n\n        val bounds = RectangularBounds.newInstance(\n            LatLng(-33.880490, 151.184363),\n            LatLng(-33.858754, 151.229596)\n        )\n\n        // Clear the previous restriction or bias\n        autocompleteFragment.setLocationRestriction(null)\n        autocompleteFragment.setLocationBias(null)\n\n        if (binding.useRestrictionSwitch.isChecked) {\n            // [START maps_places_autocomplete_location_restriction]\n            autocompleteFragment.setLocationRestriction(bounds)\n            // [END maps_places_autocomplete_location_restriction]\n        } else {\n            // [START maps_places_autocomplete_location_bias]\n            autocompleteFragment.setLocationBias(bounds)\n            // [END maps_places_autocomplete_location_bias]\n        }\n\n        // [START maps_places_autocomplete_type_filter]\n        autocompleteFragment.setTypesFilter(listOf(PlaceTypes.ADDRESS))\n        // [END maps_places_autocomplete_type_filter]\n\n        // [START maps_places_autocomplete_type_filter_multiple]\n        autocompleteFragment.setTypesFilter(listOf(\"landmark\", \"restaurant\", \"store\"))\n        // [END maps_places_autocomplete_type_filter_multiple]\n\n\n        // [START maps_places_autocomplete_country_filter]\n        autocompleteFragment.setCountries(\"AU\", \"NZ\")\n        // [END maps_places_autocomplete_country_filter]\n    }\n\n    // [START maps_places_autocomplete_intent]\n\n    // [START_EXCLUDE silent]\n    private fun startAutocompleteIntent() {\n        // [END_EXCLUDE]\n        // Set the fields to specify which types of place data to\n        // return after the user has made a selection.\n        val fields = listOf(Place.Field.ID, Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS)\n\n        // [START maps_places_intent_type_filter]\n        val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.FULLSCREEN, fields)\n            .setTypesFilter(listOf(PlaceTypes.ESTABLISHMENT))\n            .build(this)\n        // [END maps_places_intent_type_filter]\n\n        startAutocomplete.launch(intent)\n        // [END maps_places_autocomplete_intent]\n    }\n\n    // [START maps_places_on_activity_result]\n    private val startAutocomplete =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->\n            if (result.resultCode == RESULT_OK) {\n                val intent = result.data\n                if (intent != null) {\n                    val place = Autocomplete.getPlaceFromIntent(intent)\n                    binding.autocompleteResult.text = getString(\n                        R.string.place_selection,\n                        place.displayName,\n                        place.id,\n                        place.formattedAddress)\n                    Log.i(\n                        TAG, \"Place: ${place.displayName}, ${place.id}\"\n                    )\n                }\n            } else if (result.resultCode == RESULT_CANCELED) {\n                // The user canceled the operation.\n                binding.autocompleteResult.setText(R.string.user_canceled_autocomplete)\n                Log.i(TAG, \"User canceled autocomplete\")\n            }\n        }\n    // [END maps_places_on_activity_result]\n\n    private fun programmaticPlacePredictions(query: String) {\n        // [START maps_places_programmatic_place_predictions]\n        // Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest,\n        // and once again when the user makes a selection (for example when calling fetchPlace()).\n        val token = AutocompleteSessionToken.newInstance()\n\n        // Create a RectangularBounds object.\n        val bounds = RectangularBounds.newInstance(\n            LatLng(-33.880490, 151.184363),\n            LatLng(-33.858754, 151.229596)\n        )\n        // Use the builder to create a FindAutocompletePredictionsRequest.\n        val request =\n            FindAutocompletePredictionsRequest.builder()\n                // Call either setLocationBias() OR setLocationRestriction().\n                .setLocationBias(bounds)\n                //.setLocationRestriction(bounds)\n                .setOrigin(LatLng(-33.8749937, 151.2041382))\n                .setCountries(\"AU\", \"NZ\")\n                .setTypesFilter(listOf(PlaceTypes.ESTABLISHMENT))\n                .setSessionToken(token)\n                .setQuery(query)\n                .build()\n        placesClient.findAutocompletePredictions(request)\n            .addOnSuccessListener { response: FindAutocompletePredictionsResponse ->\n                val builder = StringBuilder()\n                for (prediction in response.autocompletePredictions) {\n                    builder.append(prediction.getPrimaryText(null).toString()).append(\"\\n\")\n                    Log.i(TAG, prediction.placeId)\n                    Log.i(TAG, prediction.getPrimaryText(null).toString())\n                }\n                binding.autocompleteResult.text = builder.toString()\n            }.addOnFailureListener { exception: Exception? ->\n                if (exception is ApiException) {\n                    Log.e(TAG, \"Place not found: ${exception.statusCode}\")\n                    binding.autocompleteResult.text = getString(R.string.place_not_found, exception.message)\n                }\n            }\n        // [END maps_places_programmatic_place_predictions]\n    }\n\n    companion object {\n        val TAG = PlaceAutocompleteActivity::class.java.simpleName\n    }\n}"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/PlaceDetailsActivity.kt",
    "content": "// Copyright 2023 Google LLC\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.places.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.MenuItem\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.WindowCompat\nimport com.google.android.gms.common.api.ApiException\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.places.R\nimport com.google.places.data.PlaceIdProvider\nimport com.google.places.databinding.ActivityPlaceDetailsBinding\n\nclass PlaceDetailsActivity : AppCompatActivity() {\n    private lateinit var placesClient: PlacesClient\n    private lateinit var binding: ActivityPlaceDetailsBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityPlaceDetailsBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        supportActionBar?.title = \"$title (Kotlin)\"\n\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n\n        placesClient = (application as MainApplication).getPlacesClient()\n\n        getPlaceById()\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == android.R.id.home) {\n            finish()\n            return true\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun getPlaceById() {\n        // [START maps_places_get_place_by_id]\n        // Define a Place ID.\n        val placeId = PlaceIdProvider.getRandomPlaceId()\n\n        // Specify the fields to return.\n        val placeFields = listOf(\n            Place.Field.ID,\n            Place.Field.DISPLAY_NAME,\n            Place.Field.FORMATTED_ADDRESS,\n            Place.Field.LOCATION\n        )\n\n        // Construct a request object, passing the place ID and fields array.\n        val request = FetchPlaceRequest.newInstance(placeId, placeFields)\n\n        placesClient.fetchPlace(request)\n            .addOnSuccessListener { response: FetchPlaceResponse ->\n                val place = response.place\n\n                // [START maps_places_place_details_simple]\n                val name = place.displayName\n                val address = place.formattedAddress\n                val location = place.location\n                // [END maps_places_place_details_simple]\n\n                binding.placeName.text = name\n                binding.placeAddress.text = address\n                if (location != null) {\n                    binding.placeLocation.text = getString(\n                        R.string.place_location, location.latitude, location.longitude\n                    )\n                } else {\n                    binding.placeLocation.text = null\n                }\n                Log.i(TAG, \"Place found: ${place.displayName}\")\n            }.addOnFailureListener { exception: Exception ->\n                if (exception is ApiException) {\n                    val message = getString(R.string.place_not_found, exception.message)\n                    binding.placeName.text = message\n                    Log.e(TAG, \"Place not found: ${exception.message}\")\n                    val statusCode = exception.statusCode\n                    TODO(\"Handle error with given status code\")\n                }\n            }\n        // [END maps_places_get_place_by_id]\n    }\n\n    companion object {\n        private val TAG = PlaceDetailsActivity::class.java.simpleName\n    }\n}"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/PlaceIsOpenActivity.kt",
    "content": "// Copyright 2023 Google LLC\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.places.kotlin\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.MenuItem\nimport androidx.appcompat.app.AppCompatActivity\nimport com.google.android.gms.tasks.Task\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse\nimport com.google.android.libraries.places.api.net.IsOpenRequest\nimport com.google.android.libraries.places.api.net.IsOpenResponse\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.places.R\nimport com.google.places.data.PlaceIdProvider\nimport com.google.places.databinding.ActivityPlaceIsOpenBinding\nimport java.util.Calendar\n\nclass PlaceIsOpenActivity : AppCompatActivity() {\n    private lateinit var placesClient: PlacesClient\n    private lateinit var binding: ActivityPlaceIsOpenBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityPlaceIsOpenBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        supportActionBar?.title = \"$title (Kotlin)\"\n\n        placesClient = (application as MainApplication).getPlacesClient()\n\n        binding.isOpenByObjectButton.setOnClickListener {\n            isOpenByPlaceObject()\n        }\n        binding.isOpenByIdButton.setOnClickListener {\n            isOpenByPlaceId()\n        }\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == android.R.id.home) {\n            finish()\n            return true\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    /**\n     * Check if the place is open at the time specified in the input fields.\n     * Requires a Place object that includes Place.Field.ID\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private fun isOpenByPlaceObject() {\n        // [START maps_places_place_is_open]\n        val isOpenCalendar: Calendar = Calendar.getInstance()\n        var place: Place\n        val placeId = PlaceIdProvider.getRandomPlaceId()\n        // Specify the required fields for an isOpen request.\n        val placeFields: List<Place.Field> = listOf(\n            Place.Field.BUSINESS_STATUS,\n            Place.Field.CURRENT_OPENING_HOURS,\n            Place.Field.ID,\n            Place.Field.OPENING_HOURS,\n            Place.Field.DISPLAY_NAME\n        )\n\n        val placeRequest: FetchPlaceRequest =\n            FetchPlaceRequest.newInstance(placeId, placeFields)\n        val placeTask: Task<FetchPlaceResponse> = placesClient.fetchPlace(placeRequest)\n        placeTask.addOnSuccessListener { placeResponse ->\n            place = placeResponse.place\n\n            val isOpenRequest: IsOpenRequest = try {\n                IsOpenRequest.newInstance(place, isOpenCalendar.timeInMillis)\n            } catch (e: IllegalArgumentException) {\n                Log.e(\"PlaceIsOpen\", \"Error: \" + e.message)\n                return@addOnSuccessListener\n            }\n            val isOpenTask: Task<IsOpenResponse> = placesClient.isOpen(isOpenRequest)\n            isOpenTask.addOnSuccessListener { isOpenResponse ->\n                val isOpen = when (isOpenResponse.isOpen) {\n                    true -> getString(R.string.is_open)\n                    else -> getString(R.string.is_closed)\n                }\n                binding.isOpenByObjectResult.text = getString(\n                    R.string.is_open_by_object,\n                    place.displayName,\n                    isOpen\n                )\n                Log.d(\"PlaceIsOpen\", \"Is open by object: $isOpen\")\n            }\n            // [START_EXCLUDE]\n            isOpenTask.addOnFailureListener { exception ->\n                Log.e(\"PlaceIsOpen\", \"Error: \" + exception.message)\n            }\n            // [END_EXCLUDE]\n        }\n        // [START_EXCLUDE]\n        placeTask.addOnFailureListener { exception ->\n            Log.e(\"PlaceIsOpen\", \"Error: \" + exception.message)\n        }\n        // [END_EXCLUDE]\n        // [END maps_places_place_is_open]\n    }\n\n    /**\n     * Check if the place is open at the time specified in the input fields.\n     * Use the Place ID in the input field for the isOpenRequest.\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private fun isOpenByPlaceId() {\n        // [START maps_places_id_is_open]\n        val isOpenCalendar: Calendar = Calendar.getInstance()\n        val placeId = PlaceIdProvider.getRandomPlaceId()\n\n        val request: IsOpenRequest = try {\n            IsOpenRequest.newInstance(placeId, isOpenCalendar.timeInMillis)\n        } catch (e: IllegalArgumentException) {\n            Log.e(\"PlaceIsOpen\", \"Error: \" + e.message)\n            return\n        }\n        val isOpenTask: Task<IsOpenResponse> = placesClient.isOpen(request)\n        isOpenTask.addOnSuccessListener { response ->\n            val isOpen = response.isOpen ?: false\n            binding.isOpenByIdResult.text = getString(R.string.is_open_by_id, isOpen.toString())\n            Log.d(\"PlaceIsOpen\", \"Is open by ID: $isOpen\")\n        }\n        // [START_EXCLUDE]\n        isOpenTask.addOnFailureListener { exception ->\n            Log.e(\"PlaceIsOpen\", \"Error: \" + exception.message)\n        }\n        isOpenTask.addOnCompleteListener { }\n        // [END_EXCLUDE]\n        // [END maps_places_id_is_open]\n    }\n}"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/PlacePhotosActivity.kt",
    "content": "// Copyright 2023 Google LLC\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.places.kotlin\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.MenuItem\nimport androidx.appcompat.app.AppCompatActivity\nimport com.google.android.gms.common.api.ApiException\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.android.libraries.places.api.net.FetchResolvedPhotoUriRequest\nimport com.google.android.libraries.places.api.net.FetchPlaceRequest\nimport com.google.android.libraries.places.api.net.FetchPlaceResponse\nimport com.google.android.libraries.places.api.net.PlacesClient\nimport com.google.places.data.PlaceIdProvider\nimport com.bumptech.glide.Glide\nimport com.google.places.databinding.ActivityPlacePhotosBinding\n\nclass PlacePhotosActivity : AppCompatActivity() {\n\n    private lateinit var placesClient: PlacesClient\n    private lateinit var binding: ActivityPlacePhotosBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityPlacePhotosBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        supportActionBar?.title = \"$title (Kotlin)\"\n\n        placesClient = (application as MainApplication).getPlacesClient()\n\n        binding.placePhotosButton.setOnClickListener { getPlacePhoto() }\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == android.R.id.home) {\n            finish()\n            return true\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun getPlacePhoto() {\n        // [START maps_places_get_place_photos]\n        // Define a Place ID.\n        val placeId = PlaceIdProvider.getRandomPlaceId()\n\n        // Specify fields. Requests for photos must always have the PHOTO_METADATAS field.\n        val fields = listOf(Place.Field.PHOTO_METADATAS)\n\n        // Get a Place object (this example uses fetchPlace(), but you can also use findCurrentPlace())\n        val placeRequest = FetchPlaceRequest.newInstance(placeId, fields)\n\n        placesClient.fetchPlace(placeRequest)\n            .addOnSuccessListener { response: FetchPlaceResponse ->\n                val place = response.place\n\n                // Get the photo metadata.\n                val metadata = place.photoMetadatas\n                if (metadata == null || metadata.isEmpty()) {\n                    Log.w(TAG, \"No photo metadata.\")\n                    return@addOnSuccessListener\n                }\n                val photoMetadata = metadata.first()\n\n                // Get the attribution text.\n                val attributions = photoMetadata?.attributions\n\n                binding.placePhotosAttributions.text = attributions\n\n                // Create a FetchResolvedPhotoUriRequest.\n                val photoRequest = FetchResolvedPhotoUriRequest.builder(photoMetadata)\n                    .setMaxWidth(500) // Optional.\n                    .setMaxHeight(300) // Optional.\n                    .build()\n                placesClient.fetchResolvedPhotoUri(photoRequest)\n                    .addOnSuccessListener { fetchPhotoResponse ->\n                        val photoUri = fetchPhotoResponse.uri\n                        Glide.with(this)\n                            .load(photoUri)\n                            .into(binding.placePhotosResult)\n                    }.addOnFailureListener { exception: Exception ->\n                        if (exception is ApiException) {\n                            Log.e(TAG, \"Place not found: \" + exception.message)\n                            val statusCode = exception.statusCode\n                            // TODO: Handle error with given status code.\n                        }\n                    }\n            }\n        // [END maps_places_get_place_photos]\n    }\n\n    companion object {\n        private val TAG = PlacePhotosActivity::class.java.simpleName\n    }\n}\n"
  },
  {
    "path": "snippets/src/main/java/com/google/places/kotlin/PlacesIconActivity.kt",
    "content": "// Copyright 2023 Google LLC\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.places.kotlin\n\nimport android.graphics.Color\nimport android.os.Bundle\nimport android.view.MenuItem\nimport androidx.appcompat.app.AppCompatActivity\nimport com.bumptech.glide.Glide\nimport com.google.android.libraries.places.api.model.Place\nimport com.google.places.databinding.ActivityPlacesIconBinding\n\nclass PlacesIconActivity : AppCompatActivity() {\n    private lateinit var binding: ActivityPlacesIconBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityPlacesIconBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        supportActionBar?.title = \"$title (Kotlin)\"\n\n        binding.placesIconButton.setOnClickListener {\n            // In a real app, you would get a Place object from a Place Details request or similar.\n            // For this snippet, we'll create a dummy Place object.\n            val place = Place.builder()\n                .setIconBackgroundColor(Color.BLUE)\n                .setIconMaskUrl(\"https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/generic_business-71.png\")\n                .build()\n            getPlacesIcon(place)\n        }\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == android.R.id.home) {\n            finish()\n            return true\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun getPlacesIcon(place: Place) {\n        // [START maps_places_places_icon_and_bg_color]\n        // Set the image view's background color to match the place's icon background color\n        val bgColor = place.iconBackgroundColor ?: Color.TRANSPARENT\n        binding.placesIconResult.setBackgroundColor(bgColor)\n\n        // Fetch the icon using Glide and set the result in the image view\n        Glide.with(this)\n            .load(place.iconMaskUrl)\n            .into(binding.placesIconResult)\n        // [END maps_places_places_icon_and_bg_color]\n    }\n}"
  },
  {
    "path": "snippets/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<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": "snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<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=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\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=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "snippets/src/main/res/layout/activity_current_place.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".CurrentPlaceActivity\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <com.google.android.material.appbar.MaterialToolbar\n            android:id=\"@+id/toolbar\"\n            style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:buttonGravity=\"center_vertical\"\n            app:title=\"@string/current_place_title\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/places_recycler_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:padding=\"8dp\"\n        app:layout_constraintBottom_toTopOf=\"@id/current_place_button\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/app_bar\"\n        tools:listitem=\"@layout/list_item_place\" />\n\n    <Button\n        android:id=\"@+id/current_place_button\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:text=\"Get Current Place\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n\n    <ProgressBar\n        android:id=\"@+id/progress_bar\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\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_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "snippets/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<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:id=\"@+id/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <com.google.android.material.appbar.MaterialToolbar\n            android:id=\"@+id/toolbar\"\n            style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:buttonGravity=\"center_vertical\"\n            app:title=\"@string/main_activity_title\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\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/app_bar\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "snippets/src/main/res/layout/activity_place_autocomplete.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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:id=\"@+id/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <com.google.android.material.appbar.MaterialToolbar\n            android:id=\"@+id/toolbar\"\n            style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:buttonGravity=\"center_vertical\"\n            app:title=\"@string/place_autocomplete_title\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.core.widget.NestedScrollView\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/app_bar\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"16dp\">\n\n            <androidx.fragment.app.FragmentContainerView\n                android:id=\"@+id/autocomplete_fragment\"\n                android:name=\"com.google.android.libraries.places.widget.AutocompleteSupportFragment\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"16dp\" />\n\n            <com.google.android.material.switchmaterial.SwitchMaterial\n                android:id=\"@+id/use_restriction_switch\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"Use Location Restriction (instead of Bias)\" />\n\n            <Button\n                android:id=\"@+id/autocomplete_intent_button\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/start_autocomplete_intent\" />\n\n            <EditText\n                android:id=\"@+id/autocomplete_query\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/enter_a_query\" />\n\n            <Button\n                android:id=\"@+id/programmatic_autocomplete_button\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/get_programmatic_predictions\" />\n\n            <TextView\n                android:id=\"@+id/autocomplete_result\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"16dp\" />\n        </LinearLayout>\n    </androidx.core.widget.NestedScrollView>\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "snippets/src/main/res/layout/activity_place_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <com.google.android.material.appbar.MaterialToolbar\n            android:id=\"@+id/toolbar\"\n            style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:buttonGravity=\"center_vertical\"\n            app:title=\"@string/place_details_title\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.core.widget.NestedScrollView\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/app_bar\">\n\n        <com.google.android.material.card.MaterialCardView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"8dp\"\n            app:cardCornerRadius=\"8dp\"\n            app:cardElevation=\"4dp\">\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/place_name\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?attr/textAppearanceHeadline6\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    tools:text=\"Place Name\" />\n\n                <TextView\n                    android:id=\"@+id/place_address\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"4dp\"\n                    android:textAppearance=\"?attr/textAppearanceBody2\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toBottomOf=\"@id/place_name\"\n                    tools:text=\"123 Main St, Anytown, USA\" />\n\n                <TextView\n                    android:id=\"@+id/place_location\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"4dp\"\n                    android:textAppearance=\"?attr/textAppearanceBody2\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toBottomOf=\"@id/place_address\"\n                    tools:text=\"40.25487, -105.61603\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </com.google.android.material.card.MaterialCardView>\n    </androidx.core.widget.NestedScrollView>\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "snippets/src/main/res/layout/activity_place_is_open.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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:id=\"@+id/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <com.google.android.material.appbar.MaterialToolbar\n            android:id=\"@+id/toolbar\"\n            style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:buttonGravity=\"center_vertical\"\n            app:title=\"@string/place_is_open\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.core.widget.NestedScrollView\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/app_bar\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"16dp\">\n\n            <Button\n                android:id=\"@+id/is_open_by_object_button\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/is_open_by_place_object\" />\n\n            <TextView\n                android:id=\"@+id/is_open_by_object_result\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n\n            <Button\n                android:id=\"@+id/is_open_by_id_button\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/is_open_by_place_id\" />\n\n            <TextView\n                android:id=\"@+id/is_open_by_id_result\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n        </LinearLayout>\n    </androidx.core.widget.NestedScrollView>\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "snippets/src/main/res/layout/activity_place_photos.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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:id=\"@+id/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <com.google.android.material.appbar.MaterialToolbar\n            android:id=\"@+id/toolbar\"\n            style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:buttonGravity=\"center_vertical\"\n            app:title=\"@string/place_photos_title\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.core.widget.NestedScrollView\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/app_bar\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"16dp\">\n\n            <Button\n                android:id=\"@+id/place_photos_button\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"Get Place Photos\" />\n\n            <ImageView\n                android:id=\"@+id/place_photos_result\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:contentDescription=\"@string/place_photo\" />\n\n            <TextView\n                android:id=\"@+id/place_photos_attributions\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n        </LinearLayout>\n    </androidx.core.widget.NestedScrollView>\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "snippets/src/main/res/layout/activity_places_icon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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:id=\"@+id/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <com.google.android.material.appbar.MaterialToolbar\n            android:id=\"@+id/toolbar\"\n            style=\"@style/Widget.MaterialComponents.Toolbar.Primary\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:buttonGravity=\"center_vertical\"\n            app:title=\"@string/places_icon_title\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.core.widget.NestedScrollView\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/app_bar\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"16dp\">\n\n            <Button\n                android:id=\"@+id/places_icon_button\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"Get Places Icon\" />\n\n            <ImageView\n                android:id=\"@+id/places_icon_result\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n        </LinearLayout>\n    </androidx.core.widget.NestedScrollView>\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "snippets/src/main/res/layout/list_item_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\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/activity_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/activity_description\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "snippets/src/main/res/layout/list_item_place.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<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=\"8dp\"\n    app:cardCornerRadius=\"8dp\"\n    app:cardElevation=\"4dp\">\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/place_name\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"?attr/textAppearanceHeadline6\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:text=\"Place Name\" />\n\n        <TextView\n            android:id=\"@+id/place_address\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"4dp\"\n            android:textAppearance=\"?attr/textAppearanceBody2\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/place_name\"\n            tools:text=\"123 Main St, Anytown, USA\" />\n\n        <TextView\n            android:id=\"@+id/place_likelihood\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:textAppearance=\"?attr/textAppearanceCaption\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/place_address\"\n            tools:text=\"Likelihood: 99%\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<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</adaptive-icon>"
  },
  {
    "path": "snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<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</adaptive-icon>"
  },
  {
    "path": "snippets/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2020 Google LLC\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<resources>\n    <color name=\"colorPrimary\">#6200EE</color>\n    <color name=\"colorPrimaryDark\">#3700B3</color>\n    <color name=\"colorAccent\">#03DAC5</color>\n</resources>"
  },
  {
    "path": "snippets/src/main/res/values/strings.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<resources>\n    <string name=\"app_name\">Snippets App</string>\n    <string name=\"place_is_open\">Place Is Open</string>\n    <string name=\"is_open_by_object\">%1$s is open: %2$s</string>\n    <string name=\"is_open_by_id\">Is open by ID: %1$s</string>\n    <string name=\"start_autocomplete_intent\">Start Autocomplete Intent</string>\n    <string name=\"current_place_likelihood\">Place \\'%1$s\\' has likelihood: %2$f\\nAddress: %3$s</string>\n    <string name=\"place_not_found\">Place not found: %1$s</string>\n    <string name=\"place_selection\">Place: %1$s, %2$s\\n%3$s</string>\n    <string name=\"place_location\">%1$.5f, %2$.5f</string>\n    <string name=\"an_error_occurred\">An error occurred: %1$s</string>\n    <string name=\"user_canceled_autocomplete\">User canceled autocomplete</string>\n    <string name=\"place_found\">Place found: %1$s</string>\n    <string name=\"current_place_title\">Current Place</string>\n    <string name=\"place_autocomplete_title\">Place Autocomplete</string>\n    <string name=\"place_details_title\">Place Details</string>\n    <string name=\"place_photos_title\">Place Photos</string>\n    <string name=\"places_icon_title\">Places Icon</string>\n    <string name=\"main_activity_title\">Snippets</string>\n    <string name=\"current_place_description\">Demonstrates how to find the user\\'s current place.</string>\n    <string name=\"place_autocomplete_description\">Demonstrates both the autocomplete fragment and intent.</string>\n    <string name=\"place_details_description\">Demonstrates how to get the details of a place.</string>\n    <string name=\"place_photos_description\">Demonstrates how to get photos of a place.</string>\n    <string name=\"places_icon_description\">Demonstrates how to get the icon of a place.</string>\n    <string name=\"place_is_open_description\">Demonstrates how to check if a place is open.</string>\n    <string name=\"permission_denied\">Location permission denied. This feature is unavailable.</string>\n    <string name=\"enter_a_query\">Enter a query</string>\n    <string name=\"get_programmatic_predictions\">Get Programmatic Predictions</string>\n    <string name=\"is_open_by_place_object\">Is Open by Place Object</string>\n    <string name=\"is_open_by_place_id\">Is Open by Place ID</string>\n    <string name=\"place_photo\">place photo</string>\n    <string name=\"is_open\">Open</string>\n    <string name=\"is_closed\">Closed</string>\n</resources>"
  },
  {
    "path": "snippets/src/main/res/values/styles.xml",
    "content": "<!--\n Copyright 2020 Google LLC\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<resources>\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n        <!-- Other theme attributes -->\n\n        <!-- Make status and navigation bars transparent -->\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n        <item name=\"android:navigationBarColor\">@android:color/transparent</item>\n\n        <!--\n          Required for edge-to-edge display. This allows the app to draw behind\n          the system bars.\n        -->\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "snippets/src/main/res/values-v27/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2026 Google LLC\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<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n        <!-- Other theme attributes -->\n\n        <!-- Make status and navigation bars transparent -->\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n        <item name=\"android:navigationBarColor\">@android:color/transparent</item>\n\n        <!--\n          Required for edge-to-edge display. This allows the app to draw behind\n          the system bars.\n        -->\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <!--\n                  On API 27+, this allows the app to render in the display cutout area.\n                  'shortEdges' is a good default.\n                -->\n        <item name=\"android:windowLayoutInDisplayCutoutMode\">shortEdges</item>\n    </style>\n</resources>"
  }
]