mirror of
https://github.com/google-ai-edge/gallery.git
synced 2025-07-05 06:00:31 -04:00
[gallery] add Analytics events: model_download
PiperOrigin-RevId: 778630845
This commit is contained in:
parent
d97e115993
commit
315820b146
8 changed files with 146 additions and 16 deletions
|
@ -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()
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import androidx.core.app.ActivityCompat
|
|||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
|
@ -39,6 +40,7 @@ import androidx.work.WorkQuery
|
|||
import com.google.ai.edge.gallery.AppLifecycleProvider
|
||||
import com.google.ai.edge.gallery.R
|
||||
import com.google.ai.edge.gallery.common.readLaunchInfo
|
||||
import com.google.ai.edge.gallery.firebaseAnalytics
|
||||
import com.google.ai.edge.gallery.worker.DownloadWorker
|
||||
import com.google.common.util.concurrent.FutureCallback
|
||||
import com.google.common.util.concurrent.Futures
|
||||
|
@ -82,6 +84,15 @@ class DefaultDownloadRepository(
|
|||
private val lifecycleProvider: AppLifecycleProvider,
|
||||
) : DownloadRepository {
|
||||
private val workManager = WorkManager.getInstance(context)
|
||||
/**
|
||||
* Stores the start time of a model download.
|
||||
*
|
||||
* We use SharedPreferences to persist the download start times. This ensures that the data is
|
||||
* still available after the app restarts. The key is the model name and the value is the download
|
||||
* start time in milliseconds.
|
||||
*/
|
||||
private val downloadStartTimeSharedPreferences =
|
||||
context.getSharedPreferences("download_start_time_ms", Context.MODE_PRIVATE)
|
||||
|
||||
override fun downloadModel(
|
||||
model: Model,
|
||||
|
@ -175,6 +186,17 @@ class DefaultDownloadRepository(
|
|||
workManager.getWorkInfoByIdLiveData(workerId).observeForever { workInfo ->
|
||||
if (workInfo != null) {
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.ENQUEUED -> {
|
||||
with(downloadStartTimeSharedPreferences.edit()) {
|
||||
putLong(model.name, System.currentTimeMillis())
|
||||
apply()
|
||||
}
|
||||
firebaseAnalytics?.logEvent(
|
||||
"model_download",
|
||||
bundleOf("event_type" to "start", "model_id" to model.name),
|
||||
)
|
||||
}
|
||||
|
||||
WorkInfo.State.RUNNING -> {
|
||||
val receivedBytes = workInfo.progress.getLong(KEY_MODEL_DOWNLOAD_RECEIVED_BYTES, 0L)
|
||||
val downloadRate = workInfo.progress.getLong(KEY_MODEL_DOWNLOAD_RATE, 0L)
|
||||
|
@ -210,6 +232,21 @@ class DefaultDownloadRepository(
|
|||
text = context.getString(R.string.notification_content_success).format(model.name),
|
||||
modelName = model.name,
|
||||
)
|
||||
|
||||
val startTime = downloadStartTimeSharedPreferences.getLong(model.name, 0L)
|
||||
val duration = System.currentTimeMillis() - startTime
|
||||
firebaseAnalytics?.logEvent(
|
||||
"model_download",
|
||||
bundleOf(
|
||||
"event_type" to "success",
|
||||
"model_id" to model.name,
|
||||
"duration_ms" to duration,
|
||||
),
|
||||
)
|
||||
with(downloadStartTimeSharedPreferences.edit()) {
|
||||
remove(model.name)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
WorkInfo.State.FAILED,
|
||||
|
@ -233,6 +270,22 @@ class DefaultDownloadRepository(
|
|||
model,
|
||||
ModelDownloadStatus(status = status, errorMessage = errorMessage),
|
||||
)
|
||||
|
||||
val startTime = downloadStartTimeSharedPreferences.getLong(model.name, 0L)
|
||||
val duration = System.currentTimeMillis() - startTime
|
||||
// TODO: Add failure reasons
|
||||
firebaseAnalytics?.logEvent(
|
||||
"model_download",
|
||||
bundleOf(
|
||||
"event_type" to "failure",
|
||||
"model_id" to model.name,
|
||||
"duration_ms" to duration,
|
||||
),
|
||||
)
|
||||
with(downloadStartTimeSharedPreferences.edit()) {
|
||||
remove(model.name)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
|
|
|
@ -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))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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()),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue