From 3559cf6e438fa9a54bddde2d978335adb300323f Mon Sep 17 00:00:00 2001 From: Wai Hon Law Date: Wed, 9 Jul 2025 10:51:28 -0700 Subject: [PATCH] [gallery] add Analytics events: app_open, capability_select, generate_action, resource_link_click PiperOrigin-RevId: 781122232 --- .../com/google/ai/edge/gallery/Analytics.kt | 36 +++++++++++++++++++ .../google/ai/edge/gallery/MainActivity.kt | 29 +++++++-------- .../edge/gallery/ui/common/ClickableLink.kt | 8 ++++- .../ai/edge/gallery/ui/home/HomeScreen.kt | 12 ++++++- .../edge/gallery/ui/llmchat/LlmChatScreen.kt | 7 ++++ .../ui/llmsingleturn/LlmSingleTurnScreen.kt | 10 ++++++ .../gallery/ui/navigation/GalleryNavGraph.kt | 7 ++++ 7 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 Android/src/app/src/main/java/com/google/ai/edge/gallery/Analytics.kt diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/Analytics.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/Analytics.kt new file mode 100644 index 0000000..9d496b5 --- /dev/null +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/Analytics.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.ai.edge.gallery + +import android.util.Log +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.ktx.Firebase + +private var hasLoggedAnalyticsWarning = false + +val firebaseAnalytics: FirebaseAnalytics? + get() = + runCatching { Firebase.analytics } + .onFailure { exception -> + // Firebase.analytics can throw an exception if goolgle-services is not set up, e.g., + // missing google-services.json. + if (!hasLoggedAnalyticsWarning) { + Log.w("AGAnalyticsFirebase", "Firebase Analytics is not available", exception) + } + } + .getOrNull() diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/MainActivity.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/MainActivity.kt index b923c4f..6fe29d5 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/MainActivity.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/MainActivity.kt @@ -18,7 +18,6 @@ package com.google.ai.edge.gallery import android.os.Build import android.os.Bundle -import android.util.Log import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -26,31 +25,20 @@ import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Surface import androidx.compose.ui.Modifier +import androidx.core.os.bundleOf import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import com.google.ai.edge.gallery.ui.theme.GalleryTheme import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.analytics.ktx.analytics -import com.google.firebase.ktx.Firebase import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { - private var firebaseAnalytics: FirebaseAnalytics? = null - override fun onCreate(savedInstanceState: Bundle?) { - firebaseAnalytics = - runCatching { Firebase.analytics } - .onFailure { exception -> - // Firebase.analytics can throw an exception if goolgle-services is not set up, e.g., - // missing google-services.json. - Log.w(TAG, "Firebase Analytics is not available", exception) - } - .getOrNull() + super.onCreate(savedInstanceState) installSplashScreen() - super.onCreate(savedInstanceState) enableEdgeToEdge() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Fix for three-button nav not properly going edge-to-edge. @@ -62,6 +50,19 @@ class MainActivity : ComponentActivity() { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } + override fun onResume() { + super.onResume() + + firebaseAnalytics?.logEvent( + FirebaseAnalytics.Event.APP_OPEN, + bundleOf( + "app_version" to BuildConfig.VERSION_NAME, + "os_version" to Build.VERSION.SDK_INT.toString(), + "device_model" to Build.MODEL, + ), + ) + } + companion object { private const val TAG = "AGMainActivity" } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ClickableLink.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ClickableLink.kt index 7d4abdb..cb8e2a3 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ClickableLink.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ClickableLink.kt @@ -34,6 +34,8 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp +import androidx.core.os.bundleOf +import com.google.ai.edge.gallery.firebaseAnalytics import com.google.ai.edge.gallery.ui.theme.customColors @Composable @@ -62,7 +64,11 @@ fun ClickableLink(url: String, linkText: String, icon: ImageVector) { text = annotatedText, textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(start = 6.dp).clickable { uriHandler.openUri(url) }, + modifier = + Modifier.padding(start = 6.dp).clickable { + uriHandler.openUri(url) + firebaseAnalytics?.logEvent("resource_link_click", bundleOf("link_destination" to url)) + }, ) } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/HomeScreen.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/HomeScreen.kt index 58427ab..76cfc09 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/HomeScreen.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/HomeScreen.kt @@ -88,6 +88,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.LinkAnnotation @@ -100,11 +101,13 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withLink import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.os.bundleOf import com.google.ai.edge.gallery.GalleryTopAppBar import com.google.ai.edge.gallery.R import com.google.ai.edge.gallery.data.AppBarAction import com.google.ai.edge.gallery.data.AppBarActionType import com.google.ai.edge.gallery.data.Task +import com.google.ai.edge.gallery.firebaseAnalytics import com.google.ai.edge.gallery.proto.ImportedModel import com.google.ai.edge.gallery.ui.common.TaskIcon import com.google.ai.edge.gallery.ui.common.getTaskBgColor @@ -334,17 +337,24 @@ private fun TaskList( val screenHeightDp = remember { with(density) { windowInfo.containerSize.height.toDp() } } val sizeFraction = remember { ((screenWidthDp - 360.dp) / (410.dp - 360.dp)).coerceIn(0f, 1f) } val linkColor = MaterialTheme.customColors.linkColor + val url = "https://huggingface.co/litert-community" + val uriHandler = LocalUriHandler.current val introText = buildAnnotatedString { append("Welcome to Google AI Edge Gallery! Explore a world of amazing on-device models from ") + // TODO: Consolidate the link clicking logic into ui/common/ClickableLink.kt. withLink( link = LinkAnnotation.Url( - url = "https://huggingface.co/litert-community", // Replace with the actual URL + url = url, styles = TextLinkStyles( style = SpanStyle(color = linkColor, textDecoration = TextDecoration.Underline) ), + linkInteractionListener = { _ -> + firebaseAnalytics?.logEvent("resource_link_click", bundleOf("link_destination" to url)) + uriHandler.openUri(url) + }, ) ) { append("LiteRT community") diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatScreen.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatScreen.kt index e61e2eb..9340573 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatScreen.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatScreen.kt @@ -20,6 +20,8 @@ import android.graphics.Bitmap import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.core.os.bundleOf +import com.google.ai.edge.gallery.firebaseAnalytics import com.google.ai.edge.gallery.ui.common.chat.ChatMessageAudioClip import com.google.ai.edge.gallery.ui.common.chat.ChatMessageImage import com.google.ai.edge.gallery.ui.common.chat.ChatMessageText @@ -132,6 +134,11 @@ fun ChatViewWrapper( ) }, ) + + firebaseAnalytics?.logEvent( + "generate_action", + bundleOf("capability_name" to viewModel.task.type.toString(), "model_id" to model.name), + ) } }, onRunAgainClicked = { model, message -> diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/LlmSingleTurnScreen.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/LlmSingleTurnScreen.kt index f62e2f8..5c04988 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/LlmSingleTurnScreen.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/LlmSingleTurnScreen.kt @@ -43,8 +43,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.core.os.bundleOf import com.google.ai.edge.gallery.data.ModelDownloadStatusType import com.google.ai.edge.gallery.data.TASK_LLM_PROMPT_LAB +import com.google.ai.edge.gallery.firebaseAnalytics import com.google.ai.edge.gallery.ui.common.ErrorDialog import com.google.ai.edge.gallery.ui.common.ModelPageAppBar import com.google.ai.edge.gallery.ui.common.chat.ModelDownloadStatusInfoPanel @@ -167,6 +169,14 @@ fun LlmSingleTurnScreen( modelManagerViewModel = modelManagerViewModel, onSend = { fullPrompt -> viewModel.generateResponse(model = selectedModel, input = fullPrompt) + + firebaseAnalytics?.logEvent( + "generate_action", + bundleOf( + "capability_name" to task.type.toString(), + "model_id" to selectedModel.name, + ), + ) }, onStopButtonClicked = { model -> viewModel.stopResponse(model = model) }, modifier = Modifier.fillMaxSize(), diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/navigation/GalleryNavGraph.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/navigation/GalleryNavGraph.kt index e4ebd48..a9b1bc4 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/navigation/GalleryNavGraph.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/navigation/GalleryNavGraph.kt @@ -36,6 +36,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.zIndex +import androidx.core.os.bundleOf import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver @@ -54,6 +55,7 @@ import com.google.ai.edge.gallery.data.TASK_LLM_PROMPT_LAB import com.google.ai.edge.gallery.data.Task import com.google.ai.edge.gallery.data.TaskType import com.google.ai.edge.gallery.data.getModelByName +import com.google.ai.edge.gallery.firebaseAnalytics import com.google.ai.edge.gallery.ui.home.HomeScreen import com.google.ai.edge.gallery.ui.llmchat.LlmAskAudioDestination import com.google.ai.edge.gallery.ui.llmchat.LlmAskAudioScreen @@ -144,6 +146,11 @@ fun GalleryNavHost( navigateToTaskScreen = { task -> pickedTask = task showModelManager = true + + firebaseAnalytics?.logEvent( + "capability_select", + bundleOf("capability_name" to task.type.toString()), + ) }, )