From 0876e5c735da447211d40dbcbe8fa50dd1e6c1f3 Mon Sep 17 00:00:00 2001 From: Kathleen Bryan Date: Fri, 15 May 2026 19:30:18 -0400 Subject: [PATCH] Gemini live launching and font updates --- app/build.gradle.kts | 23 ++-- app/src/main/AndroidManifest.xml | 5 + .../ai/catalog/domain/SampleCatalog.kt | 110 ------------------ build.gradle.kts | 7 ++ gradle.properties | 3 +- gradle/libs.versions.toml | 10 +- samples/gemini-live-todo/build.gradle.kts | 3 +- .../samples/geminilivetodo/GlassesActivity.kt | 54 ++++++--- .../geminilivetodo/ui/GlimmerToDoScreen.kt | 71 ++++++----- .../geminilivetodo/ui/TodoScreenViewModel.kt | 12 ++ settings.gradle.kts | 10 +- 11 files changed, 120 insertions(+), 188 deletions(-) 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") } } }