Skip to content

Commit 70ede53

Browse files
Google AI Edge Gallerycopybara-github
authored andcommitted
internal changes
PiperOrigin-RevId: 865031389
1 parent 321570e commit 70ede53

File tree

18 files changed

+540
-149
lines changed

18 files changed

+540
-149
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.ai.edge.gallery
18+
19+
import androidx.datastore.core.CorruptionException
20+
import androidx.datastore.core.Serializer
21+
import com.google.ai.edge.gallery.proto.CutoutCollection
22+
import com.google.protobuf.InvalidProtocolBufferException
23+
import java.io.InputStream
24+
import java.io.OutputStream
25+
26+
object CutoutsSerializer : Serializer<CutoutCollection> {
27+
override val defaultValue: CutoutCollection = CutoutCollection.getDefaultInstance()
28+
29+
override suspend fun readFrom(input: InputStream): CutoutCollection {
30+
try {
31+
return CutoutCollection.parseFrom(input)
32+
} catch (exception: InvalidProtocolBufferException) {
33+
throw CorruptionException("Cannot read proto.", exception)
34+
}
35+
}
36+
37+
override suspend fun writeTo(t: CutoutCollection, output: OutputStream) = t.writeTo(output)
38+
}

Android/src/app/src/main/java/com/google/ai/edge/gallery/common/Utils.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ import android.util.Log
2525
import androidx.exifinterface.media.ExifInterface
2626
import com.google.ai.edge.gallery.data.SAMPLE_RATE
2727
import com.google.gson.Gson
28+
import java.io.File
2829
import java.io.FileInputStream
2930
import java.net.HttpURLConnection
3031
import java.net.URL
3132
import java.nio.ByteBuffer
3233
import java.nio.ByteOrder
34+
import java.nio.channels.FileChannel
3335
import kotlin.math.abs
3436
import kotlin.math.floor
3537
import kotlin.math.max
@@ -298,3 +300,18 @@ private fun calculateInSampleSize(
298300

299301
return inSampleSize
300302
}
303+
304+
fun readFileToByteBuffer(file: File): ByteBuffer? {
305+
return try {
306+
val fileInputStream = FileInputStream(file)
307+
val fileChannel: FileChannel = fileInputStream.channel
308+
val byteBuffer = ByteBuffer.allocateDirect(fileChannel.size().toInt())
309+
fileChannel.read(byteBuffer)
310+
byteBuffer.rewind()
311+
fileInputStream.close()
312+
byteBuffer
313+
} catch (e: Exception) {
314+
e.printStackTrace()
315+
null
316+
}
317+
}

Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/common/CustomTaskData.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ data class CustomTaskData(
3838
val bottomPadding: Dp = 0.dp,
3939
val setAppBarControlsDisabled: (Boolean) -> Unit = {},
4040
val setTopBarVisible: (Boolean) -> Unit = {},
41+
val setCustomNavigateUpCallback: ((() -> Unit)?) -> Unit = {},
4142
)
4243

