refactor(analytics)!: modularize analytics - remove Logging (#3256)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-09-30 18:22:22 -05:00 committed by GitHub
parent 9aa0cf9335
commit cad88d277b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 1219 additions and 1426 deletions

View file

@ -1,33 +0,0 @@
/*
* 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
import com.geeksville.mesh.android.GeeksvilleApplication
import dagger.hilt.android.HiltAndroidApp
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import javax.inject.Inject
@HiltAndroidApp
class MeshUtilApplication : GeeksvilleApplication() {
@Inject override lateinit var analyticsPrefs: AnalyticsPrefs
override fun onCreate() {
super.onCreate()
}
}

View file

@ -1,58 +0,0 @@
/*
* 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.analytics
import android.content.Context
import com.geeksville.mesh.android.Logging
class DataPair(val name: String, valueIn: Any?) {
val value = valueIn ?: "null"
// / An accumulating firebase event - only one allowed per event
constructor(d: Double) : this("BOGUS", d)
constructor(d: Int) : this("BOGUS", d)
}
/** Implement our analytics API using Firebase Analytics */
@Suppress("UNUSED_PARAMETER", "EmptyFunctionBlock", "EmptyInitBlock")
class NopAnalytics(context: Context) :
AnalyticsProvider,
Logging {
init {}
override fun setEnabled(on: Boolean) {}
override fun endSession() {}
override fun trackLowValue(event: String, vararg properties: DataPair) {}
override fun track(event: String, vararg properties: DataPair) {}
override fun startSession() {}
override fun setUserInfo(vararg p: DataPair) {}
override fun increment(name: String, amount: Double) {}
/** Send a google analytics screen view event */
override fun sendScreenView(name: String) {}
override fun endScreenView() {}
}

View file

@ -1,85 +0,0 @@
/*
* 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.geeksville.mesh.BuildConfig
import com.geeksville.mesh.analytics.AnalyticsProvider
import com.geeksville.mesh.analytics.NopAnalytics
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.BuildUtils.info
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
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") ?: null
if (testLabSetting != null) {
info("Testlab is $testLabSetting")
}
return "true" == testLabSetting
}
abstract val analyticsPrefs: AnalyticsPrefs
@Suppress("EmptyFunctionBlock", "UnusedParameter")
fun askToRate(application: AppCompatActivity) {}
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
val nopAnalytics = NopAnalytics(this)
analytics = nopAnalytics
}
}
@Suppress("UnusedParameter")
fun setAttributes(deviceVersion: String, deviceHardware: DeviceHardware) {
// No-op for F-Droid version
info("Setting attributes: deviceVersion=$deviceVersion, deviceHardware=$deviceHardware")
}
@Composable
fun AddNavigationTracking(navController: NavHostController) {
// No-op for F-Droid version
navController.addOnDestinationChangedListener { _, destination, _ ->
debug("Navigation changed to: ${destination.route}")
}
}
val Context.isAnalyticsAvailable: Boolean
get() = false

View file

@ -63,7 +63,6 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.MeshProtos.Waypoint
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.gpsDisabled
import com.geeksville.mesh.android.hasGps
import com.geeksville.mesh.copy
@ -107,6 +106,7 @@ import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.Polygon
import org.osmdroid.views.overlay.infowindow.InfoWindow
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
import timber.log.Timber
import java.io.File
import java.text.DateFormat
@ -116,7 +116,7 @@ private fun MapView.UpdateMarkers(
waypointMarkers: List<MarkerWithLabel>,
nodeClusterer: RadiusMarkerClusterer,
) {
debug("Showing on map: ${nodeMarkers.size} nodes ${waypointMarkers.size} waypoints")
Timber.d("Showing on map: ${nodeMarkers.size} nodes ${waypointMarkers.size} waypoints")
overlays.removeAll { it is MarkerWithLabel }
// overlays.addAll(nodeMarkers + waypointMarkers)
overlays.addAll(waypointMarkers)
@ -242,7 +242,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
fun loadOnlineTileSourceBase(): ITileSource {
val id = mapViewModel.mapStyleId
debug("mapStyleId from prefs: $id")
Timber.d("mapStyleId from prefs: $id")
return CustomTileSource.getTileSource(id).also {
zoomLevelMax = it.maximumZoomLevel.toDouble()
showDownloadButton = if (it is OnlineTileSourceBase) it.tileSourcePolicy.acceptsBulkDownload() else false
@ -261,11 +261,11 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
fun MapView.toggleMyLocation() {
if (context.gpsDisabled()) {
debug("Telling user we need location turned on for MyLocationNewOverlay")
Timber.d("Telling user we need location turned on for MyLocationNewOverlay")
Toast.makeText(context, R.string.location_disabled, Toast.LENGTH_SHORT).show()
return
}
debug("user clicked MyLocationNewOverlay ${myLocationOverlay == null}")
Timber.d("user clicked MyLocationNewOverlay ${myLocationOverlay == null}")
if (myLocationOverlay == null) {
myLocationOverlay =
MyLocationNewOverlay(this).apply {
@ -352,14 +352,14 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
fun showDeleteMarkerDialog(waypoint: Waypoint) {
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(R.string.waypoint_delete)
builder.setNeutralButton(R.string.cancel) { _, _ -> debug("User canceled marker delete dialog") }
builder.setNeutralButton(R.string.cancel) { _, _ -> Timber.d("User canceled marker delete dialog") }
builder.setNegativeButton(R.string.delete_for_me) { _, _ ->
debug("User deleted waypoint ${waypoint.id} for me")
Timber.d("User deleted waypoint ${waypoint.id} for me")
mapViewModel.deleteWaypoint(waypoint.id)
}
if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
builder.setPositiveButton(R.string.delete_for_everyone) { _, _ ->
debug("User deleted waypoint ${waypoint.id} for everyone")
Timber.d("User deleted waypoint ${waypoint.id} for everyone")
mapViewModel.sendWaypoint(waypoint.copy { expire = 1 })
mapViewModel.deleteWaypoint(waypoint.id)
}
@ -382,7 +382,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
fun showMarkerLongPressDialog(id: Int) {
performHapticFeedback()
debug("marker long pressed id=$id")
Timber.d("marker long pressed id=$id")
val waypoint = waypoints[id]?.data?.waypoint ?: return
// edit only when unlocked or lockedTo myNodeNum
if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
@ -570,9 +570,9 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
),
)
} catch (ex: TileSourcePolicyException) {
debug("Tile source does not allow archiving: ${ex.message}")
Timber.d("Tile source does not allow archiving: ${ex.message}")
} catch (ex: Exception) {
debug("Tile source exception: ${ex.message}")
Timber.d("Tile source exception: ${ex.message}")
}
}
@ -582,7 +582,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
val mapStyleInt = mapViewModel.mapStyleId
builder.setSingleChoiceItems(mapStyles, mapStyleInt) { dialog, which ->
debug("Set mapStyleId pref to $which")
Timber.d("Set mapStyleId pref to $which")
mapViewModel.mapStyleId = which
dialog.dismiss()
map.setTileSource(loadOnlineTileSourceBase())
@ -768,7 +768,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
EditWaypointDialog(
waypoint = showEditWaypointDialog ?: return, // Safe call
onSendClicked = { waypoint ->
debug("User clicked send waypoint ${waypoint.id}")
Timber.d("User clicked send waypoint ${waypoint.id}")
showEditWaypointDialog = null
mapViewModel.sendWaypoint(
waypoint.copy {
@ -781,12 +781,12 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
)
},
onDeleteClicked = { waypoint ->
debug("User clicked delete waypoint ${waypoint.id}")
Timber.d("User clicked delete waypoint ${waypoint.id}")
showEditWaypointDialog = null
showDeleteMarkerDialog(waypoint)
},
onDismissRequest = {
debug("User clicked cancel marker edit dialog")
Timber.d("User clicked cancel marker edit dialog")
showEditWaypointDialog = null
},
)

View file

@ -34,7 +34,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.android.BuildUtils.errormsg
import org.meshtastic.feature.map.requiredZoomLevel
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.ITileSource
@ -43,6 +42,7 @@ import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import timber.log.Timber
@SuppressLint("WakelockTimeout")
private fun PowerManager.WakeLock.safeAcquire() {
@ -50,9 +50,9 @@ private fun PowerManager.WakeLock.safeAcquire() {
try {
acquire()
} catch (e: SecurityException) {
errormsg("WakeLock permission exception: ${e.message}")
Timber.e("WakeLock permission exception: ${e.message}")
} catch (e: IllegalStateException) {
errormsg("WakeLock acquire() exception: ${e.message}")
Timber.e("WakeLock acquire() exception: ${e.message}")
}
}
}
@ -62,7 +62,7 @@ private fun PowerManager.WakeLock.safeRelease() {
try {
release()
} catch (e: IllegalStateException) {
errormsg("WakeLock release() exception: ${e.message}")
Timber.e("WakeLock release() exception: ${e.message}")
}
}
}