Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Android/src/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ android {
applicationId = "com.google.aiedge.gallery"
minSdk = 31
targetSdk = 35
versionCode = 29
versionName = "1.0.12"
versionCode = 30
versionName = "1.0.13"

// Needed for HuggingFace auth workflows.
// Use the scheme of the "Redirect URLs" in HuggingFace app.
Expand Down
2 changes: 1 addition & 1 deletion Android/src/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="gallery_high_priority_push_channel" />

<uses-native-library android:name="libvndksupport.so" android:required="false"/>
<uses-native-library android:name="libOpenCL.so" android:required="false"/>
<uses-native-library android:name="libcdsprpc.so" android:required="false" />
<uses-native-library android:name="libedgetpu_litert.so" android:required="false" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.ime
Expand All @@ -36,7 +37,9 @@ import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.exifinterface.media.ExifInterface
import com.google.ai.edge.gallery.GalleryEvent
import com.google.ai.edge.gallery.data.SAMPLE_RATE
import com.google.ai.edge.gallery.firebaseAnalytics
import com.google.gson.Gson
import java.io.File
import java.io.FileInputStream
Expand Down Expand Up @@ -378,3 +381,14 @@ fun isAICoreSupported(allowedDeviceModels: Set<String>?): Boolean {
val currentModel = Build.MODEL?.lowercase() ?: return false
return allowedDeviceModels.contains(currentModel)
}

fun logErrorToFirebase(event: GalleryEvent, errorType: String, errorMessage: String?) {
firebaseAnalytics?.logEvent(
event.id,
Bundle().apply {
putBoolean("success", false)
putString("error_type", errorType)
putString("error_message", errorMessage ?: "Unknown error")
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ fun AgentChatScreen(
showAudioPicker = true,
getActiveSkills = {
skillManagerViewModel.getSelectedSkills().map { skill ->
if (skill.builtIn) skill.name else "custom_skill"
skillManagerViewModel.getSkillShortId(skill)
}
},
composableBelowMessageList = { model ->
Expand Down Expand Up @@ -236,6 +236,8 @@ fun AgentChatScreen(
} else {
action.url
}
val skill = skillManagerViewModel.getSkill(name = skillName)
val skillId = skill?.let { skillManagerViewModel.getSkillShortId(it) } ?: "xxxx"
try {
// Set up a safety net timeout so we NEVER hang the chat or tool execution
launch {
Expand All @@ -250,6 +252,7 @@ fun AgentChatScreen(
GalleryEvent.SKILL_EXECUTION.id,
Bundle().apply {
putString("skill_name", skillName)
putString("skill_id", skillId)
putBoolean("success", false)
putString("error_type", "timeout")
},
Expand Down Expand Up @@ -285,6 +288,7 @@ fun AgentChatScreen(
GalleryEvent.SKILL_EXECUTION.id,
Bundle().apply {
putString("skill_name", skillName)
putString("skill_id", skillId)
putBoolean("success", isSuccess)
putString("error_type", errorType)
},
Expand Down Expand Up @@ -323,6 +327,7 @@ fun AgentChatScreen(
GalleryEvent.SKILL_EXECUTION.id,
Bundle().apply {
putString("skill_name", skillName)
putString("skill_id", skillId)
putBoolean("success", false)
putString("error_type", "exception")
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class AgentChatTask @Inject constructor() : CustomTask {
LlmChatModelHelper.initialize(
context = context,
model = model,
taskId = task.id,
supportImage = true,
supportAudio = true,
onDone = onDone,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import kotlinx.coroutines.runBlocking

private const val TAG = "AGAgentTools"

class AgentTools() : ToolSet {
open class AgentTools() : ToolSet {
lateinit var context: Context
lateinit var skillManagerViewModel: SkillManagerViewModel

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -173,7 +175,7 @@ fun SkillManagerBottomSheet(
var addSkillOptionTypeToConfirm by remember { mutableStateOf<AddSkillOptionType?>(null) }
var skillToEditIndex by remember { mutableIntStateOf(-1) }
var searchQuery by remember { mutableStateOf("") }
var savedSelectedSkillsNamesAndDescriptions = remember { "" }
var savedSelectedSkillsNamesAndDescriptions by remember { mutableStateOf("") }
var filteredSkills by remember { mutableStateOf(uiState.skills) }
val listState = rememberLazyListState()
val uriHandler = LocalUriHandler.current
Expand Down Expand Up @@ -860,7 +862,8 @@ private fun SkillItemRow(
Switch(
checked = skill.selected,
onCheckedChange = onSkillEnabledChange,
modifier = Modifier.offset(y = (-4).dp),
modifier =
Modifier.offset(y = (-4).dp).semantics { contentDescription = "Toggle ${skill.name}" },
enabled = !inMultiSelectMode,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,23 @@ val TRYOUT_CHIPS: List<SkillTryOutChip> =
),
)

enum class SkillSource(val sourceName: String) {
BUILTIN("builtin"),
FEATURED("featured"),
REMOTE_URL("remote_url"),
LOCAL_IMPORT("local_import"),
UNKNOWN("unknown"),
}

enum class SkillAction(val value: String) {
ADD("add"),
DELETE("delete"),
ENABLE("enable"),
DISABLE("disable"),
ENABLE_ALL("enable_all"),
DISABLE_ALL("disable_all"),
}

data class SkillState(val skill: Skill)

data class SkillManagerUiState(
Expand Down Expand Up @@ -341,13 +358,7 @@ constructor(
Log.d(TAG, "Successfully added skill from URL: ${skill.name}")
firebaseAnalytics?.logEvent(
GalleryEvent.SKILL_MANAGEMENT.id,
Bundle().apply {
putString("action", "add")
putString("source", "remote_url")
putString("skill_name", getSkillNameForLogging(skill))
putBoolean("is_built_in", skill.builtIn)
putString("remote_url", url)
},
getSkillLoggingParams(skill).apply { putString("action", SkillAction.ADD.value) },
)
onSuccess()
}
Expand Down Expand Up @@ -532,12 +543,7 @@ constructor(
Log.d(TAG, "Successfully added skill from local import: ${skillWithDir.name}")
firebaseAnalytics?.logEvent(
GalleryEvent.SKILL_MANAGEMENT.id,
Bundle().apply {
putString("action", "add")
putString("source", "local_import")
putString("skill_name", getSkillNameForLogging(skillWithDir))
putBoolean("is_built_in", skillWithDir.builtIn)
},
getSkillLoggingParams(skillWithDir).apply { putString("action", SkillAction.ADD.value) },
)
onSuccess()
}
Expand Down Expand Up @@ -603,15 +609,14 @@ constructor(
return
}

val skillNameToLog = getSkillNameForLogging(skill)
Log.d(TAG, "Analytics: skill_management, action=delete, skill_name=${skillNameToLog}")
val loggingParams = getSkillLoggingParams(skill)
Log.d(
TAG,
"Analytics: skill_management, action=${SkillAction.DELETE.value}, params=$loggingParams",
)
firebaseAnalytics?.logEvent(
GalleryEvent.SKILL_MANAGEMENT.id,
Bundle().apply {
putString("action", "delete")
putString("skill_name", skillNameToLog)
putBoolean("is_built_in", skill.builtIn)
},
loggingParams.apply { putString("action", SkillAction.DELETE.value) },
)

// Update state.
Expand Down Expand Up @@ -643,17 +648,14 @@ constructor(
}

for (skill in skillsToDelete) {
val loggingParams = getSkillLoggingParams(skill)
Log.d(
TAG,
"Analytics: skill_management, action=delete, skill_name=${getSkillNameForLogging(skill)}",
"Analytics: skill_management, action=${SkillAction.DELETE.value}, params=$loggingParams",
)
firebaseAnalytics?.logEvent(
GalleryEvent.SKILL_MANAGEMENT.id,
Bundle().apply {
putString("action", "delete")
putString("skill_name", getSkillNameForLogging(skill))
putBoolean("is_built_in", skill.builtIn)
},
loggingParams.apply { putString("action", SkillAction.DELETE.value) },
)
}

Expand Down Expand Up @@ -686,10 +688,8 @@ constructor(

firebaseAnalytics?.logEvent(
GalleryEvent.SKILL_MANAGEMENT.id,
Bundle().apply {
putString("action", if (selected) "enable" else "disable")
putString("skill_name", getSkillNameForLogging(skill.skill))
putBoolean("is_built_in", skill.skill.builtIn)
getSkillLoggingParams(skill.skill).apply {
putString("action", if (selected) SkillAction.ENABLE.value else SkillAction.DISABLE.value)
},
)
val updatedSkills =
Expand Down Expand Up @@ -720,11 +720,16 @@ constructor(

Log.d(
TAG,
"Analytics: skill_management, action=${if (selected) "enable_all" else "disable_all"}",
"Analytics: skill_management, action=${if (selected) SkillAction.ENABLE_ALL.value else SkillAction.DISABLE_ALL.value}",
)
firebaseAnalytics?.logEvent(
GalleryEvent.SKILL_MANAGEMENT.id,
Bundle().apply { putString("action", if (selected) "enable_all" else "disable_all") },
Bundle().apply {
putString(
"action",
if (selected) SkillAction.ENABLE_ALL.value else SkillAction.DISABLE_ALL.value,
)
},
)

// Update data store.
Expand Down Expand Up @@ -1166,15 +1171,73 @@ constructor(
dataStoreRepository.setSkills(updatedList)
}

private fun getSkillNameForLogging(skill: Skill): String {
private fun getSkillSource(skill: Skill): SkillSource {
val isFeatured =
skill.skillUrl.isNotEmpty() &&
_uiState.value.featuredSkills.any { it.skillUrl == skill.skillUrl }
return if (skill.builtIn || isFeatured) {
skill.name
} else {
"custom_skill"
return when {
skill.builtIn -> SkillSource.BUILTIN
isFeatured -> SkillSource.FEATURED
skill.skillUrl.isNotEmpty() -> SkillSource.REMOTE_URL
skill.importDirName.isNotEmpty() -> SkillSource.LOCAL_IMPORT
else -> SkillSource.UNKNOWN
}
}

/**
* Generates a short 4-character hash to act as a stable ID. This solves the 100-character limit
* for list logging in GA4 AND allows us to distinguish between different custom skills in
* reports. Note: When we migrate to Cleancut or a similar service that doesn't have severe
* character limits, we can drop the human-readable skill_name from setup events and rely purely
* on this hash ID.
*/
fun getSkillShortId(skill: Skill): String {
val source = getSkillSource(skill)
val identifier =
when (source) {
SkillSource.BUILTIN,
SkillSource.FEATURED -> skill.name
SkillSource.LOCAL_IMPORT -> skill.importDirName
else -> skill.skillUrl
}
if (identifier.isEmpty()) return "xxxx"

val prefix =
when (source) {
SkillSource.BUILTIN -> "b_"
SkillSource.FEATURED -> "f_"
SkillSource.LOCAL_IMPORT -> "l_"
else -> "c_"
}

return try {
val digest = java.security.MessageDigest.getInstance("SHA-256")
val hashBytes = digest.digest(identifier.toByteArray())
val hexString = hashBytes.joinToString("") { "%02x".format(it) }
prefix + hexString.take(4)
} catch (e: Exception) {
prefix + "fail"
}
}

private fun getSkillLoggingParams(skill: Skill): Bundle {
val source = getSkillSource(skill)
val skillName =
if (source == SkillSource.BUILTIN || source == SkillSource.FEATURED) skill.name
else "custom_skill"
val bundle =
Bundle().apply {
putString("source", source.sourceName)
putString("skill_name", skillName)
putString("skill_id", getSkillShortId(skill))
}
if (
skill.skillUrl.isNotEmpty() &&
(source == SkillSource.REMOTE_URL || source == SkillSource.FEATURED)
) {
bundle.putString("remote_url", skill.skillUrl.take(100))
}
return bundle
}

private fun getSkillDestinationDir(originalImportDirName: String): File {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class MobileActionsTask @Inject constructor() : CustomTask {
LlmChatModelHelper.initialize(
context = context,
model = model,
taskId = task.id,
supportImage = false,
supportAudio = false,
onDone = onDone,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.ai.edge.gallery.R
import com.google.ai.edge.gallery.data.BuiltInTaskId
import com.google.ai.edge.gallery.data.Model
import com.google.ai.edge.gallery.ui.llmchat.LlmChatModelHelper
import com.google.ai.edge.gallery.ui.llmchat.LlmModelInstance
Expand Down Expand Up @@ -211,6 +212,7 @@ constructor(@ApplicationContext private val appContext: Context) : ViewModel() {
LlmChatModelHelper.initialize(
context = context,
model = model,
taskId = BuiltInTaskId.LLM_MOBILE_ACTIONS,
supportImage = false,
supportAudio = false,
onDone = { error ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class TinyGardenTask @Inject constructor() : CustomTask {
LlmChatModelHelper.initialize(
context = context,
model = model,
taskId = task.id,
supportImage = false,
supportAudio = false,
onDone = onDone,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.ai.edge.gallery.R
import com.google.ai.edge.gallery.data.BuiltInTaskId
import com.google.ai.edge.gallery.data.DataStoreRepository
import com.google.ai.edge.gallery.data.Model
import com.google.ai.edge.gallery.ui.common.chat.ChatMessage
Expand Down Expand Up @@ -168,6 +169,7 @@ constructor(
LlmChatModelHelper.initialize(
context = context,
model = model,
taskId = BuiltInTaskId.LLM_TINY_GARDEN,
supportImage = false,
supportAudio = false,
onDone = { error ->
Expand Down
Loading
Loading