mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
234 lines
8.8 KiB
Kotlin
234 lines
8.8 KiB
Kotlin
/*
|
|
* Copyright (c) 2025 Meshtastic LLC
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package com.geeksville.mesh.android
|
|
|
|
import android.app.Application
|
|
import android.content.Context
|
|
import android.provider.Settings
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.navigation.NavHostController
|
|
import com.datadog.android.Datadog
|
|
import com.datadog.android.DatadogSite
|
|
import com.datadog.android.compose.ExperimentalTrackingApi
|
|
import com.datadog.android.compose.NavigationViewTrackingEffect
|
|
import com.datadog.android.compose.enableComposeActionTracking
|
|
import com.datadog.android.core.configuration.Configuration
|
|
import com.datadog.android.log.Logger
|
|
import com.datadog.android.log.Logs
|
|
import com.datadog.android.log.LogsConfiguration
|
|
import com.datadog.android.privacy.TrackingConsent
|
|
import com.datadog.android.rum.GlobalRumMonitor
|
|
import com.datadog.android.rum.Rum
|
|
import com.datadog.android.rum.RumConfiguration
|
|
import com.datadog.android.rum.tracking.AcceptAllNavDestinations
|
|
import com.datadog.android.sessionreplay.SessionReplay
|
|
import com.datadog.android.sessionreplay.SessionReplayConfiguration
|
|
import com.datadog.android.sessionreplay.compose.ComposeExtensionSupport
|
|
import com.datadog.android.timber.DatadogTree
|
|
import com.datadog.android.trace.AndroidTracer
|
|
import com.datadog.android.trace.Trace
|
|
import com.datadog.android.trace.TraceConfiguration
|
|
import com.geeksville.mesh.BuildConfig
|
|
import com.geeksville.mesh.analytics.AnalyticsProvider
|
|
import com.geeksville.mesh.analytics.FirebaseAnalytics
|
|
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
|
|
import com.geeksville.mesh.model.DeviceHardware
|
|
import com.geeksville.mesh.util.exceptionReporter
|
|
import com.google.android.gms.common.ConnectionResult
|
|
import com.google.android.gms.common.GoogleApiAvailabilityLight
|
|
import com.suddenh4x.ratingdialog.AppRating
|
|
import io.opentracing.util.GlobalTracer
|
|
import timber.log.Timber
|
|
|
|
abstract class GeeksvilleApplication :
|
|
Application(),
|
|
Logging {
|
|
|
|
companion object {
|
|
lateinit var analytics: AnalyticsProvider
|
|
}
|
|
|
|
// / Are we running inside the testlab?
|
|
val isInTestLab: Boolean
|
|
get() {
|
|
val testLabSetting = Settings.System.getString(contentResolver, "firebase.test.lab")
|
|
if (testLabSetting != null) {
|
|
info("Testlab is $testLabSetting")
|
|
}
|
|
return "true" == testLabSetting
|
|
}
|
|
|
|
abstract val analyticsPrefs: AnalyticsPrefs
|
|
|
|
var isAnalyticsAllowed: Boolean
|
|
get() = analyticsPrefs.analyticsAllowed
|
|
set(value) {
|
|
analyticsPrefs.analyticsAllowed = value
|
|
val newConsent =
|
|
if (value && !isInTestLab) {
|
|
TrackingConsent.GRANTED
|
|
} else {
|
|
TrackingConsent.NOT_GRANTED
|
|
}
|
|
|
|
info(if (value) "Analytics enabled" else "Analytics disabled")
|
|
|
|
if (Datadog.isInitialized()) {
|
|
Datadog.setTrackingConsent(newConsent)
|
|
} else {
|
|
initDatadog()
|
|
}
|
|
|
|
// Change the flag with the providers
|
|
analytics.setEnabled(value && !isInTestLab) // Never do analytics in the test lab
|
|
}
|
|
|
|
private val minimumLaunchTimes: Int = 10
|
|
private val minimumDays: Int = 10
|
|
private val minimumLaunchTimesToShowAgain: Int = 5
|
|
private val minimumDaysToShowAgain: Int = 14
|
|
|
|
/** Ask user to rate in play store */
|
|
@Suppress("MagicNumber")
|
|
fun askToRate(activity: AppCompatActivity) {
|
|
if (!isGooglePlayAvailable) return
|
|
|
|
@Suppress("MaxLineLength")
|
|
exceptionReporter {
|
|
// we don't want to crash our app because of bugs in this optional feature
|
|
AppRating.Builder(activity)
|
|
.setMinimumLaunchTimes(minimumLaunchTimes) // default is 5, 3 means app is launched 3 or more times
|
|
.setMinimumDays(
|
|
minimumDays,
|
|
) // default is 5, 0 means install day, 10 means app is launched 10 or more days
|
|
// later than installation
|
|
.setMinimumLaunchTimesToShowAgain(
|
|
minimumLaunchTimesToShowAgain,
|
|
) // default is 5, 1 means app is launched 1 or more times after neutral button
|
|
// clicked
|
|
.setMinimumDaysToShowAgain(
|
|
minimumDaysToShowAgain,
|
|
) // default is 14, 1 means app is launched 1 or more days after neutral button
|
|
// clicked
|
|
.showIfMeetsConditions()
|
|
}
|
|
}
|
|
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
|
|
val firebaseAnalytics = FirebaseAnalytics(analyticsPrefs.installId)
|
|
analytics = firebaseAnalytics
|
|
|
|
// Set analytics per prefs
|
|
isAnalyticsAllowed = isAnalyticsAllowed
|
|
initDatadog()
|
|
}
|
|
|
|
private val sampleRate = 100f
|
|
|
|
private fun initDatadog() {
|
|
val logger =
|
|
Logger.Builder()
|
|
.setNetworkInfoEnabled(true)
|
|
.setRemoteSampleRate(sampleRate)
|
|
.setBundleWithTraceEnabled(true)
|
|
.setBundleWithRumEnabled(true)
|
|
.build()
|
|
val configuration =
|
|
Configuration.Builder(
|
|
clientToken = BuildConfig.datadogClientToken,
|
|
env = if (BuildConfig.DEBUG) "debug" else "release",
|
|
variant = BuildConfig.FLAVOR,
|
|
)
|
|
.useSite(DatadogSite.US5)
|
|
.setCrashReportsEnabled(true)
|
|
.setUseDeveloperModeWhenDebuggable(true)
|
|
.build()
|
|
val consent =
|
|
if (isAnalyticsAllowed && !isInTestLab) {
|
|
TrackingConsent.GRANTED
|
|
} else {
|
|
TrackingConsent.NOT_GRANTED
|
|
}
|
|
Datadog.initialize(this, configuration, consent)
|
|
|
|
val rumConfiguration =
|
|
RumConfiguration.Builder(BuildConfig.datadogApplicationId)
|
|
.trackAnonymousUser(true)
|
|
.trackBackgroundEvents(true)
|
|
.trackFrustrations(true)
|
|
.trackLongTasks()
|
|
.trackNonFatalAnrs(true)
|
|
// Re-enable tracking when auto instrumentation available. See note in `app/build.gradle`
|
|
.disableUserInteractionTracking()
|
|
// .trackUserInteractions()
|
|
.enableComposeActionTracking()
|
|
.build()
|
|
Rum.enable(rumConfiguration)
|
|
|
|
val logsConfig = LogsConfiguration.Builder().build()
|
|
Logs.enable(logsConfig)
|
|
|
|
val traceConfig = TraceConfiguration.Builder().build()
|
|
Trace.enable(traceConfig)
|
|
|
|
val tracer = AndroidTracer.Builder().build()
|
|
GlobalTracer.registerIfAbsent(tracer)
|
|
|
|
val sessionReplayConfig =
|
|
SessionReplayConfiguration.Builder(sampleRate = 20.0f)
|
|
// in case you need Jetpack Compose support
|
|
.addExtensionSupport(ComposeExtensionSupport())
|
|
.build()
|
|
|
|
SessionReplay.enable(sessionReplayConfig)
|
|
|
|
Timber.plant(Timber.DebugTree(), DatadogTree(logger))
|
|
}
|
|
}
|
|
|
|
fun setAttributes(firmwareVersion: String, deviceHardware: DeviceHardware) {
|
|
GlobalRumMonitor.get().addAttribute("firmware_version", firmwareVersion.extractSemanticVersion())
|
|
GlobalRumMonitor.get().addAttribute("device_hardware", deviceHardware.hwModelSlug)
|
|
}
|
|
|
|
val Context.isGooglePlayAvailable: Boolean
|
|
get() =
|
|
GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(this).let {
|
|
it != ConnectionResult.SERVICE_MISSING && it != ConnectionResult.SERVICE_INVALID
|
|
}
|
|
|
|
@OptIn(ExperimentalTrackingApi::class)
|
|
@Composable
|
|
fun AddNavigationTracking(navController: NavHostController) {
|
|
NavigationViewTrackingEffect(
|
|
navController = navController,
|
|
trackArguments = true,
|
|
destinationPredicate = AcceptAllNavDestinations(),
|
|
)
|
|
}
|
|
|
|
fun String.extractSemanticVersion(): String {
|
|
// Regex to capture up to three numeric parts separated by dots
|
|
val regex = """^(\d+)(?:\.(\d+))?(?:\.(\d+))?""".toRegex()
|
|
val matchResult = regex.find(this)
|
|
return matchResult?.groupValues?.drop(1)?.filter { it.isNotEmpty() }?.joinToString(".")
|
|
?: this // Fallback to original if no match
|
|
}
|