Skip to content

Commit cc99365

Browse files
Add permission runtime
1 parent 76b9523 commit cc99365

File tree

3 files changed

+55
-12
lines changed

3 files changed

+55
-12
lines changed

ai-catalog/samples/gemini-live-todo/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ dependencies {
6262
implementation(libs.hilt.android)
6363
ksp(libs.hilt.compiler)
6464
implementation(libs.hilt.navigation.compose)
65+
implementation(libs.androidx.activity.compose)
6566
implementation(libs.androidx.material3.android)
6667
implementation(libs.kotlinx.serialization.json)
6768
testImplementation(libs.junit)

ai-catalog/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreen.kt

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package com.android.ai.samples.geminilivetodo.ui
1717

1818
import android.Manifest
19+
import android.app.Activity
20+
import androidx.activity.compose.LocalActivity
1921
import androidx.annotation.RequiresPermission
2022
import androidx.compose.animation.Animatable
2123
import androidx.compose.animation.animateColor
@@ -25,6 +27,7 @@ import androidx.compose.animation.core.infiniteRepeatable
2527
import androidx.compose.animation.core.rememberInfiniteTransition
2628
import androidx.compose.animation.core.tween
2729
import androidx.compose.foundation.background
30+
import androidx.compose.foundation.layout.Box
2831
import androidx.compose.foundation.layout.Column
2932
import androidx.compose.foundation.layout.Row
3033
import androidx.compose.foundation.layout.Spacer
@@ -43,6 +46,7 @@ import androidx.compose.material.icons.filled.MicOff
4346
import androidx.compose.material3.AlertDialog
4447
import androidx.compose.material3.Button
4548
import androidx.compose.material3.Checkbox
49+
import androidx.compose.material3.CircularProgressIndicator
4650
import androidx.compose.material3.ExperimentalMaterial3Api
4751
import androidx.compose.material3.FabPosition
4852
import androidx.compose.material3.FloatingActionButton
@@ -64,6 +68,7 @@ import androidx.compose.runtime.setValue
6468
import androidx.compose.ui.Alignment
6569
import androidx.compose.ui.Modifier
6670
import androidx.compose.ui.graphics.Color
71+
import androidx.compose.ui.platform.LocalContext
6772
import androidx.compose.ui.res.stringResource
6873
import androidx.compose.ui.text.TextStyle
6974
import androidx.compose.ui.text.style.TextDecoration
@@ -86,16 +91,18 @@ fun TodoScreen(viewModel: TodoScreenViewModel = hiltViewModel()) {
8691
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
8792
var text by remember { mutableStateOf("") }
8893

94+
val activity = LocalActivity.current as Activity
95+
8996
LaunchedEffect(Unit) {
90-
viewModel.initializeGeminiLive()
97+
viewModel.initializeGeminiLive(activity)
9198
}
9299

93100
Scaffold(
94101
topBar = {
95102
TopAppBar(
96103
colors = TopAppBarDefaults.topAppBarColors(
97104
containerColor = MaterialTheme.colorScheme.primaryContainer,
98-
titleContentColor = MaterialTheme.colorScheme.onPrimary,
105+
titleContentColor = MaterialTheme.colorScheme.primary,
99106
),
100107
title = { Text(stringResource(R.string.gemini_live_title)) },
101108
)
@@ -127,7 +134,13 @@ fun TodoScreen(viewModel: TodoScreenViewModel = hiltViewModel()) {
127134

128135
when (uiState) {
129136
is TodoScreenUiState.Initial -> {
130-
// Show a loading indicator or initial state
137+
Box(
138+
modifier = Modifier
139+
.fillMaxSize(),
140+
contentAlignment = Alignment.Center,
141+
) {
142+
CircularProgressIndicator()
143+
}
131144
}
132145
is TodoScreenUiState.Success -> {
133146
val todos = (uiState as TodoScreenUiState.Success).todos
@@ -163,7 +176,11 @@ fun TodoScreen(viewModel: TodoScreenViewModel = hiltViewModel()) {
163176
}
164177

165178
@Composable
166-
fun TodoInput(text: String, onTextChange: (String) -> Unit, onAddClick: () -> Unit) {
179+
fun TodoInput(
180+
text: String,
181+
onTextChange: (String) -> Unit,
182+
onAddClick: () -> Unit,
183+
) {
167184
Row(
168185
modifier = Modifier
169186
.fillMaxWidth()
@@ -188,7 +205,10 @@ fun TodoInput(text: String, onTextChange: (String) -> Unit, onAddClick: () -> Un
188205
}
189206

190207
@Composable
191-
fun MicButton(uiState: TodoScreenUiState, onToggle: () -> Unit) {
208+
fun MicButton(
209+
uiState: TodoScreenUiState,
210+
onToggle: () -> Unit,
211+
) {
192212
if (uiState is TodoScreenUiState.Success) {
193213
val successState = uiState as TodoScreenUiState.Success
194214
val micIcon = when {
@@ -237,7 +257,12 @@ fun MicButton(uiState: TodoScreenUiState, onToggle: () -> Unit) {
237257
}
238258

239259
@Composable
240-
fun TodoItem(modifier: Modifier, task: Todo, onToggle: () -> Unit, onDelete: () -> Unit) {
260+
fun TodoItem(
261+
modifier: Modifier,
262+
task: Todo,
263+
onToggle: () -> Unit,
264+
onDelete: () -> Unit,
265+
) {
241266
val defaultBackgroundColor = Color.Transparent
242267
val backgroundColor = remember { Animatable(defaultBackgroundColor) }
243268

ai-catalog/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreenViewModel.kt

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@
1515
*/
1616
package com.android.ai.samples.geminilivetodo.ui
1717

18+
import android.Manifest
19+
import android.app.Activity
20+
import android.content.Context
21+
import android.content.pm.PackageManager
1822
import android.util.Log
1923
import androidx.annotation.RequiresPermission
24+
import androidx.core.app.ActivityCompat
25+
import androidx.core.content.ContextCompat
2026
import androidx.lifecycle.ViewModel
2127
import androidx.lifecycle.viewModelScope
2228
import com.android.ai.samples.geminilivetodo.data.TodoRepository
@@ -90,7 +96,7 @@ class TodoScreenViewModel @Inject constructor(private val todoRepository: TodoRe
9096
if (currentState !is TodoScreenUiState.Success) return@launch
9197

9298
session?.let {
93-
if (currentState.isLiveSessionRunning) {
99+
if (!currentState.isLiveSessionRunning) {
94100
it.startAudioConversation(::handleFunctionCall)
95101
_uiState.update {
96102
if (it is TodoScreenUiState.Success) {
@@ -113,7 +119,8 @@ class TodoScreenViewModel @Inject constructor(private val todoRepository: TodoRe
113119
}
114120
}
115121

116-
fun initializeGeminiLive() {
122+
fun initializeGeminiLive(activity: Activity) {
123+
requestAudioPermissionIfNeeded(activity)
117124
viewModelScope.launch {
118125
Log.d(TAG, "Start Gemini Live initialization")
119126
val liveGenerationConfig = liveGenerationConfig {
@@ -181,10 +188,10 @@ class TodoScreenViewModel @Inject constructor(private val todoRepository: TodoRe
181188
} catch (e: Exception) {
182189
Log.e(TAG, "Error connecting to the model", e)
183190
_uiState.update {
184-
TodoScreenUiState.Error(
185-
isLiveSessionReady = false,
186-
isLiveSessionRunning = false,
187-
)
191+
TodoScreenUiState.Error(
192+
isLiveSessionReady = false,
193+
isLiveSessionRunning = false,
194+
)
188195
}
189196
}
190197

@@ -251,4 +258,14 @@ class TodoScreenViewModel @Inject constructor(private val todoRepository: TodoRe
251258
}
252259
}
253260
}
261+
262+
fun requestAudioPermissionIfNeeded(activity: Activity){
263+
if (ContextCompat.checkSelfPermission(
264+
activity,
265+
Manifest.permission.RECORD_AUDIO
266+
) != PackageManager.PERMISSION_GRANTED
267+
) {
268+
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.RECORD_AUDIO), 1)
269+
}
270+
}
254271
}

0 commit comments

Comments
 (0)