4344
data class CustomTaskDataForBuiltinTask(

Android/src/app/src/main/java/com/google/ai/edge/gallery/data/DataStoreRepository.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package com.google.ai.edge.gallery.data
1818

1919
import androidx.datastore.core.DataStore
2020
import com.google.ai.edge.gallery.proto.AccessTokenData
21+
import com.google.ai.edge.gallery.proto.Cutout
22+
import com.google.ai.edge.gallery.proto.CutoutCollection
2123
import com.google.ai.edge.gallery.proto.ImportedModel
2224
import com.google.ai.edge.gallery.proto.Settings
2325
import com.google.ai.edge.gallery.proto.Theme
@@ -52,12 +54,21 @@ interface DataStoreRepository {
5254
fun getHasRunTinyGarden(): Boolean
5355

5456
fun setHasRunTinyGarden(hasRun: Boolean)
57+
58+
fun addCutout(cutout: Cutout)
59+
60+
fun getAllCutouts(): List<Cutout>
61+
62+
fun setCutout(newCutout: Cutout)
63+
64+
fun setCutouts(cutouts: List<Cutout>)
5565
}
5666

5767
/** Repository for managing data using Proto DataStore. */
5868
class DefaultDataStoreRepository(
5969
private val dataStore: DataStore<Settings>,
6070
private val userDataDataStore: DataStore<UserData>,
71+
private val cutoutDataStore: DataStore<CutoutCollection>,
6172
) : DataStoreRepository {
6273
override fun saveTextInputHistory(history: List<String>) {
6374
runBlocking {
@@ -167,4 +178,40 @@ class DefaultDataStoreRepository(
167178
dataStore.updateData { settings -> settings.toBuilder().setHasRunTinyGarden(hasRun).build() }
168179
}
169180
}
181+
182+
override fun addCutout(cutout: Cutout) {
183+
runBlocking {
184+
cutoutDataStore.updateData { cutouts -> cutouts.toBuilder().addCutout(cutout).build() }
185+
}
186+
}
187+
188+
override fun getAllCutouts(): List<Cutout> {
189+
return runBlocking { cutoutDataStore.data.first().cutoutList }
190+
}
191+
192+
override fun setCutout(newCutout: Cutout) {
193+
runBlocking {
194+
cutoutDataStore.updateData { cutouts ->
195+
var index = -1
196+
for (i in 0..<cutouts.cutoutCount) {
197+
val cutout = cutouts.cutoutList.get(i)
198+
if (cutout.id == newCutout.id) {
199+
index = i
200+
break
201+
}
202+
}
203+
if (index >= 0) {
204+
cutouts.toBuilder().setCutout(index, newCutout).build()
205+
} else {
206+
cutouts
207+
}
208+
}
209+
}
210+
}
211+
212+
override fun setCutouts(cutouts: List<Cutout>) {
213+
runBlocking {
214+
cutoutDataStore.updateData { CutoutCollection.newBuilder().addAllCutout(cutouts).build() }
215+
}
216+
}
170217
}

Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Tasks.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ data class Task(
100100
/** Whether the task is experimental. */
101101
val experimental: Boolean = false,
102102

103+
/** Whether to use theme color instead of the task tint color. */
104+
val useThemeColor: Boolean = false,
105+
103106
// The following fields are only used for built-in tasks. Can ignore if you are creating your own
104107
// custom tasks.
105108
//
@@ -124,6 +127,7 @@ object BuiltInTaskId {
124127
const val LLM_ASK_AUDIO = "llm_ask_audio"
125128
const val LLM_MOBILE_ACTIONS = "llm_mobile_actions"
126129
const val LLM_TINY_GARDEN = "llm_tiny_garden"
130+
const val MP_SCRAPBOOK = "mp_scrapbook"
127131
}
128132

129133
private val allLegacyTaskIds: Set<String> =

Android/src/app/src/main/java/com/google/ai/edge/gallery/di/AppModule.kt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ import androidx.datastore.core.DataStoreFactory
2222
import androidx.datastore.core.Serializer
2323
import androidx.datastore.dataStoreFile
2424
import com.google.ai.edge.gallery.AppLifecycleProvider
25+
import com.google.ai.edge.gallery.CutoutsSerializer
2526
import com.google.ai.edge.gallery.GalleryLifecycleProvider
2627
import com.google.ai.edge.gallery.SettingsSerializer
2728
import com.google.ai.edge.gallery.UserDataSerializer
2829
import com.google.ai.edge.gallery.data.DataStoreRepository
2930
import com.google.ai.edge.gallery.data.DefaultDataStoreRepository
3031
import com.google.ai.edge.gallery.data.DefaultDownloadRepository
3132
import com.google.ai.edge.gallery.data.DownloadRepository
33+
import com.google.ai.edge.gallery.proto.CutoutCollection
3234
import com.google.ai.edge.gallery.proto.Settings
3335
import com.google.ai.edge.gallery.proto.UserData
3436
import dagger.Module
@@ -49,6 +51,13 @@ internal object AppModule {
4951
return SettingsSerializer
5052
}
5153

54+
// Provides the CutoutSerializer
55+
@Provides
56+
@Singleton
57+
fun provideCutoutSerializer(): Serializer<CutoutCollection> {
58+
return CutoutsSerializer
59+
}
60+
5261
// Provides the UserDataSerializer
5362
@Provides
5463
@Singleton
@@ -69,6 +78,19 @@ internal object AppModule {
6978
)
7079
}
7180

81+
// Provides DataStore<CutoutCollection>
82+
@Provides
83+
@Singleton
84+
fun provideCutoutsDataStore(
85+
@ApplicationContext context: Context,
86+
cutoutsSerializer: Serializer<CutoutCollection>,
87+
): DataStore<CutoutCollection> {
88+
return DataStoreFactory.create(
89+
serializer = cutoutsSerializer,
90+
produceFile = { context.dataStoreFile("cutouts.pb") },
91+
)
92+
}
93+
7294
// Provides DataStore<UserData>
7395
@Provides
7496
@Singleton
@@ -95,8 +117,9 @@ internal object AppModule {
95117
fun provideDataStoreRepository(
96118
dataStore: DataStore<Settings>,
97119
userDataDataStore: DataStore<UserData>,
120+
cutoutsDataStore: DataStore<CutoutCollection>,
98121
): DataStoreRepository {
99-
return DefaultDataStoreRepository(dataStore, userDataDataStore)
122+
return DefaultDataStoreRepository(dataStore, userDataDataStore, cutoutsDataStore)
100123
}
101124

102125
// Provides DownloadRepository

Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ColorUtils.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,25 @@ import com.google.ai.edge.gallery.ui.theme.customColors
2424

2525
@Composable
2626
fun getTaskBgColor(task: Task): Color {
27-
val colorIndex: Int = task.index % MaterialTheme.customColors.taskBgColors.size
27+
val colorIndex: Int = (task.index.coerceAtLeast(0)) % MaterialTheme.customColors.taskBgColors.size
2828
return MaterialTheme.customColors.taskBgColors[colorIndex]
2929
}
3030

3131
@Composable
3232
fun getTaskBgGradientColors(task: Task): List<Color> {
33-
val colorIndex: Int = task.index % MaterialTheme.customColors.taskBgColors.size
33+
val colorIndex: Int = (task.index.coerceAtLeast(0)) % MaterialTheme.customColors.taskBgColors.size
3434
return MaterialTheme.customColors.taskBgGradientColors[colorIndex]
3535
}
3636

3737
@Composable
3838
fun getTaskIconColor(task: Task): Color {
39-
val colorIndex: Int = task.index % MaterialTheme.customColors.taskIconColors.size
39+
val colorIndex: Int =
40+
(task.index.coerceAtLeast(0)) % MaterialTheme.customColors.taskIconColors.size
4041
return MaterialTheme.customColors.taskIconColors[colorIndex]
4142
}
4243

4344
@Composable
4445
fun getTaskIconColor(index: Int): Color {
45-
val colorIndex: Int = index % MaterialTheme.customColors.taskIconColors.size
46+
val colorIndex: Int = (index.coerceAtLeast(0)) % MaterialTheme.customColors.taskIconColors.size
4647
return MaterialTheme.customColors.taskIconColors[colorIndex]
4748
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.ai.edge.gallery.ui.common
18+
19+
import androidx.annotation.StringRes
20+
import androidx.compose.foundation.layout.Arrangement
21+
import androidx.compose.foundation.layout.Box
22+
import androidx.compose.foundation.layout.Column
23+
import androidx.compose.foundation.layout.padding
24+
import androidx.compose.foundation.layout.size
25+
import androidx.compose.material3.Button
26+
import androidx.compose.material3.Icon
27+
import androidx.compose.material3.MaterialTheme
28+
import androidx.compose.material3.Text
29+
import androidx.compose.runtime.Composable
30+
import androidx.compose.ui.Alignment
31+
import androidx.compose.ui.Modifier
32+
import androidx.compose.ui.graphics.vector.ImageVector
33+
import androidx.compose.ui.res.stringResource
34+
import androidx.compose.ui.text.style.TextAlign
35+
import androidx.compose.ui.unit.dp
36+
37+
data class EmptyStateButtonConfig(
38+
@StringRes val buttonLabelResId: Int,
39+
val buttonIcon: ImageVector? = null,
40+
val onButtonClick: () -> Unit = {},
41+
val extraContent: @Composable () -> Unit = {},
42+
)
43+
44+
/**
45+
* A composable function to display an empty state with an icon, title, description, and an optional
46+
* button.
47+
*/
48+
@Composable
49+
fun EmptyState(
50+
icon: ImageVector,
51+
@StringRes titleResId: Int,
52+
@StringRes descriptionResId: Int,
53+
buttonConfig: EmptyStateButtonConfig? = null,
54+
) {
55+
Column(
56+
horizontalAlignment = Alignment.CenterHorizontally,
57+
verticalArrangement = Arrangement.spacedBy(16.dp),
58+
modifier = Modifier.padding(horizontal = 48.dp),
59+
) {
60+
Icon(
61+
icon,
62+
contentDescription = null,
63+
modifier = Modifier.size(56.dp),
64+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
65+
)
66+
Text(
67+
stringResource(titleResId),
68+
style = MaterialTheme.typography.headlineMedium,
69+
color = MaterialTheme.colorScheme.onSurface,
70+
textAlign = TextAlign.Center,
71+
)
72+
Text(
73+
stringResource(descriptionResId),
74+
style = MaterialTheme.typography.bodyLarge,
75+
color = MaterialTheme.colorScheme.onSurfaceVariant,
76+
textAlign = TextAlign.Center,
77+
)
78+
if (buttonConfig != null) {
79+
Box {
80+
Button(
81+
contentPadding = SMALL_BUTTON_CONTENT_PADDING,
82+
onClick = buttonConfig.onButtonClick,
83+
) {
84+
if (buttonConfig.buttonIcon != null) {
85+
Icon(
86+
buttonConfig.buttonIcon,
87+
contentDescription = null,
88+
modifier = Modifier.padding(end = 8.dp).size(20.dp),
89+
)
90+
}
91+
Text(stringResource(buttonConfig.buttonLabelResId))
92+
}
93+
buttonConfig.extraContent()
94+
}
95+
}
96+
}
97+
}

Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ModelPageAppBar.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ fun ModelPageAppBar(
7373
onResetSessionClicked: (Model) -> Unit = {},
7474
canShowResetSessionButton: Boolean = false,
7575
hideModelSelector: Boolean = false,
76+
useThemeColor: Boolean = false,
7677
onConfigChanged: (oldConfigValues: Map<String, Any>, newConfigValues: Map<String, Any>) -> Unit =
7778
{ _, _ ->
7879
},
@@ -96,19 +97,18 @@ fun ModelPageAppBar(
9697
// Task type.
9798
Row(
9899
verticalAlignment = Alignment.CenterVertically,
99-
horizontalArrangement = Arrangement.spacedBy(6.dp),
100+
horizontalArrangement = Arrangement.spacedBy(10.dp),
100101
) {
102+
val tintColor =
103+
if (useThemeColor) MaterialTheme.colorScheme.onSurface
104+
else getTaskIconColor(task = task)
101105
Icon(
102106
task.icon ?: ImageVector.vectorResource(task.iconVectorResourceId!!),
103-
tint = getTaskIconColor(task = task),
104-
modifier = Modifier.size(16.dp),
107+
tint = tintColor,
108+
modifier = Modifier.size(24.dp),
105109
contentDescription = null,
106110
)
107-
Text(
108-
task.label,
109-
style = MaterialTheme.typography.titleMedium,
110-
color = getTaskIconColor(task = task),
111-
)
111+
Text(task.label, style = MaterialTheme.typography.titleMedium, color = tintColor)
112112
}
113113

114114
// Model chips pager.

0 commit comments

Comments
 (0)