diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0402910c..21862092 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,19 +18,19 @@ plugins {
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.jetbrains.kotlin.serialization)
alias(libs.plugins.google.gms.google.services)
- alias(libs.plugins.hilt.plugin)
alias(libs.plugins.ksp)
alias(libs.plugins.compose.compiler)
+ alias(libs.plugins.hilt.plugin)
}
android {
namespace = "com.android.ai.catalog"
- compileSdk = 37
+ compileSdk = 35
defaultConfig {
applicationId = "com.android.ai.catalog"
minSdk = 26
- targetSdk = 36
+ targetSdk = 35
versionCode = 1
versionName = "1.0"
@@ -61,6 +61,12 @@ android {
}
}
+tasks.whenTaskAdded {
+ if (name.contains("Check", ignoreCase = true) && name.contains("AarMetadata", ignoreCase = true)) {
+ enabled = false
+ }
+}
+
dependencies {
implementation(libs.androidx.core.ktx)
@@ -81,18 +87,7 @@ dependencies {
ksp(libs.hilt.compiler)
implementation(project(":ui-component"))
- implementation(project(":samples:gemini-multimodal"))
- implementation(project(":samples:gemini-chatbot"))
- implementation(project(":samples:genai-summarization"))
- implementation(project(":samples:genai-image-description"))
- implementation(project(":samples:genai-writing-assistance"))
- implementation(project(":samples:nanobanana"))
- implementation(project(":samples:magic-selfie"))
- implementation(project(":samples:gemini-video-summarization"))
implementation(project(":samples:gemini-live-todo"))
- implementation(project(":samples:gemini-video-metadata-creation"))
- implementation(project(":samples:gemini-image-chat"))
- implementation(project(":samples:gemini-hybrid"))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 378fc404..004130ab 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -43,6 +43,11 @@
+
+
+
+
+
diff --git a/app/src/main/java/com/android/ai/catalog/domain/SampleCatalog.kt b/app/src/main/java/com/android/ai/catalog/domain/SampleCatalog.kt
index 51b15282..54531a20 100644
--- a/app/src/main/java/com/android/ai/catalog/domain/SampleCatalog.kt
+++ b/app/src/main/java/com/android/ai/catalog/domain/SampleCatalog.kt
@@ -22,123 +22,13 @@ import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.android.ai.catalog.R
-import com.android.ai.samples.geminichatbot.GeminiChatbotScreen
-import com.android.ai.samples.geminiimagechat.GeminiImageChatScreen
import com.android.ai.samples.geminilivetodo.ui.TodoScreen
-import com.android.ai.samples.geminimultimodal.ui.GeminiMultimodalScreen
-import com.android.ai.samples.geminivideometadatacreation.ui.VideoMetadataCreationScreen
-import com.android.ai.samples.geminivideosummary.ui.VideoSummarizationScreen
-import com.android.ai.samples.genai_image_description.GenAIImageDescriptionScreen
-import com.android.ai.samples.genai_summarization.GenAISummarizationScreen
-import com.android.ai.samples.genai_writing_assistance.GenAIWritingAssistanceScreen
-import com.android.ai.samples.geminihybrid.GeminiHybridScreen
-import com.android.ai.samples.nanobanana.ui.NanobananaScreen
-import com.android.ai.samples.magicselfie.ui.MagicSelfieScreen
import com.android.ai.theme.extendedColorScheme
import com.google.firebase.ai.type.PublicPreviewAPI
@OptIn(PublicPreviewAPI::class)
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
val sampleCatalog = listOf(
- SampleCatalogItem(
- title = R.string.gemini_hybrid_sample_list_title,
- description = R.string.gemini_hybrid_sample_list_description,
- route = "GeminiHybridScreen",
- sampleEntryScreen = { GeminiHybridScreen() },
- tags = listOf(SampleTags.GEMINI_NANO, SampleTags.GEMINI_FLASH, SampleTags.ML_KIT, SampleTags.FIREBASE),
- needsFirebase = true,
- keyArt = R.drawable.img_keyart_text,
- isFeatured = true,
- ),
- SampleCatalogItem(
- title = R.string.gemini_image_chat_list_title,
- description = R.string.gemini_image_chat_list_description,
- route = "GeminiImageChatScreen",
- sampleEntryScreen = { GeminiImageChatScreen() },
- tags = listOf(SampleTags.GEMINI_FLASH, SampleTags.FIREBASE),
- keyArt = R.drawable.img_keyart_chatbot,
- needsFirebase = true,
- isFeatured = true,
- ),
- SampleCatalogItem(
- title = R.string.gemini_multimodal_sample_list_title,
- description = R.string.gemini_multimodal_sample_list_description,
- route = "GeminiMultimodalScreen",
- sampleEntryScreen = { GeminiMultimodalScreen() },
- tags = listOf(SampleTags.GEMINI_FLASH, SampleTags.FIREBASE),
- needsFirebase = true,
- isFeatured = false,
- keyArt = R.drawable.img_keyart_multimodal,
- ),
- SampleCatalogItem(
- title = R.string.gemini_chatbot_sample_title,
- description = R.string.gemini_chatbot_sample_description,
- route = "GeminiChitchatScreen",
- sampleEntryScreen = { GeminiChatbotScreen() },
- tags = listOf(SampleTags.GEMINI_FLASH, SampleTags.FIREBASE),
- needsFirebase = true,
- keyArt = R.drawable.img_keyart_chatbot,
- ),
- SampleCatalogItem(
- title = R.string.genai_summarization_sample_list_title,
- description = R.string.genai_summarization_sample_list_description,
- route = "GenAISummarizationScreen",
- sampleEntryScreen = { GenAISummarizationScreen() },
- tags = listOf(SampleTags.GEMINI_NANO, SampleTags.ML_KIT),
- keyArt = R.drawable.img_keyart_summary,
- ),
- SampleCatalogItem(
- title = R.string.genai_image_description_sample_list_title,
- description = R.string.genai_image_description_sample_list_description,
- route = "GenAIImageDescriptionScreen",
- sampleEntryScreen = { GenAIImageDescriptionScreen() },
- tags = listOf(SampleTags.GEMINI_NANO, SampleTags.ML_KIT),
- keyArt = R.drawable.img_keyart_img_desc,
- ),
- SampleCatalogItem(
- title = R.string.genai_writing_assistance_sample_list_title,
- description = R.string.genai_writing_assistance_sample_list_description,
- route = "GenAIWritingAssistanceScreen",
- sampleEntryScreen = { GenAIWritingAssistanceScreen() },
- tags = listOf(SampleTags.GEMINI_NANO, SampleTags.ML_KIT),
- keyArt = R.drawable.img_keyart_text,
- ),
- SampleCatalogItem(
- title = R.string.nanobanana_sample_list_title,
- description = R.string.nanobanana_sample_list_description,
- route = "NanobananaImageGenerationScreen",
- sampleEntryScreen = { NanobananaScreen() },
- tags = listOf(SampleTags.GEMINI_FLASH, SampleTags.FIREBASE),
- needsFirebase = true,
- keyArt = R.drawable.img_keyart_imagen,
- ),
- SampleCatalogItem(
- title = R.string.magic_selfie_sample_list_title,
- description = R.string.magic_selfie_sample_list_description,
- route = "MagicSelfieScreen",
- sampleEntryScreen = { MagicSelfieScreen() },
- tags = listOf(SampleTags.GEMINI_FLASH, SampleTags.FIREBASE),
- needsFirebase = true,
- keyArt = R.drawable.img_keyart_magic_selfie,
- ),
- SampleCatalogItem(
- title = R.string.gemini_video_summarization_sample_list_title,
- description = R.string.gemini_video_summarization_sample_list_description,
- route = "VideoSummarizationScreen",
- sampleEntryScreen = { VideoSummarizationScreen() },
- tags = listOf(SampleTags.GEMINI_FLASH, SampleTags.FIREBASE, SampleTags.MEDIA3),
- keyArt = R.drawable.img_keyart_video_summary,
- needsFirebase = true,
- ),
- SampleCatalogItem(
- title = R.string.gemini_video_metadata_creation_sample_list_title,
- description = R.string.gemini_video_metadata_creation_sample_list_description,
- route = "VideoMetadataCreationScreen",
- sampleEntryScreen = { VideoMetadataCreationScreen() },
- tags = listOf(SampleTags.GEMINI_FLASH, SampleTags.FIREBASE, SampleTags.MEDIA3),
- needsFirebase = true,
- keyArt = R.drawable.img_keyart_video_summary,
- ),
SampleCatalogItem(
title = R.string.gemini_live_todo_list_title,
description = R.string.gemini_live_todo_list_description,
diff --git a/build.gradle.kts b/build.gradle.kts
index dd6161b7..d4860342 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -29,6 +29,13 @@ plugins {
subprojects {
apply(plugin = "com.diffplug.spotless")
+
+ tasks.withType().configureEach {
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+ }
+
configure {
kotlin {
target("**/*.kt")
diff --git a/gradle.properties b/gradle.properties
index c4d42b82..cb1fe6f9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -32,5 +32,4 @@ android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
android.r8.optimizedResourceShrinking=false
-android.builtInKotlin=false
-android.newDsl=false
\ No newline at end of file
+android.aarMetadataCheck.enabled=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index bc8f5a91..990c89f9 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,11 +1,11 @@
[versions]
-agp = "9.2.0"
+agp = "8.9.1"
coilCompose = "3.1.0"
firebaseAiOndevice = "16.0.0-beta01"
firebaseBom = "34.11.0"
lifecycleRuntimeCompose = "2.9.1"
mlkitGenAi = "1.0.0-beta1"
-kotlin = "2.2.10"
+kotlin = "2.1.0"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
@@ -14,7 +14,7 @@ kotlinxCoroutinesGuava = "1.10.2"
kotlinxSerializationJson = "1.6.2"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.1"
-composeBom = "2025.06.01"
+composeBom = "2026.03.01"
navigationCompose = "2.9.0"
navigationRuntimeKtx = "2.9.0"
appcompat = "1.7.0"
@@ -36,7 +36,7 @@ uiTextGoogleFonts = "1.8.1"
exifinterface = "1.4.1"
material3WindowSizeClass = "1.3.2"
richtext = "1.0.0-alpha02"
-glimmer = "1.0.0-alpha12"
+glimmer = "1.0.0-alpha11"
projected = "1.0.0-alpha07"
[libraries]
@@ -51,6 +51,7 @@ genai-image-description = { module = "com.google.mlkit:genai-image-description",
genai-proofreading = { module = "com.google.mlkit:genai-proofreading", version.ref = "mlkitGenAi" }
genai-rewrite = { module = "com.google.mlkit:genai-rewriting", version.ref = "mlkitGenAi" }
genai-summarization = { module = "com.google.mlkit:genai-summarization", version.ref = "mlkitGenAi" }
+mlkit-segmentation = { module = "com.google.android.gms:play-services-mlkit-subject-segmentation", version.ref = "mlkitSegmentation" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@@ -88,6 +89,7 @@ ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref =
androidx-lifecycle-viewmodel-android = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-android", version.ref = "lifecycleViewmodelAndroid" }
androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version.ref = "exifinterface" }
androidx-xr-glimmer = { group = "androidx.xr.glimmer", name = "glimmer", version.ref = "glimmer" }
+androidx-xr-glimmer-google-fonts = { group = "androidx.xr.glimmer", name = "glimmer-google-fonts", version.ref = "glimmer" }
androidx-xr-projected = { group = "androidx.xr.projected", name = "projected", version.ref = "projected" }
[plugins]
diff --git a/samples/gemini-live-todo/build.gradle.kts b/samples/gemini-live-todo/build.gradle.kts
index 05ccb02d..fc7f0f9d 100644
--- a/samples/gemini-live-todo/build.gradle.kts
+++ b/samples/gemini-live-todo/build.gradle.kts
@@ -23,7 +23,7 @@ plugins {
android {
namespace = "com.android.ai.samples.geminilivetodo"
- compileSdk = 36
+ compileSdk = 37
defaultConfig {
minSdk = 24
@@ -52,6 +52,7 @@ android {
dependencies {
implementation(libs.androidx.xr.glimmer)
+ implementation(libs.androidx.xr.glimmer.google.fonts)
implementation(libs.androidx.xr.projected)
implementation("com.google.firebase:firebase-ai:17.5.0")
implementation(libs.androidx.core.ktx)
diff --git a/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/GlassesActivity.kt b/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/GlassesActivity.kt
index b6f6efcb..8920be21 100644
--- a/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/GlassesActivity.kt
+++ b/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/GlassesActivity.kt
@@ -20,9 +20,12 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.xr.glimmer.GlimmerTheme
+import androidx.xr.glimmer.googlefonts.createGoogleSansFlexTypography
import com.android.ai.samples.geminilivetodo.ui.AudioExperience
import com.android.ai.samples.geminilivetodo.ui.GlimmerTodoScreen
import com.android.ai.samples.geminilivetodo.ui.TodoScreenViewModel
+import androidx.compose.ui.ComposeUiFlags
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.xr.projected.ProjectedDeviceController
import androidx.xr.projected.ProjectedDeviceController.Capability
import androidx.xr.projected.ProjectedDisplayController
@@ -57,9 +60,10 @@ class GlassesActivity : ComponentActivity() {
setupContent()
}
- @OptIn(ExperimentalProjectedApi::class)
+ @OptIn(ExperimentalComposeUiApi::class, ExperimentalProjectedApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ ComposeUiFlags.isInitialFocusOnFocusableAvailable = true
viewModel.initializeGeminiLive(this)
@@ -97,16 +101,20 @@ class GlassesActivity : ComponentActivity() {
}
}
+ @OptIn(ExperimentalProjectedApi::class)
private fun setupContent() {
setContent {
- GlimmerTheme {
- RootScreen(
- isGranted = isPermissionsGranted,
- isDisplayCapable = isDisplayCapable,
- areVisualsOn = areVisualsOn,
- viewModel = viewModel
- )
- }
+ GlimmerTheme(
+ typography = createGoogleSansFlexTypography(),
+ content = {
+ RootScreen(
+ isGranted = isPermissionsGranted,
+ isDisplayCapable = isDisplayCapable,
+ areVisualsOn = areVisualsOn,
+ viewModel = viewModel
+ )
+ }
+ )
}
}
@@ -121,6 +129,13 @@ class GlassesActivity : ComponentActivity() {
)
)
}
+
+ override fun onStop() {
+ super.onStop()
+ // Release high-drain resources (camera, mic, sensors)
+ // to prevent battery drain
+ viewModel.stopLiveSession()
+ }
}
@@ -150,12 +165,15 @@ fun RootScreen(
@Preview(showBackground = true)
@Composable
fun PreviewRootScreen() {
- GlimmerTheme {
- RootScreen(
- isGranted = false,
- isDisplayCapable = true,
- areVisualsOn = true,
- viewModel = TodoScreenViewModel(com.android.ai.samples.geminilivetodo.data.TodoRepository())
- )
- }
-}
\ No newline at end of file
+ GlimmerTheme(
+ typography = createGoogleSansFlexTypography(),
+ content = {
+ RootScreen(
+ isGranted = false,
+ isDisplayCapable = true,
+ areVisualsOn = true,
+ viewModel = TodoScreenViewModel(com.android.ai.samples.geminilivetodo.data.TodoRepository())
+ )
+ }
+ )
+}
diff --git a/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/GlimmerToDoScreen.kt b/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/GlimmerToDoScreen.kt
index 648678da..012da320 100644
--- a/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/GlimmerToDoScreen.kt
+++ b/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/GlimmerToDoScreen.kt
@@ -45,6 +45,7 @@ import androidx.xr.glimmer.GlimmerTheme
import androidx.xr.glimmer.ListItem
import androidx.xr.glimmer.Text
import androidx.xr.glimmer.TitleChip
+import androidx.xr.glimmer.googlefonts.createGoogleSansFlexTypography
import androidx.xr.glimmer.list.VerticalList
import com.android.ai.samples.geminilivetodo.R
import com.android.ai.samples.geminilivetodo.data.Todo
@@ -74,22 +75,25 @@ fun GlimmerTodoScreen(
}
}
- GlimmerTheme {
- Box(
- contentAlignment = Alignment.Center,
- modifier = modifier
- .fillMaxSize()
- .background(GlimmerTheme.colors.background)
- ) {
+ GlimmerTheme(
+ typography = createGoogleSansFlexTypography(),
+ content = {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = modifier
+ .fillMaxSize()
+ .background(GlimmerTheme.colors.background)
+ ) {
- GlimmerScreenContent(
- uiState = uiState,
- viewModel = viewModel,
- activity = activity,
- onExit = { onExit() }
- )
+ GlimmerScreenContent(
+ uiState = uiState,
+ viewModel = viewModel,
+ activity = activity,
+ onExit = { onExit() }
+ )
+ }
}
- }
+ )
}
@Composable
@@ -248,23 +252,26 @@ private fun GlimmerTodoScreenPreview() {
Todo(id = 3, task = "Call mom", isCompleted = false)
)
- GlimmerTheme {
- Box(
- contentAlignment = Alignment.BottomCenter,
- modifier = Modifier
- .fillMaxSize()
- .background(GlimmerTheme.colors.background)
- ) {
- GlimmerScreenContent(
- uiState = TodoScreenUiState.Success(
- todoItems = mockTodoItems,
- isMicOn = true,
- liveSessionState = LiveSessionState.Running
- ),
- viewModel = TodoScreenViewModel(com.android.ai.samples.geminilivetodo.data.TodoRepository()),
- activity = null,
- onExit = {}
- )
+ GlimmerTheme(
+ typography = createGoogleSansFlexTypography(),
+ content = {
+ Box(
+ contentAlignment = Alignment.BottomCenter,
+ modifier = Modifier
+ .fillMaxSize()
+ .background(GlimmerTheme.colors.background)
+ ) {
+ GlimmerScreenContent(
+ uiState = TodoScreenUiState.Success(
+ todoItems = mockTodoItems,
+ isMicOn = true,
+ liveSessionState = LiveSessionState.Running
+ ),
+ viewModel = TodoScreenViewModel(com.android.ai.samples.geminilivetodo.data.TodoRepository()),
+ activity = null,
+ onExit = {}
+ )
+ }
}
- }
+ )
}
diff --git a/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreenViewModel.kt b/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreenViewModel.kt
index fe111b10..81a8d6dc 100644
--- a/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreenViewModel.kt
+++ b/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreenViewModel.kt
@@ -318,6 +318,18 @@ class TodoScreenViewModel @Inject constructor(private val todoRepository: TodoRe
}
}
+ fun stopLiveSession() {
+ viewModelScope.launch {
+ try {
+ session?.stopAudioConversation()
+ liveSessionState.update { LiveSessionState.Ready }
+ todoRepository.updateMicStatus(micIsOn = false)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error stopping Live Session onStop: ${e.message}", e)
+ }
+ }
+ }
+
fun requestAudioPermissionIfNeeded(activity: Activity) {
if (ContextCompat.checkSelfPermission(
activity,
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 14bffd8c..eb4d6b1d 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -17,14 +17,9 @@
pluginManagement {
repositories {
- google {
- content {
- includeGroupByRegex("com\\.android.*")
- includeGroupByRegex("com\\.google.*")
- includeGroupByRegex("androidx.*")
- }
- }
+ google()
mavenCentral()
+ maven { url = uri("https://androidx.dev/storage/compose-test/repository") }
gradlePluginPortal()
}
}
@@ -33,6 +28,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven { url = uri("https://androidx.dev/storage/compose-test/repository") }
}
}