diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 03c5bbed1..b266b618b 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -52,13 +52,20 @@ jobs: - name: Run local tests run: ./gradlew testFdroidDebug testGoogleDebug - - name: Upload debug artifact + - name: Upload fdroid debug artifact uses: actions/upload-artifact@v4 with: name: fdroidDebug path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk retention-days: 30 + - name: Upload sideload debug artifact + uses: actions/upload-artifact@v4 + with: + name: sideloadDebug + path: app/build/outputs/apk/sideload/debug/app-sideload-debug.apk + retention-days: 30 + - name: Upload build reports if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 diff --git a/app/build.gradle b/app/build.gradle index c85287cf3..2b9229987 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,6 +41,11 @@ android { } flavorDimensions = ['default'] productFlavors { + sideload { + dimension = 'default' + applicationIdSuffix ".sideload" + versionNameSuffix "-sideload" + } fdroid { dimension = 'default' dependenciesInfo { diff --git a/app/src/sideload/java/com/geeksville/mesh/MeshUtilApplication.kt b/app/src/sideload/java/com/geeksville/mesh/MeshUtilApplication.kt new file mode 100644 index 000000000..1674ad754 --- /dev/null +++ b/app/src/sideload/java/com/geeksville/mesh/MeshUtilApplication.kt @@ -0,0 +1,33 @@ +/* + * 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 . + */ + +package com.geeksville.mesh + +import com.geeksville.mesh.android.GeeksvilleApplication +import com.geeksville.mesh.android.Logging +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MeshUtilApplication : GeeksvilleApplication() { + + override fun onCreate() { + super.onCreate() + + Logging.showLogs = BuildConfig.DEBUG + + } +} \ No newline at end of file diff --git a/app/src/sideload/java/com/geeksville/mesh/analytics/NopAnalytics.kt b/app/src/sideload/java/com/geeksville/mesh/analytics/NopAnalytics.kt new file mode 100644 index 000000000..658c4370b --- /dev/null +++ b/app/src/sideload/java/com/geeksville/mesh/analytics/NopAnalytics.kt @@ -0,0 +1,69 @@ +/* + * 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 . + */ + +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") +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() { + } +} diff --git a/app/src/sideload/java/com/geeksville/mesh/android/GeeksvilleApplication.kt b/app/src/sideload/java/com/geeksville/mesh/android/GeeksvilleApplication.kt new file mode 100644 index 000000000..f58046e30 --- /dev/null +++ b/app/src/sideload/java/com/geeksville/mesh/android/GeeksvilleApplication.kt @@ -0,0 +1,73 @@ +/* + * 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 . + */ + +package com.geeksville.mesh.android + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit +import com.geeksville.mesh.analytics.AnalyticsProvider + +open 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 + } + + private val analyticsPrefs: SharedPreferences by lazy { + getSharedPreferences("analytics-prefs", Context.MODE_PRIVATE) + } + + var isAnalyticsAllowed: Boolean + get() = analyticsPrefs.getBoolean("allowed", true) + set(value) { + analyticsPrefs.edit { + putBoolean("allowed", value) + } + + // Change the flag with the providers + analytics.setEnabled(value && !isInTestLab) // Never do analytics in the test lab + } + + @Suppress("UNUSED_PARAMETER") + fun askToRate(activity: AppCompatActivity) { + // do nothing + } + + override fun onCreate() { + super.onCreate() + + val nopAnalytics = com.geeksville.mesh.analytics.NopAnalytics(this) + analytics = nopAnalytics + isAnalyticsAllowed = false + } +} + +fun Context.isGooglePlayAvailable(): Boolean = false \ No newline at end of file diff --git a/app/src/sideload/res/drawable/ic_launcher2_background.xml b/app/src/sideload/res/drawable/ic_launcher2_background.xml new file mode 100644 index 000000000..1af9b2589 --- /dev/null +++ b/app/src/sideload/res/drawable/ic_launcher2_background.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/sideload/res/drawable/ic_launcher2_foreground.xml b/app/src/sideload/res/drawable/ic_launcher2_foreground.xml new file mode 100644 index 000000000..8a7b5767a --- /dev/null +++ b/app/src/sideload/res/drawable/ic_launcher2_foreground.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/app/src/sideload/res/values/strings.xml b/app/src/sideload/res/values/strings.xml new file mode 100644 index 000000000..fdad9f7a7 --- /dev/null +++ b/app/src/sideload/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Meshtastic-sideload" + \ No newline at end of file