diff --git a/.github/workflows/apply_spotless.yml b/.github/workflows/apply_spotless.yml
index d69f817b0..3727da63a 100644
--- a/.github/workflows/apply_spotless.yml
+++ b/.github/workflows/apply_spotless.yml
@@ -42,7 +42,7 @@ jobs:
java-version: '17'
- name: Run spotlessApply
- run: ./gradlew spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace
+ run: ./gradlew spotlessApply --stacktrace
- name: Auto-commit if spotlessApply has changes
uses: stefanzweifel/git-auto-commit-action@v5
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 72e815a31..1d3c201ca 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,11 +32,11 @@ jobs:
- uses: actions/checkout@v4
with:
token: ${{ secrets.PAT || github.token }}
- - name: set up Java 17
+ - name: set up Java 25
uses: actions/setup-java@v4
with:
distribution: 'zulu'
- java-version: '17'
+ java-version: '25'
- name: Build All
run: ./gradlew build --stacktrace
- name: Build Watch Face Push validation snippets
diff --git a/.gitignore b/.gitignore
index b984a9c26..7b190dc83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,16 +1,11 @@
*.iml
.gradle
/local.properties
-/.idea/caches/build_file_checksums.ser
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
+.idea/
.DS_Store
build
/captures
.externalNativeBuild
-.idea/*
-/.idea/*
.kotlin
### Xcode ###
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index ae78c113f..000000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/bluetoothle/src/main/AndroidManifest.xml b/bluetoothle/src/main/AndroidManifest.xml
index d15b213e2..b42865576 100644
--- a/bluetoothle/src/main/AndroidManifest.xml
+++ b/bluetoothle/src/main/AndroidManifest.xml
@@ -1,4 +1,19 @@
+
@@ -24,4 +39,4 @@
-
\ No newline at end of file
+
diff --git a/bluetoothle/src/main/res/layout/activity_main.xml b/bluetoothle/src/main/res/layout/activity_main.xml
index b692bf4a3..b46563beb 100644
--- a/bluetoothle/src/main/res/layout/activity_main.xml
+++ b/bluetoothle/src/main/res/layout/activity_main.xml
@@ -1,4 +1,19 @@
+
-
\ No newline at end of file
+
diff --git a/bluetoothle/src/main/res/values/strings.xml b/bluetoothle/src/main/res/values/strings.xml
index 7abc06d3b..fa7796f20 100644
--- a/bluetoothle/src/main/res/values/strings.xml
+++ b/bluetoothle/src/main/res/values/strings.xml
@@ -1 +1,17 @@
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 899385a15..d4ea18313 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,6 +13,93 @@ plugins {
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.android.kotlin.multiplatform.library) apply false
alias(libs.plugins.android.lint) apply false
+ alias(libs.plugins.spotless) apply false
+}
+
+allprojects {
+ apply(plugin = "com.diffplug.spotless")
+ extensions.configure {
+ kotlin {
+ target("**/*.kt")
+ targetExclude("**/build/**/*.kt", "spotless/**/*.kt")
+
+ val disabledRules = arrayOf(
+ // These rules were introduced in ktlint 0.46.0 and should not be
+ // enabled without further discussion. They are disabled for now.
+ // See: https://github.com/pinterest/ktlint/releases/tag/0.46.0
+ "filename",
+ "annotation",
+ "annotation-spacing",
+ "argument-list-wrapping",
+ "double-colon-spacing",
+ "enum-entry-name-case",
+ "multiline-if-else",
+ "no-empty-first-line-in-method-block",
+ "package-name",
+ "trailing-comma",
+ "spacing-around-angle-brackets",
+ "spacing-between-declarations-with-annotations",
+ "spacing-between-declarations-with-comments",
+ "unary-op-spacing",
+ "no-trailing-spaces",
+ "max-line-length",
+ // Disabled rules that were introduced or changed between 0.46.0 ~ 1.50.0
+ "class-signature",
+ "trailing-comma-on-call-site",
+ "trailing-comma-on-declaration-site",
+ "comment-wrapping",
+ "function-literal",
+ "function-signature",
+ "function-expression-body",
+ "function-start-of-body-spacing",
+ "multiline-expression-wrapping",
+ )
+
+ ktlint(libs.versions.ktlint.get()).editorConfigOverride(
+ mapOf(
+ "android" to "true",
+ "ktlint_code_style" to "android_studio",
+ "ij_kotlin_allow_trailing_comma" to "true",
+ ) + disabledRules.map { Pair("ktlint_standard_$it", "disabled") }
+ )
+
+ // ktlint 7.0.0 introduces lints, which existing snippets do not satisfy
+ val kotlinSuppressLints = arrayOf(
+ "standard:function-naming",
+ "standard:property-naming",
+ "standard:class-naming",
+ "standard:max-line-length",
+ "standard:comment-wrapping",
+ "standard:import-ordering",
+ "standard:filename",
+ "standard:backing-property-naming",
+ )
+ for (lint in kotlinSuppressLints) {
+ suppressLintsFor {
+ step = "ktlint"
+ shortCode = lint
+ }
+ }
+
+ licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
+ }
+ kotlinGradle {
+ target("**/*.kts")
+ targetExclude("**/build/**/*.kts", "spotless/**/*.kts")
+ // Look for the first line that doesn't have a block comment (assumed to be the license)
+ licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
+ }
+ format("xml") {
+ target("**/*.xml")
+ targetExclude(
+ "**/build/**/*.xml",
+ "spotless/**/*.xml",
+ ".idea/**",
+ )
+ // Look for the root tag or a tag that is a snippet
+ licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[a-zA-Z])|(
+ Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
RecomposeHighlighter
diff --git a/compose/recomposehighlighter/src/main/res/values/themes.xml b/compose/recomposehighlighter/src/main/res/values/themes.xml
index 918929c1b..f94279433 100644
--- a/compose/recomposehighlighter/src/main/res/values/themes.xml
+++ b/compose/recomposehighlighter/src/main/res/values/themes.xml
@@ -1,5 +1,21 @@
+
+
-
\ No newline at end of file
+
diff --git a/compose/snippets/build.gradle.kts b/compose/snippets/build.gradle.kts
index c8d728da0..7a5062f65 100644
--- a/compose/snippets/build.gradle.kts
+++ b/compose/snippets/build.gradle.kts
@@ -25,7 +25,9 @@ plugins {
}
android {
- compileSdk = libs.versions.compileSdk.get().toInt()
+ compileSdk {
+ version = release(libs.versions.compileSdk.get().toInt())
+ {minorApiLevel = 1}} // Android 16 QPR 2
namespace = "com.example.compose.snippets"
defaultConfig {
@@ -163,4 +165,6 @@ dependencies {
androidTestImplementation(libs.androidx.compose.ui.test.junit4.accessibility)
debugImplementation(libs.androidx.compose.ui.test.manifest)
+ androidTestImplementation(libs.androidx.glance.testing)
+ androidTestImplementation(libs.androidx.glance.appwidget.testing)
}
diff --git a/compose/snippets/lint.xml b/compose/snippets/lint.xml
index 778dd98f3..9ec5c3429 100644
--- a/compose/snippets/lint.xml
+++ b/compose/snippets/lint.xml
@@ -1,6 +1,21 @@
-
+
+
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/androidTest/java/com/example/compose/snippets/glance/GlanceUnitTest.kt b/compose/snippets/src/androidTest/java/com/example/compose/snippets/glance/GlanceUnitTest.kt
new file mode 100644
index 000000000..05698d1a3
--- /dev/null
+++ b/compose/snippets/src/androidTest/java/com/example/compose/snippets/glance/GlanceUnitTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.compose.snippets.glance
+
+import androidx.compose.runtime.Composable
+import androidx.glance.GlanceModifier
+import androidx.glance.appwidget.testing.unit.runGlanceAppWidgetUnitTest
+import androidx.glance.layout.Row
+import androidx.glance.semantics.semantics
+import androidx.glance.semantics.testTag
+import androidx.glance.testing.unit.assertHasText
+import androidx.glance.testing.unit.hasTestTag
+import androidx.glance.text.Text
+import org.junit.Test
+
+// [START android_compose_glance_unit_test]
+private const val FAKE_HEADLINE = "EXTRA! EXTRA! READ ALL ABOUT IT!"
+
+class MyGlanceComposableTest {
+ @Test
+ fun myNewsItemComposable_largeSize_hasHeadline() = runGlanceAppWidgetUnitTest {
+ // Set the composable to test
+ provideComposable {
+ MyNewsItemComposable(FAKE_HEADLINE)
+ }
+
+ // Perform assertions
+ onNode(hasTestTag("headline"))
+ .assertHasText(FAKE_HEADLINE)
+ }
+
+
+ @Composable
+ fun MyNewsItemComposable(headline: String) {
+ Row {
+ Text(
+ text = headline,
+ modifier = GlanceModifier.semantics { testTag = "headline" },
+ )
+ }
+ }
+}
+// [END android_compose_glance_unit_test]
\ No newline at end of file
diff --git a/compose/snippets/src/main/AndroidManifest.xml b/compose/snippets/src/main/AndroidManifest.xml
index 4ec4d9b70..5c0f57b2e 100644
--- a/compose/snippets/src/main/AndroidManifest.xml
+++ b/compose/snippets/src/main/AndroidManifest.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
@@ -70,4 +69,4 @@
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleNavigationSuiteScaffold.kt b/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleNavigationSuiteScaffold.kt
index e8d06f783..6221902cf 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleNavigationSuiteScaffold.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleNavigationSuiteScaffold.kt
@@ -17,6 +17,7 @@
package com.example.compose.snippets.adaptivelayouts
import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.material.icons.filled.Favorite
@@ -28,6 +29,7 @@ import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItem
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
@@ -154,6 +156,34 @@ fun SampleNavigationSuiteScaffoldColors() {
// [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_item_colors]
}
+@Composable
+fun SampleNavigationSuiteScaffoldIconsAlignment() {
+ var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) }
+
+ // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_icons_alignment]
+ NavigationSuiteScaffold(
+ navigationItems = {
+ AppDestinations.entries.forEach {
+ NavigationSuiteItem(
+ icon = {
+ Icon(
+ it.icon,
+ contentDescription = stringResource(it.contentDescription)
+ )
+ },
+ label = { Text(stringResource(it.label)) },
+ selected = it == currentDestination,
+ onClick = { currentDestination = it },
+ )
+ }
+ },
+ navigationItemVerticalArrangement = Arrangement.Center
+ ) {
+ // TODO: Destination content.
+ }
+ // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_icons_alignment]
+}
+
@Composable
fun SampleNavigationSuiteScaffoldCustomType() {
// [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_layout_type]
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt
index 354efe635..72b4586a0 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt
@@ -218,7 +218,7 @@ fun CenterAlignedTopAppBarExample() {
topBar = {
CenterAlignedTopAppBar(
- colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
+ colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
@@ -431,6 +431,7 @@ fun AppBarSelectionActions(
}
}
},
+ modifier = modifier
)
}
// [END android_compose_components_appbarselectionactions]
@@ -454,6 +455,7 @@ private fun AppBarMultiSelectionExample(
var selectedItems by rememberSaveable { mutableStateOf(setOf()) }
Scaffold(
+ modifier = modifier,
topBar = { AppBarSelectionActions(selectedItems) }
) { innerPadding ->
LazyColumn(contentPadding = innerPadding) {
@@ -517,7 +519,8 @@ fun LazyListMultiSelection(
) {
var selectedItems by rememberSaveable { mutableStateOf(setOf()) }
- LazyColumn(contentPadding = contentPadding) {
+ LazyColumn(modifier = modifier,
+ contentPadding = contentPadding) {
itemsIndexed(listItems) { _, index ->
val selected = selectedItems.contains(index)
ListItemSelectable(
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt
index 692199123..4d59ea4ef 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt
@@ -57,7 +57,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
-import com.example.compose.snippets.touchinput.userinteractions.MyAppTheme
+import com.example.compose.snippets.ui.theme.SnippetsTheme
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -65,7 +65,7 @@ import java.util.Locale
@Preview
@Composable
private fun DatePickerPreview() {
- MyAppTheme {
+ SnippetsTheme {
DatePickerExamples()
}
}
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/PullToRefreshBox.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/PullToRefreshBox.kt
index 81280151a..5dbcb9221 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/components/PullToRefreshBox.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/PullToRefreshBox.kt
@@ -32,11 +32,10 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
-import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.PositionalThreshold
import androidx.compose.material3.pulltorefresh.PullToRefreshState
-import androidx.compose.material3.pulltorefresh.pullToRefreshIndicator
+import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -187,11 +186,13 @@ fun MyCustomIndicator(
modifier: Modifier = Modifier,
) {
Box(
- modifier = modifier.pullToRefreshIndicator(
+ modifier = modifier.pullToRefresh(
state = state,
isRefreshing = isRefreshing,
- containerColor = PullToRefreshDefaults.containerColor,
- threshold = PositionalThreshold
+ threshold = PositionalThreshold,
+ onRefresh = {
+
+ }
),
contentAlignment = Alignment.Center
) {
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Tooltips.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Tooltips.kt
index 1066d0cfb..9a034e261 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/components/Tooltips.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Tooltips.kt
@@ -166,7 +166,6 @@ fun AdvancedRichTooltipExample(
}
}
},
- caretSize = DpSize(32.dp, 16.dp)
) {
Text(richTooltipText)
}
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/Mdc3ThemeSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/Mdc3ThemeSnippets.kt
deleted file mode 100644
index 5e3e4000f..000000000
--- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/Mdc3ThemeSnippets.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.compose.snippets.designsystems
-
-import android.os.Bundle
-import androidx.activity.compose.setContent
-import androidx.appcompat.app.AppCompatActivity
-// [START android_compose_designsystems_interop_mdc3theme]
-import com.google.accompanist.themeadapter.material3.Mdc3Theme
-
-class Mdc3ThemeExample : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- // Use Mdc3Theme instead of M3 MaterialTheme
- // Color scheme, typography, and shapes have been read from the
- // View-based theme used in this Activity
- setContent {
- Mdc3Theme {
- // Your app-level composable here
- }
- }
- }
-}
-// [END android_compose_designsystems_interop_mdc3theme]
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceMetrics.kt b/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceMetrics.kt
new file mode 100644
index 000000000..3541cceb4
--- /dev/null
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceMetrics.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.compose.snippets.glance
+
+import android.appwidget.AppWidgetManager
+import android.content.Context
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+
+private const val TAG = "WidgetMetrics"
+
+class GlanceMetrics {
+
+ // [START android_compose_glance_metrics]
+ @RequiresApi(Build.VERSION_CODES_FULL.BAKLAVA_1)
+ fun getWidgetEngagementMetrics(context: Context) {
+ val manager = AppWidgetManager.getInstance(context)
+
+ val endTime = System.currentTimeMillis()
+ val startTime = endTime - (24 * 60 * 60 * 1000) // a day ago
+
+ val events = manager.queryAppWidgetEvents(startTime, endTime)
+
+ if (events.isEmpty()) {
+ Log.d(TAG, "No events found for the given time range.")
+ }
+
+ val metrics = hashMapOf(
+ "clicks" to 0L,
+ "scrolls" to 0L,
+ "totalImpressionLength" to 0L
+ )
+
+ for (event in events) {
+
+ Log.d(TAG, "Event Start: ${event.start}")
+ Log.d(TAG, "Event End: ${event.end}")
+
+ val widgetId = event.appWidgetId
+
+ // Tap actions
+ val clickedIds = event.clickedIds
+ if (clickedIds?.isNotEmpty() == true) {
+ metrics["clicks"] = metrics.getValue("clicks") + clickedIds.size
+ // Log or analyze which components were clicked.
+ for (id in clickedIds) {
+ Log.d(TAG, "Widget $widgetId: Tap event on component with ID $id")
+ }
+ }
+
+ // Scroll events
+ val scrolledIds = event.scrolledIds
+ if (scrolledIds?.isNotEmpty() == true) {
+ metrics["scrolls"] = metrics.getValue("scrolls") + scrolledIds.size
+ // Log or analyze which lists were scrolled.
+ for (id in scrolledIds) {
+ Log.d(TAG, "Widget $widgetId: Scroll event in list with ID/tag $id")
+ }
+ }
+
+ // Impressions
+ metrics["totalImpressionLength"] = metrics.getValue("totalImpressionLength") + event.visibleDuration.toMillis()
+ Log.d(
+ TAG,
+ "Widget $widgetId: Impression event with duration " + event.visibleDuration.toMillis() + "ms"
+ )
+
+ // Position
+ val position = event.position
+ if (position != null) {
+ Log.d(
+ TAG,
+ "Widget $widgetId: left=${position.left}, right=${position.right}, top=${position.top}, bottom=${position.bottom}"
+ )
+ }
+ }
+ Log.d("WidgetMetrics", "Metrics: $metrics")
+ }
+ // [END android_compose_glance_metrics]
+}
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/interop/MigrationCommonScenariosSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/interop/MigrationCommonScenariosSnippets.kt
index 59ac0129d..fec60d697 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/interop/MigrationCommonScenariosSnippets.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/interop/MigrationCommonScenariosSnippets.kt
@@ -58,7 +58,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/FlowLayoutSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/FlowLayoutSnippets.kt
index 60e795874..8c682a601 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/FlowLayoutSnippets.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/FlowLayoutSnippets.kt
@@ -21,9 +21,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.ContextualFlowRow
-import androidx.compose.foundation.layout.ContextualFlowRowOverflow
-import androidx.compose.foundation.layout.ContextualFlowRowOverflowScope
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowColumn
import androidx.compose.foundation.layout.FlowRow
@@ -33,22 +30,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
@@ -465,48 +453,6 @@ fun FlowLayout_FractionalSizing() {
// [END android_compose_flow_layout_fractional_sizing]
}
-@OptIn(ExperimentalLayoutApi::class)
-@Preview
-@Composable
-fun ContextualFlowLayoutExample() {
- // [START android_compose_layouts_contextual_flow]
- val totalCount = 40
- var maxLines by remember {
- mutableIntStateOf(2)
- }
-
- val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope ->
- val remainingItems = totalCount - scope.shownItemCount
- ChipItem(if (remainingItems == 0) "Less" else "+$remainingItems", onClick = {
- if (remainingItems == 0) {
- maxLines = 2
- } else {
- maxLines += 5
- }
- })
- }
- ContextualFlowRow(
- modifier = Modifier
- .safeDrawingPadding()
- .fillMaxWidth(1f)
- .padding(16.dp)
- .wrapContentHeight(align = Alignment.Top)
- .verticalScroll(rememberScrollState()),
- verticalArrangement = Arrangement.spacedBy(4.dp),
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- maxLines = maxLines,
- overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
- minRowsToShowCollapse = 4,
- expandIndicator = moreOrCollapseIndicator,
- collapseIndicator = moreOrCollapseIndicator
- ),
- itemCount = totalCount
- ) { index ->
- ChipItem("Item $index")
- }
- // [END android_compose_layouts_contextual_flow]
-}
-
@OptIn(ExperimentalLayoutApi::class)
@Preview
@Composable
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/InsetsSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/InsetsSnippets.kt
index 031d78dfb..e893e8099 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/InsetsSnippets.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/InsetsSnippets.kt
@@ -31,6 +31,8 @@ import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.fitInside
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars
@@ -49,9 +51,12 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.BottomCenter
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.RectRulers
import androidx.compose.ui.layout.WindowInsetsRulers
+import androidx.compose.ui.layout.innermostOf
import androidx.compose.ui.layout.layout
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Constraints
@@ -208,3 +213,53 @@ fun Modifier.alignToSafeDrawing(): Modifier {
}
}
// [END android_compose_insets_rulers]
+
+// [START android_compose_insets_fit_inside]
+@Composable
+fun FitInsideDemo(modifier: Modifier) {
+ Box(
+ modifier = modifier
+ .fillMaxSize()
+ // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc...
+ .fitInside(WindowInsetsRulers.SafeDrawing.current)
+ )
+}
+// [END android_compose_insets_fit_inside]
+
+// [START android_compose_insets_rulers_ime]
+@Composable
+fun FitInsideWithImeDemo(modifier: Modifier) {
+ Box(
+ modifier = modifier
+ .fillMaxSize()
+ .fitInside(
+ RectRulers.innermostOf(
+ WindowInsetsRulers.NavigationBars.current,
+ WindowInsetsRulers.Ime.current
+ )
+ )
+ ) {
+ TextField(
+ value = "Demo IME Insets",
+ onValueChange = {},
+ modifier = modifier.align(Alignment.BottomStart).fillMaxWidth()
+ )
+ }
+}
+// [END android_compose_insets_rulers_ime]
+
+// [START android_compose_insets_rulers_status_caption_bars]
+@Composable
+fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) {
+ Box(
+ modifier = modifier
+ .fillMaxSize()
+ .fitInside(
+ RectRulers.innermostOf(
+ WindowInsetsRulers.StatusBars.current,
+ WindowInsetsRulers.CaptionBar.current
+ )
+ )
+ )
+}
+// [END android_compose_insets_rulers_status_caption_bars]
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/PagerSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/PagerSnippets.kt
index 899163367..e09fe456d 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/PagerSnippets.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/PagerSnippets.kt
@@ -50,8 +50,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Tab
-import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -239,7 +239,7 @@ fun PagerWithTabsExample() {
pages.size
})
- TabRow(
+ PrimaryTabRow(
// Our selected tab is our current page
selectedTabIndex = pagerState.currentPage,
) {
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/resources/ResourcesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/resources/ResourcesSnippets.kt
index 0a2f34a56..f964e352d 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/resources/ResourcesSnippets.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/resources/ResourcesSnippets.kt
@@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material3.Divider
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -109,7 +110,7 @@ fun Colors() {
// #FFBB86FC
// In your Compose code
- Divider(color = colorResource(R.color.purple_200))
+ HorizontalDivider(color = colorResource(R.color.purple_200))
// [END android_compose_resources_colors]
}
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/state/StateHoistingSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/state/StateHoistingSnippets.kt
index 2a4f0c949..2e607fde9 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/state/StateHoistingSnippets.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/state/StateHoistingSnippets.kt
@@ -18,6 +18,8 @@
package com.example.compose.snippets.state
+import android.R.id.message
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
@@ -34,7 +36,13 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.withLink
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewModelScope
@@ -60,9 +68,11 @@ private object StateHoistingSnippets1 {
) {
var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state
- ClickableText(
+ Text(
text = AnnotatedString(message.content),
- onClick = { showDetails = !showDetails } // Apply simple UI logic
+ modifier = Modifier.clickable {
+ showDetails = !showDetails // Apply UI logic
+ }
)
if (showDetails) {
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/userinteractions/UserInteractions.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/userinteractions/UserInteractions.kt
index 12de9f923..6b9fa3fda 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/userinteractions/UserInteractions.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/userinteractions/UserInteractions.kt
@@ -37,7 +37,6 @@ import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.LocalUseFallbackRippleImplementation
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -240,28 +239,6 @@ private class ScaleIndicationNode(
fun App() {
}
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun LocalUseFallbackRippleImplementationExample() {
-// [START android_compose_userinteractions_localusefallbackrippleimplementation]
- CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
- MaterialTheme {
- App()
- }
- }
-// [END android_compose_userinteractions_localusefallbackrippleimplementation]
-}
-
-// [START android_compose_userinteractions_localusefallbackrippleimplementation_app_theme]
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MyAppTheme(content: @Composable () -> Unit) {
- CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
- MaterialTheme(content = content)
- }
-}
-// [END android_compose_userinteractions_localusefallbackrippleimplementation_app_theme]
-
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun MyComposableDisabledRippleConfig() {
diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/ui/theme/Type.kt b/compose/snippets/src/main/java/com/example/compose/snippets/ui/theme/Type.kt
index acf007bfd..ab604e852 100644
--- a/compose/snippets/src/main/java/com/example/compose/snippets/ui/theme/Type.kt
+++ b/compose/snippets/src/main/java/com/example/compose/snippets/ui/theme/Type.kt
@@ -46,5 +46,5 @@ val Typography = Typography(
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
- */
+ */
)
diff --git a/compose/snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml b/compose/snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml
index 2f9758487..0f6026a41 100644
--- a/compose/snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ b/compose/snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -1,19 +1,19 @@
+
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/drawable/baseline_directions_bus_24.xml b/compose/snippets/src/main/res/drawable/baseline_directions_bus_24.xml
index 2de5d0a86..745eab18a 100644
--- a/compose/snippets/src/main/res/drawable/baseline_directions_bus_24.xml
+++ b/compose/snippets/src/main/res/drawable/baseline_directions_bus_24.xml
@@ -1,19 +1,19 @@
+
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
diff --git a/compose/snippets/src/main/res/drawable/baseline_shopping_cart_24.xml b/compose/snippets/src/main/res/drawable/baseline_shopping_cart_24.xml
index 817fb0539..15368afa6 100644
--- a/compose/snippets/src/main/res/drawable/baseline_shopping_cart_24.xml
+++ b/compose/snippets/src/main/res/drawable/baseline_shopping_cart_24.xml
@@ -1,19 +1,19 @@
+
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
diff --git a/compose/snippets/src/main/res/drawable/button_outline.xml b/compose/snippets/src/main/res/drawable/button_outline.xml
index f4eb02291..790fd2eb2 100644
--- a/compose/snippets/src/main/res/drawable/button_outline.xml
+++ b/compose/snippets/src/main/res/drawable/button_outline.xml
@@ -1,22 +1,22 @@
+
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/drawable/fast_forward.xml b/compose/snippets/src/main/res/drawable/fast_forward.xml
index d49dffbf3..95cdea9c4 100644
--- a/compose/snippets/src/main/res/drawable/fast_forward.xml
+++ b/compose/snippets/src/main/res/drawable/fast_forward.xml
@@ -1,3 +1,19 @@
+
+
+
+
+
+
+
diff --git a/compose/snippets/src/main/res/drawable/ic_launcher_background.xml b/compose/snippets/src/main/res/drawable/ic_launcher_background.xml
index 4c2360d21..2edadf928 100644
--- a/compose/snippets/src/main/res/drawable/ic_launcher_background.xml
+++ b/compose/snippets/src/main/res/drawable/ic_launcher_background.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
diff --git a/compose/snippets/src/main/res/drawable/ic_moon_24.xml b/compose/snippets/src/main/res/drawable/ic_moon_24.xml
index 527559f3f..b1c551d5e 100644
--- a/compose/snippets/src/main/res/drawable/ic_moon_24.xml
+++ b/compose/snippets/src/main/res/drawable/ic_moon_24.xml
@@ -1,3 +1,19 @@
+
+
diff --git a/compose/snippets/src/main/res/drawable/ic_sun_24.xml b/compose/snippets/src/main/res/drawable/ic_sun_24.xml
index a63c6400e..4bac89d3e 100644
--- a/compose/snippets/src/main/res/drawable/ic_sun_24.xml
+++ b/compose/snippets/src/main/res/drawable/ic_sun_24.xml
@@ -1,3 +1,19 @@
+
+
diff --git a/compose/snippets/src/main/res/layout/activity_example.xml b/compose/snippets/src/main/res/layout/activity_example.xml
index be37892ac..b85c61cf1 100644
--- a/compose/snippets/src/main/res/layout/activity_example.xml
+++ b/compose/snippets/src/main/res/layout/activity_example.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/layout/example_layout.xml b/compose/snippets/src/main/res/layout/example_layout.xml
index d77d31543..91e49c045 100644
--- a/compose/snippets/src/main/res/layout/example_layout.xml
+++ b/compose/snippets/src/main/res/layout/example_layout.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/layout/example_view.xml b/compose/snippets/src/main/res/layout/example_view.xml
index 77fff6e8c..86bdacae8 100644
--- a/compose/snippets/src/main/res/layout/example_view.xml
+++ b/compose/snippets/src/main/res/layout/example_view.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
diff --git a/compose/snippets/src/main/res/layout/fragment_example.xml b/compose/snippets/src/main/res/layout/fragment_example.xml
index fd65463ef..d5517e3c4 100644
--- a/compose/snippets/src/main/res/layout/fragment_example.xml
+++ b/compose/snippets/src/main/res/layout/fragment_example.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/layout/interop_layout_preview_composable.xml b/compose/snippets/src/main/res/layout/interop_layout_preview_composable.xml
index bb28c5685..b3a672cbe 100644
--- a/compose/snippets/src/main/res/layout/interop_layout_preview_composable.xml
+++ b/compose/snippets/src/main/res/layout/interop_layout_preview_composable.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/layout/migration_strategy_existing_screens.xml b/compose/snippets/src/main/res/layout/migration_strategy_existing_screens.xml
index 2ef7e2dcb..a7bd893b7 100644
--- a/compose/snippets/src/main/res/layout/migration_strategy_existing_screens.xml
+++ b/compose/snippets/src/main/res/layout/migration_strategy_existing_screens.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/layout/migration_strategy_xml_example.xml b/compose/snippets/src/main/res/layout/migration_strategy_xml_example.xml
index ac462f26c..e5ed1b289 100644
--- a/compose/snippets/src/main/res/layout/migration_strategy_xml_example.xml
+++ b/compose/snippets/src/main/res/layout/migration_strategy_xml_example.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/layout/my_container_view.xml b/compose/snippets/src/main/res/layout/my_container_view.xml
index 4d0ecb396..cec8feedc 100644
--- a/compose/snippets/src/main/res/layout/my_container_view.xml
+++ b/compose/snippets/src/main/res/layout/my_container_view.xml
@@ -1,21 +1,21 @@
+
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/layout/my_fragment_layout.xml b/compose/snippets/src/main/res/layout/my_fragment_layout.xml
index d2428b777..3b0ec2765 100644
--- a/compose/snippets/src/main/res/layout/my_fragment_layout.xml
+++ b/compose/snippets/src/main/res/layout/my_fragment_layout.xml
@@ -1,24 +1,23 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/layout/touchinput_gestures_nested_scroll_interop.xml b/compose/snippets/src/main/res/layout/touchinput_gestures_nested_scroll_interop.xml
index bbc6128e7..229f37a13 100644
--- a/compose/snippets/src/main/res/layout/touchinput_gestures_nested_scroll_interop.xml
+++ b/compose/snippets/src/main/res/layout/touchinput_gestures_nested_scroll_interop.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 0b0b5355c..05f11b4d6 100644
--- a/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,22 +1,21 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 0b0b5355c..05f11b4d6 100644
--- a/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,22 +1,21 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/values-es/strings.xml b/compose/snippets/src/main/res/values-es/strings.xml
index 3ba327f49..7287beb35 100644
--- a/compose/snippets/src/main/res/values-es/strings.xml
+++ b/compose/snippets/src/main/res/values-es/strings.xml
@@ -1,19 +1,19 @@
+
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
snippets
Golden Retriever in fall leaves
@@ -53,4 +53,4 @@
Compras
Perfil
Esto es sólo un texto de marcador de posición.
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/values/colors.xml b/compose/snippets/src/main/res/values/colors.xml
index 0205675f4..55242e03e 100644
--- a/compose/snippets/src/main/res/values/colors.xml
+++ b/compose/snippets/src/main/res/values/colors.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
#FFBB86FC
#FF6200EE
@@ -25,4 +24,4 @@
#FFFFFFFF
#FFF
#FFF
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/values/dimens.xml b/compose/snippets/src/main/res/values/dimens.xml
index d8dec0639..99c6f584a 100644
--- a/compose/snippets/src/main/res/values/dimens.xml
+++ b/compose/snippets/src/main/res/values/dimens.xml
@@ -1,20 +1,19 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
8dp
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/values/ids.xml b/compose/snippets/src/main/res/values/ids.xml
index f97cf8a5b..6cc94fcf1 100644
--- a/compose/snippets/src/main/res/values/ids.xml
+++ b/compose/snippets/src/main/res/values/ids.xml
@@ -1,23 +1,22 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/values/strings.xml b/compose/snippets/src/main/res/values/strings.xml
index 02254e29a..d4b9a63a6 100644
--- a/compose/snippets/src/main/res/values/strings.xml
+++ b/compose/snippets/src/main/res/values/strings.xml
@@ -1,19 +1,19 @@
+
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
snippets
Golden Retriever in fall leaves
@@ -55,4 +55,4 @@
This is just a placeholder.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/values/themes.xml b/compose/snippets/src/main/res/values/themes.xml
index 3d9f463cd..9e7318559 100644
--- a/compose/snippets/src/main/res/values/themes.xml
+++ b/compose/snippets/src/main/res/values/themes.xml
@@ -1,21 +1,20 @@
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/compose/snippets/src/main/res/xml/my_app_widget_info.xml b/compose/snippets/src/main/res/xml/my_app_widget_info.xml
index a1a7f5da2..75a4b70f6 100644
--- a/compose/snippets/src/main/res/xml/my_app_widget_info.xml
+++ b/compose/snippets/src/main/res/xml/my_app_widget_info.xml
@@ -1,21 +1,21 @@
+
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
-
\ No newline at end of file
+
diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts
deleted file mode 100644
index 8a852469a..000000000
--- a/gradle/init.gradle.kts
+++ /dev/null
@@ -1,52 +0,0 @@
-val ktlintVersion = "0.43.0"
-
-initscript {
- val spotlessVersion = "6.11.0"
-
- repositories {
- mavenCentral()
- }
-
- dependencies {
- classpath("com.diffplug.spotless:spotless-plugin-gradle:$spotlessVersion")
- }
-}
-
-rootProject {
- subprojects {
- apply()
- extensions.configure {
- kotlin {
- target("**/*.kt")
- targetExclude("**/build/**/*.kt")
- ktlint(ktlintVersion).userData(
- mapOf(
- "android" to "true",
- "ktlint_code_style" to "android",
- "ij_kotlin_allow_trailing_comma" to "true",
- // These rules were introduced in ktlint 0.46.0 and should not be
- // enabled without further discussion. They are disabled for now.
- // See: https://github.com/pinterest/ktlint/releases/tag/0.46.0
- "disabled_rules" to
- "filename," +
- "annotation,annotation-spacing," +
- "argument-list-wrapping," +
- "double-colon-spacing," +
- "enum-entry-name-case," +
- "multiline-if-else," +
- "no-empty-first-line-in-method-block," +
- "package-name," +
- "trailing-comma," +
- "spacing-around-angle-brackets," +
- "spacing-between-declarations-with-annotations," +
- "spacing-between-declarations-with-comments," +
- "unary-op-spacing," +
- "no-trailing-spaces," +
- "max-line-length"
- )
- )
- licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
- }
- }
- }
-}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8bab1c791..5517bdcc1 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,82 +1,89 @@
[versions]
accompanist = "0.36.0"
-activityKtx = "1.10.1"
+activityKtx = "1.11.0"
android-googleid = "1.1.1"
-androidGradlePlugin = "8.12.1"
-androidx-activity-compose = "1.10.1"
+androidGradlePlugin = "8.13.0"
+androidx-activity-compose = "1.11.0"
androidx-appcompat = "1.7.0"
-androidx-compose-bom = "2025.08.00"
+androidx-compose-bom = "2025.10.00"
androidx-compose-ui-test = "1.7.0-alpha08"
-androidx-compose-ui-test-junit4-accessibility = "1.10.0-alpha01"
+androidx-compose-ui-test-junit4-accessibility = "1.10.0-alpha05"
androidx-constraintlayout = "2.2.1"
androidx-constraintlayout-compose = "1.1.1"
androidx-coordinator-layout = "1.3.0"
androidx-corektx = "1.17.0"
androidx-credentials = "1.5.0"
androidx-credentials-play-services-auth = "1.5.0"
-androidx-emoji2-views = "1.5.0"
+androidx-emoji2-views = "1.6.0"
androidx-fragment-ktx = "1.8.9"
androidx-glance-appwidget = "1.1.1"
-androidx-lifecycle-compose = "2.9.2"
-androidx-lifecycle-runtime-compose = "2.9.2"
-androidx-navigation = "2.9.3"
+androidx-lifecycle-compose = "2.9.4"
+androidx-lifecycle-runtime-compose = "2.9.4"
+androidx-navigation = "2.9.5"
androidx-paging = "3.3.6"
androidx-startup-runtime = "1.2.0"
androidx-test = "1.7.0"
androidx-test-espresso = "3.7.0"
androidx-test-junit = "1.3.0"
-androidx-window = "1.5.0-beta02"
-androidx-window-core = "1.5.0-beta02"
-androidx-window-java = "1.5.0-beta02"
-androidx-xr-arcore = "1.0.0-alpha05"
-androidx-xr-compose = "1.0.0-alpha06"
-androidx-xr-scenecore = "1.0.0-alpha06"
-androidxHiltNavigationCompose = "1.2.0"
+androidx-window = "1.5.0"
+androidx-window-core = "1.5.0"
+androidx-window-java = "1.5.0"
+androidx-xr-arcore = "1.0.0-alpha06"
+androidx-xr-compose = "1.0.0-alpha07"
+androidx-xr-scenecore = "1.0.0-alpha07"
+androidxHiltNavigationCompose = "1.3.0"
appcompat = "1.7.1"
coil = "2.7.0"
# @keep
compileSdk = "36"
-compose-latest = "1.9.0"
-composeUiTooling = "1.4.1"
+compose-latest = "1.9.3"
+composeUiTooling = "1.5.3"
coreSplashscreen = "1.0.1"
coroutines = "1.10.2"
-glide = "1.0.0-beta01"
+firebase-bom = "34.4.0"
+glide = "1.0.0-beta08"
google-maps = "19.2.0"
-gradle-versions = "0.52.0"
-guava = "33.4.8-jre"
-hilt = "2.57"
-horologist = "0.8.1-alpha"
+gradle-versions = "0.53.0"
+guava = "33.5.0-jre"
+guava-android = "31.0.1-android"
+reactive-streams = "1.0.4"
+hilt = "2.57.2"
+horologist = "0.8.2-alpha"
junit = "4.13.2"
-kotlin = "2.2.10"
+kotlin = "2.2.20"
kotlinCoroutinesOkhttp = "1.0"
kotlinxCoroutinesGuava = "1.10.2"
kotlinxSerializationJson = "1.9.0"
-ksp = "2.2.10-2.0.2"
-lifecycleService = "2.9.2"
-maps-compose = "6.7.2"
-material = "1.14.0-alpha03"
+ksp = "2.2.20-2.0.4"
+ktlint = "1.5.0"
+lifecycleService = "2.9.4"
+maps-compose = "6.12.1"
+material = "1.14.0-alpha05"
material3-adaptive = "1.1.0"
-material3-adaptive-navigation-suite = "1.3.2"
+material3-adaptive-navigation-suite = "1.4.0"
media3 = "1.8.0"
+media3Ui = "1.8.0"
# @keep
minSdk = "35"
-okHttp = "5.1.0"
+okHttp = "5.2.0"
playServicesWearable = "19.0.0"
protolayout = "1.3.0"
recyclerview = "1.4.0"
-targetSdk = "35"
+spotless = "8.0.0"
+targetSdk = "36"
tiles = "1.5.0"
tracing = "1.3.0"
-validatorPush = "1.0.0-alpha06"
-version-catalog-update = "1.0.0"
+validatorPush = "1.0.0-alpha08"
+version-catalog-update = "1.0.1"
watchfaceComplicationsDataSourceKtx = "1.2.1"
wear = "1.3.0"
-wearComposeFoundation = "1.5.0-rc02"
-wearComposeMaterial = "1.5.0-rc02"
-wearComposeMaterial3 = "1.5.0-rc02"
-wearOngoing = "1.0.0"
+wearComposeFoundation = "1.5.3"
+wearComposeMaterial = "1.5.3"
+wearComposeMaterial3 = "1.5.3"
+wearOngoing = "1.1.0"
wearToolingPreview = "1.0.0"
webkit = "1.14.0"
+wearInput = "1.2.0"
[libraries]
accompanist-adaptive = "com.google.accompanist:accompanist-adaptive:0.37.3"
@@ -123,7 +130,9 @@ androidx-credentials-play-services-auth = { module = "androidx.credentials:crede
androidx-emoji2-views = { module = "androidx.emoji2:emoji2-views", version.ref = "androidx-emoji2-views" }
androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment-ktx" }
androidx-glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "androidx-glance-appwidget" }
+androidx-glance-appwidget-testing = { module = "androidx.glance:glance-appwidget-testing", version.ref = "androidx-glance-appwidget" }
androidx-glance-material3 = { module = "androidx.glance:glance-material3", version.ref = "androidx-glance-appwidget" }
+androidx-glance-testing = { module = "androidx.glance:glance-testing", version.ref = "androidx-glance-appwidget" }
androidx-graphics-shapes = "androidx.graphics:graphics-shapes:1.0.1"
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-compose" }
@@ -135,6 +144,7 @@ androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-view
androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core" }
androidx-media3-common = { module = "androidx.media3:media3-common", version.ref = "media3" }
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
+androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Ui" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" }
androidx-protolayout = { module = "androidx.wear.protolayout:protolayout", version.ref = "protolayout" }
@@ -161,7 +171,7 @@ androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
androidx-window = { module = "androidx.window:window", version.ref = "androidx-window" }
androidx-window-core = { module = "androidx.window:window-core", version.ref = "androidx-window-core" }
androidx-window-java = { module = "androidx.window:window-java", version.ref = "androidx-window-java" }
-androidx-work-runtime-ktx = "androidx.work:work-runtime-ktx:2.10.3"
+androidx-work-runtime-ktx = "androidx.work:work-runtime-ktx:2.10.5"
androidx-xr-arcore = { module = "androidx.xr.arcore:arcore", version.ref = "androidx-xr-arcore" }
androidx-xr-compose = { module = "androidx.xr.compose:compose", version.ref = "androidx-xr-compose" }
androidx-xr-scenecore = { module = "androidx.xr.scenecore:scenecore", version.ref = "androidx-xr-scenecore" }
@@ -169,18 +179,24 @@ appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat"
coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wearComposeFoundation" }
compose-ui-tooling = { module = "androidx.wear.compose:compose-ui-tooling", version.ref = "composeUiTooling" }
+firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
+firebase-ai = { module = "com.google.firebase:firebase-ai" }
glide-compose = { module = "com.github.bumptech.glide:compose", version.ref = "glide" }
google-android-material = { module = "com.google.android.material:material", version.ref = "material" }
googlemaps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "maps-compose" }
googlemaps-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "google-maps" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
+guava-android = { module = "com.google.guava:guava", version.ref = "guava-android" }
+reactive-streams = { module = "org.reactivestreams:reactive-streams", version.ref = "reactive-streams" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" }
horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologist" }
+jetbrains-kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
junit = { module = "junit:junit", version.ref = "junit" }
kotlin-coroutines-okhttp = { module = "ru.gildor.coroutines:kotlin-coroutines-okhttp", version.ref = "kotlinCoroutinesOkhttp" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
+kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
@@ -190,20 +206,20 @@ play-services-wearable = { module = "com.google.android.gms:play-services-wearab
validator-push = { module = "com.google.android.wearable.watchface.validator:validator-push", version.ref = "validatorPush" }
wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wearComposeMaterial" }
wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wearComposeMaterial3" }
-jetbrains-kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
-kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" }
+androidx-wear-input = { group = "androidx.wear", name = "wear-input", version.ref = "wearInput" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
+android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
+android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
gradle-versions = { id = "com.github.ben-manes.versions", version.ref = "gradle-versions" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
-kotlin-android = "org.jetbrains.kotlin.android:2.2.10"
+kotlin-android = "org.jetbrains.kotlin.android:2.2.20"
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
-android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "androidGradlePlugin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
version-catalog-update = { id = "nl.littlerobots.version-catalog-update", version.ref = "version-catalog-update" }
-android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 37f853b1c..2e1113280 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/identity/credentialmanager/src/main/AndroidManifest.xml b/identity/credentialmanager/src/main/AndroidManifest.xml
index fb060cc44..b0f129562 100644
--- a/identity/credentialmanager/src/main/AndroidManifest.xml
+++ b/identity/credentialmanager/src/main/AndroidManifest.xml
@@ -1,20 +1,19 @@
+ Copyright 2025 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
diff --git a/identity/credentialmanager/src/main/res/drawable-v24/ic_launcher_foreground.xml b/identity/credentialmanager/src/main/res/drawable-v24/ic_launcher_foreground.xml
index 2b068d114..8760078c8 100644
--- a/identity/credentialmanager/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ b/identity/credentialmanager/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -1,3 +1,19 @@
+
+
-
\ No newline at end of file
+
diff --git a/identity/credentialmanager/src/main/res/drawable/ic_launcher_background.xml b/identity/credentialmanager/src/main/res/drawable/ic_launcher_background.xml
index 07d5da9cb..e6202fbb1 100644
--- a/identity/credentialmanager/src/main/res/drawable/ic_launcher_background.xml
+++ b/identity/credentialmanager/src/main/res/drawable/ic_launcher_background.xml
@@ -1,4 +1,19 @@
+
+ Copyright 2025 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
@@ -25,4 +24,4 @@
android:isCredential="true" />
-
\ No newline at end of file
+
diff --git a/identity/credentialmanager/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/identity/credentialmanager/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 6f3b755bf..b8ff0f029 100644
--- a/identity/credentialmanager/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/identity/credentialmanager/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,6 +1,21 @@
+
-
\ No newline at end of file
+
diff --git a/identity/credentialmanager/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/identity/credentialmanager/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 6f3b755bf..b8ff0f029 100644
--- a/identity/credentialmanager/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/identity/credentialmanager/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,6 +1,21 @@
+
-
\ No newline at end of file
+
diff --git a/identity/credentialmanager/src/main/res/values/colors.xml b/identity/credentialmanager/src/main/res/values/colors.xml
index f8c6127d3..732ae206f 100644
--- a/identity/credentialmanager/src/main/res/values/colors.xml
+++ b/identity/credentialmanager/src/main/res/values/colors.xml
@@ -1,4 +1,19 @@
+
#FFBB86FC
#FF6200EE
@@ -7,4 +22,4 @@
#FF018786
#FF000000
#FFFFFFFF
-
\ No newline at end of file
+
diff --git a/identity/credentialmanager/src/main/res/values/strings.xml b/identity/credentialmanager/src/main/res/values/strings.xml
index 8f5fb8e80..cb1497463 100644
--- a/identity/credentialmanager/src/main/res/values/strings.xml
+++ b/identity/credentialmanager/src/main/res/values/strings.xml
@@ -1,3 +1,19 @@
+
+
credentialmanager
// [START android_identity_assetlinks_app_association]
@@ -7,4 +23,4 @@
}]
// [END android_identity_assetlinks_app_association]
-
\ No newline at end of file
+
diff --git a/identity/credentialmanager/src/main/res/values/themes.xml b/identity/credentialmanager/src/main/res/values/themes.xml
index 65078ebe0..e2cf423e7 100644
--- a/identity/credentialmanager/src/main/res/values/themes.xml
+++ b/identity/credentialmanager/src/main/res/values/themes.xml
@@ -1,5 +1,20 @@
+
-
\ No newline at end of file
+
diff --git a/identity/credentialmanager/src/main/res/xml/provider.xml b/identity/credentialmanager/src/main/res/xml/provider.xml
index 81bdbd01d..9d75e5ad1 100644
--- a/identity/credentialmanager/src/main/res/xml/provider.xml
+++ b/identity/credentialmanager/src/main/res/xml/provider.xml
@@ -1,4 +1,19 @@
+
diff --git a/identity/credentialmanager/src/main/res/xml/provider_settings.xml b/identity/credentialmanager/src/main/res/xml/provider_settings.xml
index 698ba5f6c..1983ab623 100644
--- a/identity/credentialmanager/src/main/res/xml/provider_settings.xml
+++ b/identity/credentialmanager/src/main/res/xml/provider_settings.xml
@@ -1,4 +1,19 @@
+
-
\ No newline at end of file
+
+
diff --git a/kmp/shared/src/androidMain/AndroidManifest.xml b/kmp/shared/src/androidMain/AndroidManifest.xml
index a5918e68a..1d4927a52 100644
--- a/kmp/shared/src/androidMain/AndroidManifest.xml
+++ b/kmp/shared/src/androidMain/AndroidManifest.xml
@@ -1,4 +1,19 @@
+
-
\ No newline at end of file
+
diff --git a/kotlin/src/main/AndroidManifest.xml b/kotlin/src/main/AndroidManifest.xml
index 8072ee00d..1e772a05e 100644
--- a/kotlin/src/main/AndroidManifest.xml
+++ b/kotlin/src/main/AndroidManifest.xml
@@ -1,2 +1,17 @@
+
diff --git a/misc/build.gradle.kts b/misc/build.gradle.kts
index cb0905420..6a2dc60d1 100644
--- a/misc/build.gradle.kts
+++ b/misc/build.gradle.kts
@@ -1,3 +1,4 @@
+
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
@@ -72,6 +73,10 @@ dependencies {
implementation(libs.androidx.startup.runtime)
implementation(libs.androidx.window.java)
implementation(libs.appcompat)
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.ai)
+ implementation(libs.guava.android)
+ implementation(libs.reactive.streams)
testImplementation(libs.junit)
testImplementation(kotlin("test"))
androidTestImplementation(libs.androidx.test.ext.junit)
diff --git a/misc/src/main/AndroidManifest.xml b/misc/src/main/AndroidManifest.xml
index 2ad7db1e7..770f36dcd 100644
--- a/misc/src/main/AndroidManifest.xml
+++ b/misc/src/main/AndroidManifest.xml
@@ -1,4 +1,19 @@
+
diff --git a/misc/src/main/java/com/example/snippets/ActivityEmbeddingKotlinSnippets.kt b/misc/src/main/java/com/example/snippets/ActivityEmbeddingKotlinSnippets.kt
index d00308286..7871e78d5 100644
--- a/misc/src/main/java/com/example/snippets/ActivityEmbeddingKotlinSnippets.kt
+++ b/misc/src/main/java/com/example/snippets/ActivityEmbeddingKotlinSnippets.kt
@@ -276,7 +276,9 @@ class ActivityEmbeddingKotlinSnippets {
/**
* Function used by snippet.
*/
- fun classForItem(item: Int): Class<*> { return Class::class.java }
+ fun classForItem(item: Int): Class<*> {
+ return Class::class.java
+ }
// [START android_activity_embedding_MenuActivity_class_kotlin]
inner class MenuActivity : AppCompatActivity() {
diff --git a/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippets.kt b/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippets.kt
new file mode 100644
index 000000000..568945103
--- /dev/null
+++ b/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippets.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.snippets.ai
+
+import android.content.ContentResolver
+import android.graphics.Bitmap
+import android.net.Uri
+import com.google.firebase.Firebase
+import com.google.firebase.ai.ai
+import com.google.firebase.ai.type.GenerativeBackend
+import com.google.firebase.ai.type.ImagePart
+import com.google.firebase.ai.type.ResponseModality
+import com.google.firebase.ai.type.content
+import com.google.firebase.ai.type.generationConfig
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+object GeminiDeveloperApi25FlashModelConfiguration {
+ // [START android_gemini_developer_api_gemini_25_flash_model]
+ // Start by instantiating a GenerativeModel and specifying the model name:
+ val model = Firebase.ai(backend = GenerativeBackend.googleAI())
+ .generativeModel("gemini-2.5-flash")
+ // [END android_gemini_developer_api_gemini_25_flash_model]
+}
+
+object Gemini25FlashImagePreviewModelConfiguration {
+ // [START android_gemini_developer_api_gemini_25_flash_image_model]
+ val model = Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel(
+ modelName = "gemini-2.5-flash-image-preview",
+ // Configure the model to respond with text and images (required)
+ generationConfig = generationConfig {
+ responseModalities = listOf(
+ ResponseModality.TEXT,
+ ResponseModality.IMAGE
+ )
+ }
+ )
+ // [END android_gemini_developer_api_gemini_25_flash_image_model]
+}
+
+@Suppress("unused")
+fun textOnlyInput(scope: CoroutineScope) {
+ val model = GeminiDeveloperApi25FlashModelConfiguration.model
+ // [START android_gemini_developer_api_text_only_input]
+ scope.launch {
+ val response = model.generateContent("Write a story about a magic backpack.")
+ }
+ // [END android_gemini_developer_api_text_only_input]
+}
+
+@Suppress("unused")
+fun textAndImageInput(scope: CoroutineScope, bitmap: Bitmap) {
+ val model = GeminiDeveloperApi25FlashModelConfiguration.model
+ // [START android_gemini_developer_api_multimodal_input]
+ scope.launch {
+ val response = model.generateContent(
+ content {
+ image(bitmap)
+ text("what is the object in the picture?")
+ }
+ )
+ }
+ // [END android_gemini_developer_api_multimodal_input]
+}
+
+@Suppress("unused")
+fun textAndAudioInput(
+ scope: CoroutineScope,
+ contentResolver: ContentResolver,
+ audioUri: Uri
+) {
+ val model = GeminiDeveloperApi25FlashModelConfiguration.model
+ // [START android_gemini_developer_api_multimodal_audio_input]
+ scope.launch {
+ contentResolver.openInputStream(audioUri).use { stream ->
+ stream?.let {
+ val bytes = it.readBytes()
+
+ val prompt = content {
+ inlineData(bytes, "audio/mpeg") // Specify the appropriate audio MIME type
+ text("Transcribe this audio recording.")
+ }
+
+ val response = model.generateContent(prompt)
+ }
+ }
+ }
+ // [END android_gemini_developer_api_multimodal_audio_input]
+}
+
+@Suppress("unused")
+fun textAndVideoInput(
+ scope: CoroutineScope,
+ contentResolver: ContentResolver,
+ videoUri: Uri
+) {
+ val model = GeminiDeveloperApi25FlashModelConfiguration.model
+ // [START android_gemini_developer_api_multimodal_video_input]
+ scope.launch {
+ contentResolver.openInputStream(videoUri).use { stream ->
+ stream?.let {
+ val bytes = it.readBytes()
+
+ val prompt = content {
+ inlineData(bytes, "video/mp4") // Specify the appropriate video MIME type
+ text("Describe the content of this video")
+ }
+
+ val response = model.generateContent(prompt)
+ }
+ }
+ }
+ // [END android_gemini_developer_api_multimodal_video_input]
+}
+
+@Suppress("unused")
+fun multiTurnChat(scope: CoroutineScope) {
+ val model = GeminiDeveloperApi25FlashModelConfiguration.model
+ // [START android_gemini_developer_api_multiturn_chat]
+ val chat = model.startChat(
+ history = listOf(
+ content(role = "user") { text("Hello, I have 2 dogs in my house.") },
+ content(role = "model") { text("Great to meet you. What would you like to know?") }
+ )
+ )
+
+ scope.launch {
+ val response = chat.sendMessage("How many paws are in my house?")
+ }
+ // [END android_gemini_developer_api_multiturn_chat]
+}
+
+@Suppress("unused")
+fun generateImageFromText(scope: CoroutineScope) {
+ val model = Gemini25FlashImagePreviewModelConfiguration.model
+ // [START android_gemini_developer_api_generate_image_from_text]
+ scope.launch {
+ // Provide a text prompt instructing the model to generate an image
+ val prompt =
+ "A hyper realistic picture of a t-rex with a blue bag pack roaming a pre-historic forest."
+ // To generate image output, call `generateContent` with the text input
+ val generatedImageAsBitmap: Bitmap? = model.generateContent(prompt)
+ .candidates.first().content.parts.filterIsInstance()
+ .firstOrNull()?.image
+ }
+ // [END android_gemini_developer_api_generate_image_from_text]
+}
+
+@Suppress("unused")
+fun editImage(scope: CoroutineScope, bitmap: Bitmap) {
+ val model = Gemini25FlashImagePreviewModelConfiguration.model
+ // [START android_gemini_developer_api_edit_image]
+ scope.launch {
+ // Provide a text prompt instructing the model to edit the image
+ val prompt = content {
+ image(bitmap)
+ text("Edit this image to make it look like a cartoon")
+ }
+ // To edit the image, call `generateContent` with the prompt (image and text input)
+ val generatedImageAsBitmap: Bitmap? = model.generateContent(prompt)
+ .candidates.first().content.parts.filterIsInstance().firstOrNull()?.image
+ // Handle the generated text and image
+ }
+ // [END android_gemini_developer_api_edit_image]
+}
+
+@Suppress("unused")
+fun editImageWithChat(scope: CoroutineScope, bitmap: Bitmap) {
+ val model = Gemini25FlashImagePreviewModelConfiguration.model
+ // [START android_gemini_developer_api_edit_image_chat]
+ scope.launch {
+ // Create the initial prompt instructing the model to edit the image
+ val prompt = content {
+ image(bitmap)
+ text("Edit this image to make it look like a cartoon")
+ }
+ // Initialize the chat
+ val chat = model.startChat()
+ // To generate an initial response, send a user message with the image and text prompt
+ var response = chat.sendMessage(prompt)
+ // Inspect the returned image
+ var generatedImageAsBitmap: Bitmap? = response
+ .candidates.first().content.parts.filterIsInstance().firstOrNull()?.image
+ // Follow up requests do not need to specify the image again
+ response = chat.sendMessage("But make it old-school line drawing style")
+ generatedImageAsBitmap = response
+ .candidates.first().content.parts.filterIsInstance().firstOrNull()?.image
+ }
+ // [END android_gemini_developer_api_edit_image_chat]
+}
diff --git a/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippetsJava.java b/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippetsJava.java
new file mode 100644
index 000000000..691c2f394
--- /dev/null
+++ b/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippetsJava.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.snippets.ai;
+
+import android.app.Application;
+import android.content.ContentResolver;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+
+import com.example.snippets.R;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.firebase.ai.FirebaseAI;
+import com.google.firebase.ai.GenerativeModel;
+import com.google.firebase.ai.java.ChatFutures;
+import com.google.firebase.ai.java.GenerativeModelFutures;
+import com.google.firebase.ai.type.Content;
+import com.google.firebase.ai.type.GenerateContentResponse;
+import com.google.firebase.ai.type.GenerativeBackend;
+import com.google.firebase.ai.type.ImagePart;
+import com.google.firebase.ai.type.Part;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SuppressWarnings("unused")
+public final class GeminiDeveloperApiSnippetsJava {
+
+ private static final String TAG = "GeminiDeveloperApiSnippetsJava";
+
+ private GeminiDeveloperApiSnippetsJava() {}
+
+ static final class GeminiDeveloperApi25FlashModelConfigurationJava {
+ public static GenerativeModelFutures model;
+
+ static {
+ // [START android_gemini_developer_api_gemini_25_flash_model_java]
+ GenerativeModel firebaseAI = FirebaseAI.getInstance(GenerativeBackend.googleAI())
+ .generativeModel("gemini-2.5-flash");
+
+ GenerativeModelFutures model = GenerativeModelFutures.from(firebaseAI);
+ // [END android_gemini_developer_api_gemini_25_flash_model_java]
+ GeminiDeveloperApi25FlashModelConfigurationJava.model = model;
+ }
+ }
+
+ static final class Gemini25FlashImagePreviewModelConfigurationJava {
+ public static GenerativeModelFutures model;
+
+ static {
+ // [START android_gemini_developer_api_gemini_25_flash_image_model_java]
+ GenerativeModel firebaseAI = FirebaseAI.getInstance(GenerativeBackend.googleAI())
+ .generativeModel("gemini-2.5-flash");
+
+ GenerativeModelFutures model = GenerativeModelFutures.from(firebaseAI);
+ // [END android_gemini_developer_api_gemini_25_flash_image_model_java]
+ Gemini25FlashImagePreviewModelConfigurationJava.model = model;
+ }
+
+ }
+
+ public static void textOnlyInput(Executor executor) {
+ GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model;
+ // [START android_gemini_developer_api_text_only_input_java]
+ Content prompt = new Content.Builder()
+ .addText("Write a story about a magic backpack.")
+ .build();
+
+ ListenableFuture response = model.generateContent(prompt);
+ Futures.addCallback(response, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ String resultText = result.getText();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ t.printStackTrace();
+ }
+ }, executor);
+ // [END android_gemini_developer_api_text_only_input_java]
+ }
+
+ public static void textAndImageInput(Executor executor, Bitmap bitmap) {
+ GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model;
+ // [START android_gemini_developer_api_multimodal_input_java]
+ Content content = new Content.Builder()
+ .addImage(bitmap)
+ .addText("what is the object in the picture?")
+ .build();
+
+ ListenableFuture response = model.generateContent(content);
+ Futures.addCallback(response, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ String resultText = result.getText();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ t.printStackTrace();
+ }
+ }, executor);
+ // [END android_gemini_developer_api_multimodal_input_java]
+ }
+
+ public static void textAndAudioInput(Executor executor, Application applicationContext, Uri audioUri) {
+ GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model;
+ // [START android_gemini_developer_api_multimodal_audio_input_java]
+ ContentResolver resolver = applicationContext.getContentResolver();
+
+ try (InputStream stream = resolver.openInputStream(audioUri)) {
+ File audioFile = new File(new URI(audioUri.toString()));
+ int audioSize = (int) audioFile.length();
+ byte[] audioBytes = new byte[audioSize];
+ if (stream != null) {
+ stream.read(audioBytes, 0, audioBytes.length);
+ stream.close();
+
+ // Provide a prompt that includes audio specified earlier and text
+ Content prompt = new Content.Builder()
+ .addInlineData(audioBytes, "audio/mpeg") // Specify the appropriate audio MIME type
+ .addText("Transcribe what's said in this audio recording.")
+ .build();
+
+ // To generate text output, call `generateContent` with the prompt
+ ListenableFuture response = model.generateContent(prompt);
+ Futures.addCallback(response, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ String text = result.getText();
+ Log.d(TAG, (text == null) ? "" : text);
+ }
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e(TAG, "Failed to generate a response", t);
+ }
+ }, executor);
+ } else {
+ Log.e(TAG, "Error getting input stream for file.");
+ // Handle the error appropriately
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read the audio file", e);
+ } catch (URISyntaxException e) {
+ Log.e(TAG, "Invalid audio file", e);
+ }
+ // [END android_gemini_developer_api_multimodal_audio_input_java]
+ }
+
+ public static void textAndVideoInput(Executor executor, Application applicationContext, Uri videoUri) {
+ GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model;
+ // [START android_gemini_developer_api_multimodal_video_input_java]
+ ContentResolver resolver = applicationContext.getContentResolver();
+
+ try (InputStream stream = resolver.openInputStream(videoUri)) {
+ File videoFile = new File(new URI(videoUri.toString()));
+ int videoSize = (int) videoFile.length();
+ byte[] videoBytes = new byte[videoSize];
+ if (stream != null) {
+ stream.read(videoBytes, 0, videoBytes.length);
+ stream.close();
+
+ // Provide a prompt that includes video specified earlier and text
+ Content prompt = new Content.Builder()
+ .addInlineData(videoBytes, "video/mp4")
+ .addText("Describe the content of this video")
+ .build();
+
+ // To generate text output, call generateContent with the prompt
+ ListenableFuture response = model.generateContent(prompt);
+ Futures.addCallback(response, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ String resultText = result.getText();
+ System.out.println(resultText);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ t.printStackTrace();
+ }
+ }, executor);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ // [END android_gemini_developer_api_multimodal_video_input_java]
+ }
+
+ public static void multiTurnChat(Executor executor) {
+ GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model;
+ // [START android_gemini_developer_api_multiturn_chat_java]
+ Content.Builder userContentBuilder = new Content.Builder();
+ userContentBuilder.setRole("user");
+ userContentBuilder.addText("Hello, I have 2 dogs in my house.");
+ Content userContent = userContentBuilder.build();
+
+ Content.Builder modelContentBuilder = new Content.Builder();
+ modelContentBuilder.setRole("model");
+ modelContentBuilder.addText("Great to meet you. What would you like to know?");
+ Content modelContent = modelContentBuilder.build();
+
+ List history = Arrays.asList(userContent, modelContent);
+
+ // Initialize the chat
+ ChatFutures chat = model.startChat(history);
+
+ // Create a new user message
+ Content.Builder messageBuilder = new Content.Builder();
+ messageBuilder.setRole("user");
+ messageBuilder.addText("How many paws are in my house?");
+
+ Content message = messageBuilder.build();
+
+ // Send the message
+ ListenableFuture response = chat.sendMessage(message);
+ Futures.addCallback(response, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ String resultText = result.getText();
+ System.out.println(resultText);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ t.printStackTrace();
+ }
+ }, executor);
+ // [END android_gemini_developer_api_multiturn_chat_java]
+ }
+
+ public static void generateImageFromText(Executor executor) {
+ GenerativeModelFutures model = Gemini25FlashImagePreviewModelConfigurationJava.model;
+ // [START android_gemini_developer_api_generate_image_from_text_java]
+ // Provide a text prompt instructing the model to generate an image
+ Content prompt = new Content.Builder()
+ .addText("Generate an image of the Eiffel Tower with fireworks in the background.")
+ .build();
+ // To generate an image, call `generateContent` with the text input
+ ListenableFuture response = model.generateContent(prompt);
+ Futures.addCallback(response, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ // iterate over all the parts in the first candidate in the result object
+ for (Part part : result.getCandidates().get(0).getContent().getParts()) {
+ if (part instanceof ImagePart) {
+ ImagePart imagePart = (ImagePart) part;
+ // The returned image as a bitmap
+ Bitmap generatedImageAsBitmap = imagePart.getImage();
+ break;
+ }
+ }
+ }
+ @Override
+ public void onFailure(Throwable t) {
+ t.printStackTrace();
+ }
+ }, executor);
+ // [END android_gemini_developer_api_generate_image_from_text_java]
+ }
+
+ public static void editImage(Executor executor, Resources resources) {
+ GenerativeModelFutures model = Gemini25FlashImagePreviewModelConfigurationJava.model;
+ // [START android_gemini_developer_api_edit_image_java]
+ // Provide an image for the model to edit
+ Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.scones);
+ // Provide a text prompt instructing the model to edit the image
+ Content promptcontent = new Content.Builder()
+ .addImage(bitmap)
+ .addText("Edit this image to make it look like a cartoon")
+ .build();
+ // To edit the image, call `generateContent` with the prompt (image and text input)
+ ListenableFuture response = model.generateContent(promptcontent);
+ Futures.addCallback(response, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ // iterate over all the parts in the first candidate in the result object
+ for (Part part : result.getCandidates().get(0).getContent().getParts()) {
+ if (part instanceof ImagePart) {
+ ImagePart imagePart = (ImagePart) part;
+ Bitmap generatedImageAsBitmap = imagePart.getImage();
+ break;
+ }
+ }
+ }
+ @Override
+ public void onFailure(Throwable t) {
+ t.printStackTrace();
+ }
+ }, executor);
+ // [END android_gemini_developer_api_edit_image_java]
+ }
+
+ public static void editImageWithChat(Executor executor, Resources resources) {
+ GenerativeModelFutures model = Gemini25FlashImagePreviewModelConfigurationJava.model;
+ // [START android_gemini_developer_api_edit_image_chat_java]
+ // Provide an image for the model to edit
+ Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.scones);
+ // Initialize the chat
+ ChatFutures chat = model.startChat();
+ // Create the initial prompt instructing the model to edit the image
+ Content prompt = new Content.Builder()
+ .setRole("user")
+ .addImage(bitmap)
+ .addText("Edit this image to make it look like a cartoon")
+ .build();
+ // To generate an initial response, send a user message with the image and text prompt
+ ListenableFuture response = chat.sendMessage(prompt);
+ // Extract the image from the initial response
+ ListenableFuture initialRequest = Futures.transform(response,
+ result -> {
+ for (Part part : result.getCandidates().get(0).getContent().getParts()) {
+ if (part instanceof ImagePart) {
+ ImagePart imagePart = (ImagePart) part;
+ return imagePart.getImage();
+ }
+ }
+ return null;
+ }, executor);
+ // Follow up requests do not need to specify the image again
+ ListenableFuture modelResponseFuture = Futures.transformAsync(
+ initialRequest,
+ generatedImage -> {
+ Content followUpPrompt = new Content.Builder()
+ .addText("But make it old-school line drawing style")
+ .build();
+ return chat.sendMessage(followUpPrompt);
+ }, executor);
+ // Add a final callback to check the reworked image
+ Futures.addCallback(modelResponseFuture, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ for (Part part : result.getCandidates().get(0).getContent().getParts()) {
+ if (part instanceof ImagePart) {
+ ImagePart imagePart = (ImagePart) part;
+ Bitmap generatedImageAsBitmap = imagePart.getImage();
+ break;
+ }
+ }
+ }
+ @Override
+ public void onFailure(Throwable t) {
+ t.printStackTrace();
+ }
+ }, executor);
+ // [END android_gemini_developer_api_edit_image_chat_java]
+ }
+}
diff --git a/misc/src/main/java/com/example/snippets/ai/ImagenSnippets.kt b/misc/src/main/java/com/example/snippets/ai/ImagenSnippets.kt
new file mode 100644
index 000000000..2f55c4ade
--- /dev/null
+++ b/misc/src/main/java/com/example/snippets/ai/ImagenSnippets.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(PublicPreviewAPI::class)
+
+package com.example.snippets.ai
+
+import com.google.firebase.Firebase
+import com.google.firebase.ai.ai
+import com.google.firebase.ai.type.GenerativeBackend
+import com.google.firebase.ai.type.ImagenAspectRatio
+import com.google.firebase.ai.type.ImagenGenerationConfig
+import com.google.firebase.ai.type.ImagenImageFormat
+import com.google.firebase.ai.type.ImagenPersonFilterLevel
+import com.google.firebase.ai.type.ImagenSafetyFilterLevel
+import com.google.firebase.ai.type.ImagenSafetySettings
+import com.google.firebase.ai.type.PublicPreviewAPI
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+private object ImagenModelConfiguration {
+ // [START android_imagen_model_configuration]
+ val config = ImagenGenerationConfig(
+ numberOfImages = 2,
+ aspectRatio = ImagenAspectRatio.LANDSCAPE_16x9,
+ imageFormat = ImagenImageFormat.jpeg(compressionQuality = 100),
+ addWatermark = false,
+ )
+
+ // Initialize the Gemini Developer API backend service
+ // For Vertex AI use Firebase.ai(backend = GenerativeBackend.vertexAI())
+ val model = Firebase.ai(backend = GenerativeBackend.googleAI()).imagenModel(
+ modelName = "imagen-4.0-generate-001",
+ generationConfig = config,
+ safetySettings = ImagenSafetySettings(
+ safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,
+ personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL
+ ),
+ )
+ // [END android_imagen_model_configuration]
+}
+
+private fun generateImagesWithImagen(scope: CoroutineScope) {
+ val model = ImagenModelConfiguration.model
+ scope.launch {
+ // [START android_imagen_generate_images]
+ val imageResponse = model.generateImages(
+ prompt = "A hyper realistic picture of a t-rex with a blue bagpack in a prehistoric forest",
+ )
+ val image = imageResponse.images.first()
+ val bitmapImage = image.asBitmap()
+ // [END android_imagen_generate_images]
+ }
+}
diff --git a/misc/src/main/java/com/example/snippets/ai/ImagenSnippetsJava.java b/misc/src/main/java/com/example/snippets/ai/ImagenSnippetsJava.java
new file mode 100644
index 000000000..2c6a12133
--- /dev/null
+++ b/misc/src/main/java/com/example/snippets/ai/ImagenSnippetsJava.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.snippets.ai;
+
+import android.graphics.Bitmap;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.firebase.ai.FirebaseAI;
+import com.google.firebase.ai.java.ImagenModelFutures;
+import com.google.firebase.ai.type.GenerativeBackend;
+import com.google.firebase.ai.type.ImagenAspectRatio;
+import com.google.firebase.ai.type.ImagenGenerationConfig;
+import com.google.firebase.ai.type.ImagenGenerationResponse;
+import com.google.firebase.ai.type.ImagenImageFormat;
+import com.google.firebase.ai.type.ImagenInlineImage;
+import com.google.firebase.ai.type.ImagenPersonFilterLevel;
+import com.google.firebase.ai.type.ImagenSafetyFilterLevel;
+import com.google.firebase.ai.type.ImagenSafetySettings;
+import com.google.firebase.ai.type.PublicPreviewAPI;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+@SuppressWarnings("unused")
+@PublicPreviewAPI
+final class ImagenSnippetsJava {
+
+ private ImagenSnippetsJava() {}
+
+ static final class ImagenModelConfigurationJava {
+ public static ImagenModelFutures model;
+ }
+
+ static {
+ // [START android_imagen_model_configuration_java]
+ ImagenGenerationConfig config = new ImagenGenerationConfig.Builder()
+ .setNumberOfImages(2)
+ .setAspectRatio(ImagenAspectRatio.LANDSCAPE_16x9)
+ .setImageFormat(ImagenImageFormat.jpeg(100))
+ .setAddWatermark(false)
+ .build();
+
+ // For Vertex AI use Firebase.ai(backend = GenerativeBackend.vertexAI())
+ ImagenModelFutures model = ImagenModelFutures.from(
+ FirebaseAI.getInstance(GenerativeBackend.googleAI()).imagenModel(
+ "imagen-4.0-generate-001",
+ config,
+ new ImagenSafetySettings(
+ ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,
+ ImagenPersonFilterLevel.BLOCK_ALL))
+ );
+ // [END android_imagen_model_configuration_java]
+ ImagenModelConfigurationJava.model = model;
+ }
+
+ public static void generateImagesWithImagen(Executor executor) {
+ ImagenModelFutures model = ImagenModelConfigurationJava.model;
+ // [START android_imagen_generate_images_java]
+ ListenableFuture> futureResponse =
+ model.generateImages(
+ "A hyper realistic picture of a t-rex with a blue bagpack in a prehistoric forest");
+
+ try {
+ ImagenGenerationResponse imageResponse = futureResponse.get();
+ List images = null;
+ if (imageResponse != null) {
+ images = imageResponse.getImages();
+ }
+ if (images != null && !images.isEmpty()) {
+ ImagenInlineImage image = images.get(0);
+ Bitmap bitmapImage = image.asBitmap();
+ // Use bitmapImage
+ }
+ } catch (ExecutionException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ // [END android_imagen_generate_images_java]
+ }
+}
diff --git a/misc/src/main/java/com/example/snippets/ui/theme/Type.kt b/misc/src/main/java/com/example/snippets/ui/theme/Type.kt
index f383a07ba..db2e63291 100644
--- a/misc/src/main/java/com/example/snippets/ui/theme/Type.kt
+++ b/misc/src/main/java/com/example/snippets/ui/theme/Type.kt
@@ -46,5 +46,5 @@ val Typography = Typography(
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
- */
+ */
)
diff --git a/misc/src/main/res/drawable/ic_launcher_background.xml b/misc/src/main/res/drawable/ic_launcher_background.xml
index 07d5da9cb..e6202fbb1 100644
--- a/misc/src/main/res/drawable/ic_launcher_background.xml
+++ b/misc/src/main/res/drawable/ic_launcher_background.xml
@@ -1,4 +1,19 @@
+
+
-
\ No newline at end of file
+
diff --git a/misc/src/main/res/drawable/scones.xml b/misc/src/main/res/drawable/scones.xml
new file mode 100644
index 000000000..2eafb40af
--- /dev/null
+++ b/misc/src/main/res/drawable/scones.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/misc/src/main/res/layout/activity_main.xml b/misc/src/main/res/layout/activity_main.xml
index 59cbc1b45..dbfa14759 100644
--- a/misc/src/main/res/layout/activity_main.xml
+++ b/misc/src/main/res/layout/activity_main.xml
@@ -1,5 +1,19 @@
-
+
+
-
\ No newline at end of file
+
diff --git a/misc/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/misc/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 6f3b755bf..b8ff0f029 100644
--- a/misc/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/misc/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,6 +1,21 @@
+
-
\ No newline at end of file
+
diff --git a/misc/src/main/res/values/colors.xml b/misc/src/main/res/values/colors.xml
index 61944aef2..e1f592422 100644
--- a/misc/src/main/res/values/colors.xml
+++ b/misc/src/main/res/values/colors.xml
@@ -1,4 +1,19 @@
+
#FFBB86FC
#FF6200EE
diff --git a/misc/src/main/res/values/strings.xml b/misc/src/main/res/values/strings.xml
index 870fc4736..681420d2b 100644
--- a/misc/src/main/res/values/strings.xml
+++ b/misc/src/main/res/values/strings.xml
@@ -1,3 +1,19 @@
+
+
Background Snippets
-
\ No newline at end of file
+
diff --git a/misc/src/main/res/values/themes.xml b/misc/src/main/res/values/themes.xml
index 65078ebe0..e2cf423e7 100644
--- a/misc/src/main/res/values/themes.xml
+++ b/misc/src/main/res/values/themes.xml
@@ -1,5 +1,20 @@
+
-
\ No newline at end of file
+
diff --git a/misc/src/main/res/xml/main_split_config.xml b/misc/src/main/res/xml/main_split_config.xml
index 2113dc80d..a7860fa14 100644
--- a/misc/src/main/res/xml/main_split_config.xml
+++ b/misc/src/main/res/xml/main_split_config.xml
@@ -1,5 +1,19 @@
-
+
-
\ No newline at end of file
+
diff --git a/spotless/copyright.kts b/spotless/copyright.kts
new file mode 100644
index 000000000..806db0fb5
--- /dev/null
+++ b/spotless/copyright.kts
@@ -0,0 +1,16 @@
+/*
+ * Copyright $YEAR The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
diff --git a/spotless/copyright.xml b/spotless/copyright.xml
new file mode 100644
index 000000000..9cb1e5942
--- /dev/null
+++ b/spotless/copyright.xml
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/views/build.gradle.kts b/views/build.gradle.kts
index 80ee4edc8..623add7ab 100644
--- a/views/build.gradle.kts
+++ b/views/build.gradle.kts
@@ -19,10 +19,10 @@ plugins {
android {
namespace = "com.example.example.snippet.views"
- compileSdk = 35
+ compileSdk = libs.versions.compileSdk.get().toInt()
defaultConfig {
- minSdk = 35
+ minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
@@ -38,11 +38,11 @@ android {
}
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = "1.8"
+ jvmTarget = "17"
}
}
diff --git a/views/src/main/AndroidManifest.xml b/views/src/main/AndroidManifest.xml
index 65ba8e1e0..cb8f943f4 100644
--- a/views/src/main/AndroidManifest.xml
+++ b/views/src/main/AndroidManifest.xml
@@ -1,20 +1,19 @@
-
\ No newline at end of file
+
diff --git a/views/src/main/res/layout/system_bar_protection.xml b/views/src/main/res/layout/system_bar_protection.xml
index f70ccd21f..d230aab10 100644
--- a/views/src/main/res/layout/system_bar_protection.xml
+++ b/views/src/main/res/layout/system_bar_protection.xml
@@ -1,22 +1,19 @@
-
-
\ No newline at end of file
+
diff --git a/views/src/main/res/layout/widget_preview.xml b/views/src/main/res/layout/widget_preview.xml
index c9f449bb1..356458236 100644
--- a/views/src/main/res/layout/widget_preview.xml
+++ b/views/src/main/res/layout/widget_preview.xml
@@ -1,24 +1,22 @@
-
\ No newline at end of file
+
diff --git a/watchfacepush/validator/build.gradle.kts b/watchfacepush/validator/build.gradle.kts
index 0b9289a5c..7cfb5372d 100644
--- a/watchfacepush/validator/build.gradle.kts
+++ b/watchfacepush/validator/build.gradle.kts
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import org.gradle.jvm.toolchain.JavaLanguageVersion
group = "com.example.validator"
version = "1.0"
@@ -22,8 +23,18 @@ plugins {
application
}
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+kotlin {
+ jvmToolchain(17)
+}
application {
mainClass.set("com.example.validator.Main")
+
}
sourceSets {
@@ -32,8 +43,9 @@ sourceSets {
srcDir("src/main/java")
}
}
+
}
dependencies {
implementation(libs.validator.push)
-}
\ No newline at end of file
+}
diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts
index d88421c1e..68d64d8dd 100644
--- a/wear/build.gradle.kts
+++ b/wear/build.gradle.kts
@@ -53,6 +53,9 @@ android {
dependencies {
implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.media3.exoplayer)
+ implementation(libs.androidx.media3.ui)
+ implementation(libs.androidx.wear.input)
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
androidTestImplementation(composeBom)
diff --git a/wear/lint.xml b/wear/lint.xml
index 44fac75b8..51fd99c77 100644
--- a/wear/lint.xml
+++ b/wear/lint.xml
@@ -1,8 +1,23 @@
-
+
+
-
\ No newline at end of file
+
diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
index a585fb701..9fd910e5d 100644
--- a/wear/src/main/AndroidManifest.xml
+++ b/wear/src/main/AndroidManifest.xml
@@ -1,4 +1,19 @@
+
@@ -26,6 +41,17 @@
android:name="com.google.android.wearable.standalone"
android:value="true" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
?>(null) }
+ val listState = rememberScalingLazyListState()
+
MaterialTheme(
colorScheme = dynamicColorScheme(LocalContext.current) ?: MaterialTheme.colorScheme
) {
- // [START android_wear_ongoing_activity_ambientaware]
AmbientAware { ambientState ->
- // [START_EXCLUDE]
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ ScalingLazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ state = listState,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ autoCentering = AutoCenteringParams(itemIndex = 0)
+ ) {
+ item {
Text(text = "Elapsed Time", style = MaterialTheme.typography.titleLarge)
+ }
+ item {
Spacer(modifier = Modifier.height(8.dp))
- // [END_EXCLUDE]
+ }
+ item {
ElapsedTime(ambientState = ambientState)
- // [START_EXCLUDE]
+ }
+ item {
Spacer(modifier = Modifier.height(8.dp))
+ }
+
+ val services = listOf(
+ AlwaysOnService1::class.java,
+ AlwaysOnService2::class.java,
+ AlwaysOnService3::class.java
+ )
+
+ items(services.size) { index ->
+ val serviceClass = services[index]
+ val isRunning = runningService == serviceClass
SwitchButton(
- checked = isOngoingActivity,
+ checked = isRunning,
onCheckedChange = { newState ->
- Log.d(TAG, "Switch button changed: $newState")
- isOngoingActivity = newState
-
if (newState) {
- Log.d(TAG, "Starting AlwaysOnService")
- AlwaysOnService.startService(context)
+ if (runningService != null) {
+ Log.d(TAG, "Stopping ${runningService?.simpleName}")
+ context.stopService(Intent(context, runningService))
+ }
+ Log.d(TAG, "Starting ${serviceClass.simpleName}")
+ val intent = Intent(context, serviceClass)
+ context.startForegroundService(intent)
+ runningService = serviceClass
} else {
- Log.d(TAG, "Stopping AlwaysOnService")
- AlwaysOnService.stopService(context)
+ Log.d(TAG, "Stopping ${serviceClass.simpleName}")
+ context.stopService(Intent(context, serviceClass))
+ runningService = null
}
},
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
) {
Text(
- text = "Ongoing Activity",
- style = MaterialTheme.typography.bodyExtraSmall,
+ text = "Ongoing Activity ${index + 1}",
+ style = MaterialTheme.typography.bodySmall,
)
}
}
}
- // [END_EXCLUDE]
}
- // [END android_wear_ongoing_activity_ambientaware]
}
}
diff --git a/wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt b/wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt
index 59ed0f8af..d52db8fd8 100644
--- a/wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt
+++ b/wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt
@@ -16,12 +16,11 @@
package com.example.wear.snippets.alwayson
-import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
-import android.content.Context
import android.content.Intent
+import android.os.SystemClock
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService
@@ -30,29 +29,19 @@ import androidx.wear.ongoing.OngoingActivity
import androidx.wear.ongoing.Status
import com.example.wear.R
-class AlwaysOnService : LifecycleService() {
+abstract class AlwaysOnServiceBase : LifecycleService() {
private val notificationManager by lazy { getSystemService() }
companion object {
private const val TAG = "AlwaysOnService"
- private const val NOTIFICATION_ID = 1001
- private const val CHANNEL_ID = "always_on_service_channel"
+ const val NOTIFICATION_ID = 1001
+ const val CHANNEL_ID = "always_on_service_channel"
private const val CHANNEL_NAME = "Always On Service"
+
@Volatile
var isRunning = false
private set
-
- fun startService(context: Context) {
- Log.d(TAG, "Starting AlwaysOnService")
- val intent = Intent(context, AlwaysOnService::class.java)
- context.startForegroundService(intent)
- }
-
- fun stopService(context: Context) {
- Log.d(TAG, "Stopping AlwaysOnService")
- context.stopService(Intent(context, AlwaysOnService::class.java))
- }
}
override fun onCreate() {
@@ -66,9 +55,7 @@ class AlwaysOnService : LifecycleService() {
super.onStartCommand(intent, flags, startId)
Log.d(TAG, "onStartCommand: Service started with startId: $startId")
- // Create and start foreground notification
- val notification = createNotification()
- startForeground(NOTIFICATION_ID, notification)
+ createNotification()
Log.d(TAG, "onStartCommand: Service is now running as foreground service")
@@ -93,8 +80,13 @@ class AlwaysOnService : LifecycleService() {
Log.d(TAG, "createNotificationChannel: Notification channel created")
}
- // [START android_wear_ongoing_activity_create_notification]
- private fun createNotification(): Notification {
+ abstract fun createNotification()
+}
+
+class AlwaysOnService1 : AlwaysOnServiceBase() {
+ override fun createNotification() {
+ // Creates an ongoing activity that demonstrates how to link the touch intent to the always-on activity.
+ // [START android_wear_ongoing_activity_create_notification]
val activityIntent =
Intent(this, AlwaysOnActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
@@ -122,7 +114,6 @@ class AlwaysOnService : LifecycleService() {
.setOngoing(true)
// [START_EXCLUDE]
- // Create an Ongoing Activity
val ongoingActivityStatus = Status.Builder().addTemplate("Stopwatch running").build()
// [END_EXCLUDE]
@@ -139,7 +130,120 @@ class AlwaysOnService : LifecycleService() {
ongoingActivity.apply(applicationContext)
- return notificationBuilder.build()
+ val notification = notificationBuilder.build()
+ // [END android_wear_ongoing_activity_create_notification]
+
+ startForeground(NOTIFICATION_ID, notification)
+ }
+}
+
+class AlwaysOnService2 : AlwaysOnServiceBase() {
+ override fun createNotification() {
+ // Creates an ongoing activity with a static status text
+
+ // [START android_wear_ongoing_activity_notification_builder]
+ // Create a PendingIntent to pass to the notification builder
+ val pendingIntent =
+ PendingIntent.getActivity(
+ this,
+ 0,
+ Intent(this, AlwaysOnActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
+ },
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
+ )
+
+ val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle("Always On Service")
+ .setContentText("Service is running in background")
+ .setSmallIcon(R.drawable.animated_walk)
+ // Category helps the system prioritize the ongoing activity
+ .setCategory(NotificationCompat.CATEGORY_WORKOUT)
+ .setContentIntent(pendingIntent)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setOngoing(true) // Important!
+ // [END android_wear_ongoing_activity_notification_builder]
+
+ // [START android_wear_ongoing_activity_builder]
+ val ongoingActivity =
+ OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
+ // Sets the icon that appears on the watch face in active mode.
+ .setAnimatedIcon(R.drawable.animated_walk)
+ // Sets the icon that appears on the watch face in ambient mode.
+ .setStaticIcon(R.drawable.ic_walk)
+ // Sets the tap target to bring the user back to the app.
+ .setTouchIntent(pendingIntent)
+ .build()
+ // [END android_wear_ongoing_activity_builder]
+
+ // [START android_wear_ongoing_activity_post_notification]
+ // This call modifies notificationBuilder to include the ongoing activity data.
+ ongoingActivity.apply(applicationContext)
+
+ // Post the notification.
+ startForeground(NOTIFICATION_ID, notificationBuilder.build())
+ // [END android_wear_ongoing_activity_post_notification]
+ }
+}
+
+class AlwaysOnService3 : AlwaysOnServiceBase() {
+ override fun createNotification() {
+ // Creates an ongoing activity that demonstrates dynamic status text (a timer)
+ val activityIntent =
+ Intent(this, AlwaysOnActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+
+ val pendingIntent =
+ PendingIntent.getActivity(
+ this,
+ 0,
+ activityIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
+ )
+
+ val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle("Always On Service")
+ .setContentText("Service is running in background")
+ .setSmallIcon(R.drawable.animated_walk)
+ // Category helps the system prioritize the ongoing activity
+ .setCategory(NotificationCompat.CATEGORY_WORKOUT)
+ .setContentIntent(pendingIntent)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setOngoing(true) // Important!
+
+ // [START android_wear_ongoing_activity_create_status]
+ // Define a template with placeholders for the activity type and the timer.
+ val statusTemplate = "#type# for #time#"
+
+ // Set the start time for a stopwatch.
+ // Use SystemClock.elapsedRealtime() for time-based parts.
+ val runStartTime = SystemClock.elapsedRealtime()
+
+ val ongoingActivityStatus = Status.Builder()
+ // Sets the template string.
+ .addTemplate(statusTemplate)
+ // Fills the #type# placeholder with a static text part.
+ .addPart("type", Status.TextPart("Run"))
+ // Fills the #time# placeholder with a stopwatch part.
+ .addPart("time", Status.StopwatchPart(runStartTime))
+ .build()
+ // [END android_wear_ongoing_activity_create_status]
+
+ // [START android_wear_ongoing_activity_set_status]
+ val ongoingActivity =
+ OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
+ // [START_EXCLUDE]
+ .setAnimatedIcon(R.drawable.animated_walk)
+ .setStaticIcon(R.drawable.ic_walk)
+ .setTouchIntent(pendingIntent)
+ // [END_EXCLUDE]
+ // Add the status to the OngoingActivity.
+ .setStatus(ongoingActivityStatus)
+ .build()
+ // [END android_wear_ongoing_activity_set_status]
+
+ ongoingActivity.apply(applicationContext)
+ startForeground(NOTIFICATION_ID, notificationBuilder.build())
}
- // [END android_wear_ongoing_activity_create_notification]
}
diff --git a/wear/src/main/java/com/example/wear/snippets/audio/AudioActivity.kt b/wear/src/main/java/com/example/wear/snippets/audio/AudioActivity.kt
new file mode 100644
index 000000000..5ba03df50
--- /dev/null
+++ b/wear/src/main/java/com/example/wear/snippets/audio/AudioActivity.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.wear.snippets.audio
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.media.AudioDeviceCallback
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.activity.ComponentActivity
+import androidx.annotation.OptIn
+import androidx.media3.common.AudioAttributes
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.WearUnsuitableOutputPlaybackSuppressionResolverListener
+
+class AudioActivity : ComponentActivity() {
+
+ // [START android_wear_audio_detect_devices]
+ private val audioManager: AudioManager by lazy {
+ getSystemService(AUDIO_SERVICE) as AudioManager
+ }
+
+ fun audioOutputAvailable(type: Int): Boolean {
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+ return false
+ }
+ return audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS).any { it.type == type }
+ }
+ // [END android_wear_audio_detect_devices]
+
+ @OptIn(UnstableApi::class)
+ fun buildExoPlayer(context: Context): ExoPlayer {
+ // [START android_wear_exoplayer_audio_output_suppression]
+ val exoPlayer = ExoPlayer.Builder(context)
+ .setAudioAttributes(AudioAttributes.DEFAULT, true)
+ .setSuppressPlaybackOnUnsuitableOutput(true)
+ .build()
+ // [END android_wear_exoplayer_audio_output_suppression]
+ // [START android_wear_exoplayer_audio_output_suppression_listener]
+ exoPlayer.addListener(WearUnsuitableOutputPlaybackSuppressionResolverListener(context))
+ // [END android_wear_exoplayer_audio_output_suppression_listener]
+ return exoPlayer
+ }
+
+ override fun onCreate(savedInstanceState: android.os.Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // [START android_wear_audio_detect_devices_sample]
+ val hasSpeaker = audioOutputAvailable(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
+ val hasBluetoothHeadset = audioOutputAvailable(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP)
+ val hasBLEBroadcast = audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_BROADCAST)
+ val hasBLEHeadset = audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_HEADSET)
+ val hasBLESpeaker = audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_SPEAKER)
+ // [END android_wear_audio_detect_devices_sample]
+ println("Has speaker: $hasSpeaker")
+ println("Has Bluetooth headset: $hasBluetoothHeadset")
+ println("Has BLE broadcast: $hasBLEBroadcast")
+ println("Has BLE headset: $hasBLEHeadset")
+ println("Has BLE speaker: $hasBLESpeaker")
+
+ // [START android_wear_audio_register_callback]
+ val audioDeviceCallback =
+ object : AudioDeviceCallback() {
+ override fun onAudioDevicesAdded(addedDevices: Array?) {
+ super.onAudioDevicesAdded(addedDevices)
+ if (audioOutputAvailable(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) ||
+ audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_BROADCAST) ||
+ audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_HEADSET) ||
+ audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_SPEAKER)
+ ) {
+ // A Bluetooth or BLE device is connected and available for playback.
+ }
+ }
+ override fun onAudioDevicesRemoved(removedDevices: Array?) {
+ super.onAudioDevicesRemoved(removedDevices)
+ if (!(audioOutputAvailable(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP)) &&
+ !(audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_BROADCAST)) &&
+ !(audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_HEADSET)) &&
+ !(audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_SPEAKER))
+ ) {
+ // No Bluetooth or BLE devices are connected anymore.
+ }
+ }
+ }
+
+ audioManager.registerAudioDeviceCallback(audioDeviceCallback, /*handler=*/ null)
+ // [END android_wear_audio_register_callback]
+ }
+}
diff --git a/wear/src/main/java/com/example/wear/snippets/audio/BluetoothSettings.kt b/wear/src/main/java/com/example/wear/snippets/audio/BluetoothSettings.kt
new file mode 100644
index 000000000..d2ebbd13e
--- /dev/null
+++ b/wear/src/main/java/com/example/wear/snippets/audio/BluetoothSettings.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.wear.snippets.audio
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+
+object BluetoothSettings {
+ // [START android_wear_bluetooth_settings]
+ fun Context.launchBluetoothSettings(closeOnConnect: Boolean = true) {
+ val intent = with(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ putExtra("EXTRA_CONNECTION_ONLY", true)
+ if (closeOnConnect) {
+ putExtra("EXTRA_CLOSE_ON_CONNECT", true)
+ }
+ putExtra("android.bluetooth.devicepicker.extra.FILTER_TYPE", FILTER_TYPE_AUDIO)
+ }
+ startActivity(intent)
+ }
+
+ internal const val FILTER_TYPE_AUDIO = 1
+ // [END android_wear_bluetooth_settings]
+}
diff --git a/wear/src/main/java/com/example/wear/snippets/hardwarebuttons/HardwareButtonsActivity.kt b/wear/src/main/java/com/example/wear/snippets/hardwarebuttons/HardwareButtonsActivity.kt
new file mode 100644
index 000000000..e9c8b471c
--- /dev/null
+++ b/wear/src/main/java/com/example/wear/snippets/hardwarebuttons/HardwareButtonsActivity.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.wear.snippets.hardwarebuttons
+
+import android.app.Activity
+import android.content.Context
+import android.util.Log
+import android.view.KeyEvent
+import androidx.activity.ComponentActivity
+import androidx.wear.input.WearableButtons
+
+class HardwareButtonsActivity : ComponentActivity() {
+ // [START android_wear_hardware_buttons_events]
+ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+ return if (event?.repeatCount == 0) {
+ when (keyCode) {
+ KeyEvent.KEYCODE_STEM_1 -> {
+ Log.d(TAG, "KEYCODE_STEM_1 pressed")
+ true
+ }
+ KeyEvent.KEYCODE_STEM_2 -> {
+ Log.d(TAG, "KEYCODE_STEM_2 pressed")
+ true
+ }
+ else -> {
+ super.onKeyDown(keyCode, event)
+ }
+ }
+ } else {
+ super.onKeyDown(keyCode, event)
+ }
+ }
+ // [END android_wear_hardware_buttons_events]
+
+ fun hardwareButtonsCount(context: Context, activity: Activity) {
+ // [START android_wear_hardware_buttons_count]
+ val count = WearableButtons.getButtonCount(context)
+
+ if (count > 1) {
+ Log.d(TAG, "More than one button available")
+ }
+
+ val buttonInfo = WearableButtons.getButtonInfo(
+ activity,
+ KeyEvent.KEYCODE_STEM_1
+ )
+
+ if (buttonInfo == null) {
+ // KEYCODE_STEM_1 is unavailable
+ Log.d(TAG, "KEYCODE_STEM_1 not available")
+ } else {
+ // KEYCODE_STEM_1 is present on the device
+ Log.d(TAG, "KEYCODE_STEM_1 is present on the device")
+ }
+ // [END android_wear_hardware_buttons_count]
+ }
+}
+private const val TAG = "HardwareButtons"
diff --git a/wear/src/main/java/com/example/wear/snippets/location/LocationActivity.kt b/wear/src/main/java/com/example/wear/snippets/location/LocationActivity.kt
new file mode 100644
index 000000000..c51c6ef93
--- /dev/null
+++ b/wear/src/main/java/com/example/wear/snippets/location/LocationActivity.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.wear.snippets.location
+
+import android.content.pm.PackageManager
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
+import androidx.wear.compose.material3.ListHeader
+import androidx.wear.compose.material3.ScreenScaffold
+import androidx.wear.compose.material3.Text
+import com.google.android.horologist.compose.layout.AppScaffold
+import com.google.android.horologist.compose.layout.ColumnItemType
+import com.google.android.horologist.compose.layout.rememberResponsiveColumnPadding
+
+
+// [START android_wear_location]
+class LocationActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // [START_EXCLUDE]
+ setContent {
+ WearApp(hasGps = { hasGps() })
+ }
+ // [END_EXCLUDE]
+ }
+ fun hasGps(): Boolean =
+ packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)
+}
+// [END android_wear_location]
+
+@Composable
+fun WearApp(hasGps: () -> Boolean) {
+
+ val columnState = rememberTransformingLazyColumnState()
+ val contentPadding = rememberResponsiveColumnPadding(
+ first = ColumnItemType.ListHeader,
+ last = ColumnItemType.Button,
+ )
+ AppScaffold {
+ ScreenScaffold(
+ scrollState = columnState,
+ contentPadding = contentPadding
+ ) { contentPadding ->
+ TransformingLazyColumn(
+ state = columnState,
+ contentPadding = contentPadding
+ ) {
+ item {
+ ListHeader(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ if (!hasGps()) {
+ Text(text = "This hardware doesn't have GPS")
+ // Fall back to functionality that doesn't use location or
+ // warn the user that location function isn't available.
+ }
+ else {
+ Text(text = "This hardware has GPS")
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt
index 52e9c2eb7..5f33d63d1 100644
--- a/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt
+++ b/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt
@@ -21,6 +21,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import com.example.wear.snippets.m3.list.ComposeList
+import com.example.wear.snippets.m3.pager.HorizontalPager
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -35,5 +36,5 @@ class MainActivity : ComponentActivity() {
@Composable
fun WearApp() {
// insert here the snippet you want to test
- ComposeList()
+ HorizontalPager()
}
diff --git a/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt b/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt
index 83f0f58d0..5e1e4c12f 100644
--- a/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt
+++ b/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt
@@ -74,7 +74,7 @@ fun MessageDetail(id: String) {
contentPadding = padding
) { scaffoldPaddingValues ->
// Screen content goes here
- // [END android_wear_navigation]
+ // [START_EXCLUDE]
TransformingLazyColumn(
state = scrollState,
contentPadding = scaffoldPaddingValues
@@ -87,6 +87,8 @@ fun MessageDetail(id: String) {
)
}
}
+ // [END_EXCLUDE]
+ // [END android_wear_navigation]
}
}
diff --git a/wear/src/main/java/com/example/wear/snippets/m3/pager/Pager.kt b/wear/src/main/java/com/example/wear/snippets/m3/pager/Pager.kt
new file mode 100644
index 000000000..38e68513f
--- /dev/null
+++ b/wear/src/main/java/com/example/wear/snippets/m3/pager/Pager.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.wear.snippets.m3.pager
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
+import androidx.wear.compose.foundation.pager.HorizontalPager
+import androidx.wear.compose.foundation.pager.rememberPagerState
+import androidx.wear.compose.material3.AnimatedPage
+import androidx.wear.compose.material3.AppScaffold
+import androidx.wear.compose.material3.HorizontalPagerScaffold
+import androidx.wear.compose.material3.ListHeader
+import androidx.wear.compose.material3.ScreenScaffold
+import androidx.wear.compose.material3.Text
+import com.google.android.horologist.compose.layout.ColumnItemType
+import com.google.android.horologist.compose.layout.rememberResponsiveColumnPadding
+
+@Composable
+fun HorizontalPager() {
+ // [START android_wear_horizontal_pager]
+ AppScaffold {
+ val pagerState = rememberPagerState(pageCount = { 10 })
+ val columnState = rememberTransformingLazyColumnState()
+ val contentPadding = rememberResponsiveColumnPadding(
+ first = ColumnItemType.ListHeader,
+ last = ColumnItemType.BodyText,
+ )
+ HorizontalPagerScaffold(pagerState = pagerState) {
+ HorizontalPager(
+ state = pagerState,
+ ) { page ->
+ AnimatedPage(pageIndex = page, pagerState = pagerState) {
+ ScreenScaffold(
+ scrollState = columnState,
+ contentPadding = contentPadding
+ ) { contentPadding ->
+ TransformingLazyColumn(
+ state = columnState,
+ contentPadding = contentPadding
+ ) {
+ item {
+ ListHeader(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(text = "Pager sample")
+ }
+ }
+ item {
+ if (page == 0) {
+ Text(text = "Page #$page. Swipe right")
+ }
+ else{
+ Text(text = "Page #$page. Swipe left and right")
+ }
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ // [END android_wear_horizontal_pager]
+}
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/animated_walk.xml b/wear/src/main/res/drawable/animated_walk.xml
index e94991e07..b9e7fa835 100644
--- a/wear/src/main/res/drawable/animated_walk.xml
+++ b/wear/src/main/res/drawable/animated_walk.xml
@@ -1,12 +1,12 @@
diff --git a/wear/src/main/res/drawable/ic_launcher_background.xml b/wear/src/main/res/drawable/ic_launcher_background.xml
index ca3826a46..67a0a8f0d 100644
--- a/wear/src/main/res/drawable/ic_launcher_background.xml
+++ b/wear/src/main/res/drawable/ic_launcher_background.xml
@@ -1,4 +1,19 @@
+
+
-
\ No newline at end of file
+
diff --git a/wear/src/main/res/drawable/ic_walk.xml b/wear/src/main/res/drawable/ic_walk.xml
index 6c226e943..0d0bf8dca 100644
--- a/wear/src/main/res/drawable/ic_walk.xml
+++ b/wear/src/main/res/drawable/ic_walk.xml
@@ -1,3 +1,19 @@
+
+
+
-
\ No newline at end of file
+
diff --git a/wear/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/wear/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index bbd3e0212..8ebcf9c97 100644
--- a/wear/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/wear/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,20 @@
+
-
\ No newline at end of file
+
diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml
index 19e308a68..d0ddb884a 100644
--- a/wear/src/main/res/values/strings.xml
+++ b/wear/src/main/res/values/strings.xml
@@ -1,3 +1,19 @@
+
+
Wear Snippets
Voice Input
@@ -9,4 +25,4 @@
My Complication
My Timeline Complication
Configuration activity
-
\ No newline at end of file
+
diff --git a/xr/build.gradle.kts b/xr/build.gradle.kts
index 51c224f20..9b632756d 100644
--- a/xr/build.gradle.kts
+++ b/xr/build.gradle.kts
@@ -6,21 +6,21 @@ plugins {
android {
namespace = "com.example.xr"
- compileSdk = 35
+ compileSdk = libs.versions.compileSdk.get().toInt()
defaultConfig {
applicationId = "com.example.xr"
minSdk = 34
- targetSdk = 35
+ targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
- jvmToolchain(11)
+ jvmToolchain(17)
}
buildFeatures {
compose = true
diff --git a/xr/src/main/AndroidManifest.xml b/xr/src/main/AndroidManifest.xml
index 6d6399c14..bc726787c 100644
--- a/xr/src/main/AndroidManifest.xml
+++ b/xr/src/main/AndroidManifest.xml
@@ -1,4 +1,19 @@
+
@@ -6,4 +21,4 @@
android:label="XR"
tools:ignore="MissingApplicationIcon" />
-
\ No newline at end of file
+
diff --git a/xr/src/main/java/com/example/xr/arcore/Anchors.kt b/xr/src/main/java/com/example/xr/arcore/Anchors.kt
index 56c7cb8d7..baafef6ed 100644
--- a/xr/src/main/java/com/example/xr/arcore/Anchors.kt
+++ b/xr/src/main/java/com/example/xr/arcore/Anchors.kt
@@ -34,11 +34,11 @@ fun configureAnchoring(session: Session) {
anchorPersistence = Config.AnchorPersistenceMode.LOCAL,
)
when (val result = session.configure(newConfig)) {
+ is SessionConfigureSuccess -> TODO(/* Success! */)
is SessionConfigureConfigurationNotSupported ->
TODO(/* Some combinations of configurations are not valid. Handle this failure case. */)
- is SessionConfigureSuccess -> TODO(/* Success! */)
else ->
- TODO(/* A different unhandled exception was thrown. */)
+ TODO(/* The session could not be configured. See SessionConfigureResult for possible causes. */)
}
// [END androidxr_arcore_anchoring_configure]
}
diff --git a/xr/src/main/java/com/example/xr/arcore/Hands.kt b/xr/src/main/java/com/example/xr/arcore/Hands.kt
index 1507bed7c..0a671a616 100644
--- a/xr/src/main/java/com/example/xr/arcore/Hands.kt
+++ b/xr/src/main/java/com/example/xr/arcore/Hands.kt
@@ -20,8 +20,8 @@ import android.app.Activity
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import androidx.xr.arcore.Hand
+import androidx.xr.arcore.HandJointType
import androidx.xr.runtime.Config
-import androidx.xr.runtime.HandJointType
import androidx.xr.runtime.Session
import androidx.xr.runtime.SessionConfigureConfigurationNotSupported
import androidx.xr.runtime.SessionConfigureSuccess
@@ -40,11 +40,11 @@ fun ComponentActivity.configureSession(session: Session) {
handTracking = Config.HandTrackingMode.BOTH
)
when (val result = session.configure(newConfig)) {
+ is SessionConfigureSuccess -> TODO(/* Success! */)
is SessionConfigureConfigurationNotSupported ->
TODO(/* Some combinations of configurations are not valid. Handle this failure case. */)
- is SessionConfigureSuccess -> TODO(/* Success! */)
else ->
- TODO(/* A different unhandled exception was thrown. */)
+ TODO(/* The session could not be configured. See SessionConfigureResult for possible causes. */)
}
// [END androidxr_arcore_hand_configure]
}
@@ -80,7 +80,7 @@ fun ComponentActivity.renderPlanetAtHandPalm(leftHandState: Hand.State) {
val session: Session = null!!
val palmEntity: GltfModelEntity = null!!
// [START androidxr_arcore_hand_entityAtHandPalm]
- val palmPose = leftHandState.handJoints[HandJointType.PALM] ?: return
+ val palmPose = leftHandState.handJoints[HandJointType.HAND_JOINT_TYPE_PALM] ?: return
// the down direction points in the same direction as the palm
val angle = Vector3.angleBetween(palmPose.rotation * Vector3.Down, Vector3.Up)
@@ -101,7 +101,7 @@ fun ComponentActivity.renderPlanetAtFingerTip(rightHandState: Hand.State) {
val indexFingerEntity: GltfModelEntity = null!!
// [START androidxr_arcore_hand_entityAtIndexFingerTip]
- val tipPose = rightHandState.handJoints[HandJointType.INDEX_TIP] ?: return
+ val tipPose = rightHandState.handJoints[HandJointType.HAND_JOINT_TYPE_INDEX_TIP] ?: return
// the forward direction points towards the finger tip.
val angle = Vector3.angleBetween(tipPose.rotation * Vector3.Forward, Vector3.Up)
@@ -120,9 +120,9 @@ fun ComponentActivity.renderPlanetAtFingerTip(rightHandState: Hand.State) {
private fun detectPinch(session: Session, handState: Hand.State): Boolean {
// [START androidxr_arcore_hand_pinch_gesture]
- val thumbTip = handState.handJoints[HandJointType.THUMB_TIP] ?: return false
+ val thumbTip = handState.handJoints[HandJointType.HAND_JOINT_TYPE_THUMB_TIP] ?: return false
val thumbTipPose = session.scene.perceptionSpace.transformPoseTo(thumbTip, session.scene.activitySpace)
- val indexTip = handState.handJoints[HandJointType.INDEX_TIP] ?: return false
+ val indexTip = handState.handJoints[HandJointType.HAND_JOINT_TYPE_INDEX_TIP] ?: return false
val indexTipPose = session.scene.perceptionSpace.transformPoseTo(indexTip, session.scene.activitySpace)
return Vector3.distance(thumbTipPose.translation, indexTipPose.translation) < 0.05
// [END androidxr_arcore_hand_pinch_gesture]
@@ -136,8 +136,8 @@ private fun detectStop(session: Session, handState: Hand.State): Boolean {
val forward2 = handState.handJoints[joint2]?.forward ?: return false
return Vector3.angleBetween(forward1, forward2) < threshold
}
- return pointingInSameDirection(HandJointType.INDEX_PROXIMAL, HandJointType.INDEX_TIP) &&
- pointingInSameDirection(HandJointType.MIDDLE_PROXIMAL, HandJointType.MIDDLE_TIP) &&
- pointingInSameDirection(HandJointType.RING_PROXIMAL, HandJointType.RING_TIP)
+ return pointingInSameDirection(HandJointType.HAND_JOINT_TYPE_INDEX_PROXIMAL, HandJointType.HAND_JOINT_TYPE_INDEX_TIP) &&
+ pointingInSameDirection(HandJointType.HAND_JOINT_TYPE_MIDDLE_PROXIMAL, HandJointType.HAND_JOINT_TYPE_MIDDLE_TIP) &&
+ pointingInSameDirection(HandJointType.HAND_JOINT_TYPE_RING_PROXIMAL, HandJointType.HAND_JOINT_TYPE_RING_TIP)
// [END androidxr_arcore_hand_stop_gesture]
}
diff --git a/xr/src/main/java/com/example/xr/arcore/Planes.kt b/xr/src/main/java/com/example/xr/arcore/Planes.kt
index b5017dcb7..42689bf1f 100644
--- a/xr/src/main/java/com/example/xr/arcore/Planes.kt
+++ b/xr/src/main/java/com/example/xr/arcore/Planes.kt
@@ -31,11 +31,11 @@ fun configurePlaneTracking(session: Session) {
planeTracking = Config.PlaneTrackingMode.HORIZONTAL_AND_VERTICAL,
)
when (val result = session.configure(newConfig)) {
+ is SessionConfigureSuccess -> TODO(/* Success! */)
is SessionConfigureConfigurationNotSupported ->
TODO(/* Some combinations of configurations are not valid. Handle this failure case. */)
- is SessionConfigureSuccess -> TODO(/* Success! */)
else ->
- TODO(/* A different unhandled exception was thrown. */)
+ TODO(/* The session could not be configured. See SessionConfigureResult for possible causes. */)
}
// [END androidxr_arcore_planetracking_configure]
}
diff --git a/xr/src/main/java/com/example/xr/compose/Orbiter.kt b/xr/src/main/java/com/example/xr/compose/Orbiter.kt
index f01c4cd1f..8b89e4c63 100644
--- a/xr/src/main/java/com/example/xr/compose/Orbiter.kt
+++ b/xr/src/main/java/com/example/xr/compose/Orbiter.kt
@@ -40,13 +40,13 @@ import androidx.xr.compose.spatial.ContentEdge
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterOffsetType
import androidx.xr.compose.spatial.Subspace
+import androidx.xr.compose.subspace.MovePolicy
+import androidx.xr.compose.subspace.ResizePolicy
import androidx.xr.compose.subspace.SpatialPanel
import androidx.xr.compose.subspace.SpatialRow
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape
import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.height
-import androidx.xr.compose.subspace.layout.movable
-import androidx.xr.compose.subspace.layout.resizable
import androidx.xr.compose.subspace.layout.width
import com.example.xr.R
@@ -57,9 +57,9 @@ private fun OrbiterExampleSubspace() {
SpatialPanel(
SubspaceModifier
.height(824.dp)
- .width(1400.dp)
- .movable()
- .resizable()
+ .width(1400.dp),
+ dragPolicy = MovePolicy(),
+ resizePolicy = ResizePolicy(),
) {
SpatialPanelContent()
OrbiterExample()
diff --git a/xr/src/main/java/com/example/xr/compose/SpatialPanel.kt b/xr/src/main/java/com/example/xr/compose/SpatialPanel.kt
index c3a3a58ef..2b75a426b 100644
--- a/xr/src/main/java/com/example/xr/compose/SpatialPanel.kt
+++ b/xr/src/main/java/com/example/xr/compose/SpatialPanel.kt
@@ -29,11 +29,11 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.xr.compose.platform.LocalSpatialCapabilities
import androidx.xr.compose.spatial.Subspace
+import androidx.xr.compose.subspace.MovePolicy
+import androidx.xr.compose.subspace.ResizePolicy
import androidx.xr.compose.subspace.SpatialPanel
import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.height
-import androidx.xr.compose.subspace.layout.movable
-import androidx.xr.compose.subspace.layout.resizable
import androidx.xr.compose.subspace.layout.width
@Composable
@@ -43,9 +43,9 @@ private fun SpatialPanelExample() {
SpatialPanel(
SubspaceModifier
.height(824.dp)
- .width(1400.dp)
- .movable()
- .resizable()
+ .width(1400.dp),
+ dragPolicy = MovePolicy(),
+ resizePolicy = ResizePolicy(),
) {
SpatialPanelContent()
}
@@ -81,9 +81,8 @@ private fun ContentInSpatialPanel() {
if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
Subspace {
SpatialPanel(
- SubspaceModifier
- .resizable(true)
- .movable(true)
+ dragPolicy = MovePolicy(),
+ resizePolicy = ResizePolicy(),
) {
AppContent()
}
diff --git a/xr/src/main/java/com/example/xr/compose/Volume.kt b/xr/src/main/java/com/example/xr/compose/Volume.kt
index cce37ca83..2144cc3c2 100644
--- a/xr/src/main/java/com/example/xr/compose/Volume.kt
+++ b/xr/src/main/java/com/example/xr/compose/Volume.kt
@@ -28,13 +28,13 @@ import androidx.compose.ui.unit.sp
import androidx.xr.compose.platform.LocalSession
import androidx.xr.compose.spatial.Subspace
import androidx.xr.compose.subspace.ExperimentalSubspaceVolumeApi
+import androidx.xr.compose.subspace.MovePolicy
+import androidx.xr.compose.subspace.ResizePolicy
import androidx.xr.compose.subspace.SpatialPanel
import androidx.xr.compose.subspace.Volume
import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.height
-import androidx.xr.compose.subspace.layout.movable
import androidx.xr.compose.subspace.layout.offset
-import androidx.xr.compose.subspace.layout.resizable
import androidx.xr.compose.subspace.layout.scale
import androidx.xr.compose.subspace.layout.width
import kotlinx.coroutines.launch
@@ -44,8 +44,9 @@ private fun VolumeExample() {
// [START androidxr_compose_Volume]
Subspace {
SpatialPanel(
- SubspaceModifier.height(1500.dp).width(1500.dp)
- .resizable().movable()
+ SubspaceModifier.height(1500.dp).width(1500.dp),
+ dragPolicy = MovePolicy(),
+ resizePolicy = ResizePolicy(),
) {
ObjectInAVolume(true)
Box(
@@ -70,7 +71,7 @@ fun ObjectInAVolume(show3DObject: Boolean) {
val volumeXOffset = 0.dp
val volumeYOffset = 0.dp
val volumeZOffset = 0.dp
- // [END_EXCLUDE silent]
+ // [END_EXCLUDE]
val session = checkNotNull(LocalSession.current)
val scope = rememberCoroutineScope()
if (show3DObject) {
@@ -79,7 +80,6 @@ fun ObjectInAVolume(show3DObject: Boolean) {
modifier = SubspaceModifier
.offset(volumeXOffset, volumeYOffset, volumeZOffset) // Relative position
.scale(1.2f) // Scale to 120% of the size
-
) { parent ->
scope.launch {
// Load your 3D model here
diff --git a/xr/src/main/java/com/example/xr/scenecore/GltfEntity.kt b/xr/src/main/java/com/example/xr/scenecore/GltfEntity.kt
index 997436ddf..cf39f9f4f 100644
--- a/xr/src/main/java/com/example/xr/scenecore/GltfEntity.kt
+++ b/xr/src/main/java/com/example/xr/scenecore/GltfEntity.kt
@@ -36,7 +36,7 @@ private suspend fun loadGltfFile(session: Session) {
private fun createModelEntity(session: Session, gltfModel: GltfModel) {
// [START androidxr_scenecore_gltfmodelentity_create]
if (session.scene.spatialCapabilities
- .hasCapability(SpatialCapabilities.SPATIAL_CAPABILITY_3D_CONTENT)
+ .hasCapability(SpatialCapabilities.SPATIAL_CAPABILITY_3D_CONTENT)
) {
val gltfEntity = GltfModelEntity.create(session, gltfModel)
}
diff --git a/xr/src/main/java/com/example/xr/scenecore/ResizableComponent.kt b/xr/src/main/java/com/example/xr/scenecore/ResizableComponent.kt
index 28b70027d..e4136ea72 100644
--- a/xr/src/main/java/com/example/xr/scenecore/ResizableComponent.kt
+++ b/xr/src/main/java/com/example/xr/scenecore/ResizableComponent.kt
@@ -17,6 +17,7 @@
package com.example.xr.scenecore
import androidx.xr.runtime.Session
+import androidx.xr.runtime.math.FloatSize2d
import androidx.xr.runtime.math.FloatSize3d
import androidx.xr.scenecore.ResizableComponent
import androidx.xr.scenecore.ResizeEvent
@@ -32,11 +33,11 @@ private fun resizableComponentExample(
val resizableComponent = ResizableComponent.create(session) { event ->
if (event.resizeState == ResizeEvent.ResizeState.RESIZE_STATE_END) {
// update the Entity to reflect the new size
- surfaceEntity.canvasShape = SurfaceEntity.CanvasShape.Quad(event.newSize.width, event.newSize.height)
+ surfaceEntity.shape = SurfaceEntity.Shape.Quad(FloatSize2d(event.newSize.width, event.newSize.height))
}
}
resizableComponent.minimumEntitySize = FloatSize3d(177f, 100f, 1f)
- resizableComponent.fixedAspectRatio = 16f / 9f // Specify a 16:9 aspect ratio
+ resizableComponent.isFixedAspectRatioEnabled = true // Maintain a fixed aspect ratio when resizing
surfaceEntity.addComponent(resizableComponent)
// [END androidxr_scenecore_resizableComponentExample]
diff --git a/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt b/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt
index b68e67713..25b1556a4 100644
--- a/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt
+++ b/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt
@@ -40,7 +40,7 @@ private fun playSpatialAudioAtEntity(session: Session, appContext: Context, enti
// [START androidxr_scenecore_playSpatialAudio]
// Check spatial capabilities before using spatial audio
if (session.scene.spatialCapabilities
- .hasCapability(SpatialCapabilities.SPATIAL_CAPABILITY_SPATIAL_AUDIO)
+ .hasCapability(SpatialCapabilities.SPATIAL_CAPABILITY_SPATIAL_AUDIO)
) { // The session has spatial audio capabilities
val maxVolume = 1F
val lowPriority = 0
diff --git a/xr/src/main/java/com/example/xr/scenecore/SpatialVideo.kt b/xr/src/main/java/com/example/xr/scenecore/SpatialVideo.kt
index ec733bb61..85f1dfa05 100644
--- a/xr/src/main/java/com/example/xr/scenecore/SpatialVideo.kt
+++ b/xr/src/main/java/com/example/xr/scenecore/SpatialVideo.kt
@@ -24,11 +24,11 @@ import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.xr.runtime.Session
+import androidx.xr.runtime.math.FloatSize2d
import androidx.xr.runtime.math.Pose
import androidx.xr.runtime.math.Vector3
import androidx.xr.scenecore.SurfaceEntity
import androidx.xr.scenecore.Texture
-import androidx.xr.scenecore.TextureSampler
import androidx.xr.scenecore.scene
import java.nio.file.Paths
import kotlinx.coroutines.launch
@@ -36,10 +36,10 @@ import kotlinx.coroutines.launch
private fun ComponentActivity.surfaceEntityCreate(xrSession: Session) {
// [START androidxr_scenecore_surfaceEntityCreate]
val stereoSurfaceEntity = SurfaceEntity.create(
- xrSession,
- SurfaceEntity.StereoMode.SIDE_BY_SIDE,
- Pose(Vector3(0.0f, 0.0f, -1.5f)),
- SurfaceEntity.CanvasShape.Quad(1.0f, 1.0f)
+ session = xrSession,
+ stereoMode = SurfaceEntity.StereoMode.STEREO_MODE_SIDE_BY_SIDE,
+ pose = Pose(Vector3(0.0f, 0.0f, -1.5f)),
+ shape = SurfaceEntity.Shape.Quad(FloatSize2d(1.0f, 1.0f))
)
val videoUri = Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
@@ -60,13 +60,13 @@ private fun ComponentActivity.surfaceEntityCreateSbs(xrSession: Session) {
// Set up the surface for playing a 180° video on a hemisphere.
val hemisphereStereoSurfaceEntity =
SurfaceEntity.create(
- xrSession,
- SurfaceEntity.StereoMode.SIDE_BY_SIDE,
- xrSession.scene.spatialUser.head?.transformPoseTo(
+ session = xrSession,
+ stereoMode = SurfaceEntity.StereoMode.STEREO_MODE_SIDE_BY_SIDE,
+ pose = xrSession.scene.spatialUser.head?.transformPoseTo(
Pose.Identity,
xrSession.scene.activitySpace
)!!,
- SurfaceEntity.CanvasShape.Vr180Hemisphere(1.0f),
+ shape = SurfaceEntity.Shape.Hemisphere(1.0f),
)
// ... and use the surface for playing the media.
// [END androidxr_scenecore_surfaceEntityCreateSbs]
@@ -77,13 +77,13 @@ private fun ComponentActivity.surfaceEntityCreateTb(xrSession: Session) {
// Set up the surface for playing a 360° video on a sphere.
val sphereStereoSurfaceEntity =
SurfaceEntity.create(
- xrSession,
- SurfaceEntity.StereoMode.TOP_BOTTOM,
- xrSession.scene.spatialUser.head?.transformPoseTo(
+ session = xrSession,
+ stereoMode = SurfaceEntity.StereoMode.STEREO_MODE_TOP_BOTTOM,
+ pose = xrSession.scene.spatialUser.head?.transformPoseTo(
Pose.Identity,
xrSession.scene.activitySpace
)!!,
- SurfaceEntity.CanvasShape.Vr360Sphere(1.0f),
+ shape = SurfaceEntity.Shape.Sphere(1.0f),
)
// ... and use the surface for playing the media.
// [END androidxr_scenecore_surfaceEntityCreateTb]
@@ -93,10 +93,10 @@ private fun ComponentActivity.surfaceEntityCreateMVHEVC(xrSession: Session) {
// [START androidxr_scenecore_surfaceEntityCreateMVHEVC]
// Create the SurfaceEntity with the StereoMode corresponding to the MV-HEVC content
val stereoSurfaceEntity = SurfaceEntity.create(
- xrSession,
- SurfaceEntity.StereoMode.MULTIVIEW_LEFT_PRIMARY,
- Pose(Vector3(0.0f, 0.0f, -1.5f)),
- SurfaceEntity.CanvasShape.Quad(1.0f, 1.0f)
+ session = xrSession,
+ stereoMode = SurfaceEntity.StereoMode.STEREO_MODE_MULTIVIEW_LEFT_PRIMARY,
+ pose = Pose(Vector3(0.0f, 0.0f, -1.5f)),
+ shape = SurfaceEntity.Shape.Quad(FloatSize2d(1.0f, 1.0f))
)
val videoUri = Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
@@ -123,10 +123,10 @@ private fun ComponentActivity.surfaceEntityCreateDRM(xrSession: Session) {
// Create the SurfaceEntity with the PROTECTED content security level.
val protectedSurfaceEntity = SurfaceEntity.create(
session = xrSession,
- stereoMode = SurfaceEntity.StereoMode.SIDE_BY_SIDE,
+ stereoMode = SurfaceEntity.StereoMode.STEREO_MODE_SIDE_BY_SIDE,
pose = Pose(Vector3(0.0f, 0.0f, -1.5f)),
- canvasShape = SurfaceEntity.CanvasShape.Quad(1.0f, 1.0f),
- contentSecurityLevel = SurfaceEntity.ContentSecurityLevel.PROTECTED
+ shape = SurfaceEntity.Shape.Quad(FloatSize2d(1.0f, 1.0f)),
+ surfaceProtection = SurfaceEntity.SurfaceProtection.SURFACE_PROTECTION_PROTECTED
)
// Build a MediaItem with the necessary DRM configuration.
@@ -156,20 +156,20 @@ private fun ComponentActivity.surfaceEntityHDR(xrSession: Session) {
// Define the color properties for your HDR video. These values should be specific
// to your content.
val hdrMetadata = SurfaceEntity.ContentColorMetadata(
- colorSpace = SurfaceEntity.ContentColorMetadata.ColorSpace.BT2020,
- colorTransfer = SurfaceEntity.ContentColorMetadata.ColorTransfer.ST2084, // PQ
- colorRange = SurfaceEntity.ContentColorMetadata.ColorRange.LIMITED,
- maxCLL = 1000 // Example: 1000 nits
+ colorSpace = SurfaceEntity.ContentColorMetadata.ColorSpace.COLOR_SPACE_BT2020,
+ colorTransfer = SurfaceEntity.ContentColorMetadata.ColorTransfer.COLOR_TRANSFER_ST2084, // PQ
+ colorRange = SurfaceEntity.ContentColorMetadata.ColorRange.COLOR_RANGE_LIMITED,
+ maxContentLightLevel = 1000 // Example: 1000 nits
)
// Create a SurfaceEntity, passing the HDR metadata at creation time.
val hdrSurfaceEntity = SurfaceEntity.create(
session = xrSession,
- stereoMode = SurfaceEntity.StereoMode.MONO,
+ stereoMode = SurfaceEntity.StereoMode.STEREO_MODE_MONO,
pose = Pose(Vector3(0.0f, 0.0f, -1.5f)),
- canvasShape = SurfaceEntity.CanvasShape.Quad(1.0f, 1.0f),
- contentColorMetadata = hdrMetadata
+ shape = SurfaceEntity.Shape.Quad(FloatSize2d(1.0f, 1.0f)),
)
+ hdrSurfaceEntity.contentColorMetadata = hdrMetadata
// Initialize ExoPlayer and set the surface.
val exoPlayer = ExoPlayer.Builder(this).build()
@@ -195,8 +195,8 @@ private fun surfaceEntityEdgeFeathering(xrSession: Session) {
)
// Feather the edges of the surface.
- surfaceEntity.edgeFeather =
- SurfaceEntity.EdgeFeatheringParams.SmoothFeather(0.1f, 0.1f)
+ surfaceEntity.edgeFeatheringParams =
+ SurfaceEntity.EdgeFeatheringParams.RectangleFeather(0.1f, 0.1f)
// [END androidxr_scenecore_surfaceEntityEdgeFeathering]
}
@@ -214,7 +214,6 @@ private fun surfaceEntityAlphaMasking(xrSession: Session, activity: ComponentAct
Texture.create(
xrSession,
Paths.get("textures", "alpha_mask.png"),
- TextureSampler.create()
)
// Apply the alpha mask.
diff --git a/xr/src/main/res/layout/example_fragment.xml b/xr/src/main/res/layout/example_fragment.xml
index 13aa8cbbe..607557309 100644
--- a/xr/src/main/res/layout/example_fragment.xml
+++ b/xr/src/main/res/layout/example_fragment.xml
@@ -1,4 +1,19 @@
+
@@ -6,4 +21,4 @@
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
-
\ No newline at end of file
+
diff --git a/xr/src/main/res/values/dimens.xml b/xr/src/main/res/values/dimens.xml
index ed1e9310d..25ca08b8f 100644
--- a/xr/src/main/res/values/dimens.xml
+++ b/xr/src/main/res/values/dimens.xml
@@ -1,5 +1,20 @@
+
8dp
8dp
-
\ No newline at end of file
+