Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1767] Enforce FOSS principles #1772

Merged
merged 9 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package co.electriccoin.zcash.app
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.ProcessLifecycleOwner
import co.electriccoin.zcash.crash.android.GlobalCrashReporter
import co.electriccoin.zcash.crash.android.di.CrashReportersProvider
import co.electriccoin.zcash.crash.android.di.crashProviderModule
import co.electriccoin.zcash.di.coreModule
import co.electriccoin.zcash.di.dataSourceModule
import co.electriccoin.zcash.di.providerModule
Expand All @@ -25,6 +27,7 @@ class ZcashApplication : CoroutineApplication() {
private val standardPreferenceProvider by inject<StandardPreferenceProvider>()
private val flexaRepository by inject<FlexaRepository>()
private val applicationStateProvider: ApplicationStateProvider by inject()
private val getAvailableCrashReporters: CrashReportersProvider by inject()

override fun onCreate() {
super.onCreate()
Expand All @@ -39,6 +42,7 @@ class ZcashApplication : CoroutineApplication() {
modules(
coreModule,
providerModule,
crashProviderModule,
dataSourceModule,
repositoryModule,
useCaseModule,
Expand Down Expand Up @@ -77,7 +81,7 @@ class ZcashApplication : CoroutineApplication() {
}

private fun configureAnalytics() {
if (GlobalCrashReporter.register(this)) {
if (GlobalCrashReporter.register(this, getAvailableCrashReporters())) {
applicationScope.launch {
StandardPreferenceKeys.IS_ANALYTICS_ENABLED.observe(standardPreferenceProvider()).collect {
if (it) {
Expand Down
33 changes: 29 additions & 4 deletions crash-android-lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import model.DistributionDimension
import model.NetworkDimension

plugins {
id("com.android.library")
kotlin("android")
Expand All @@ -21,17 +24,39 @@ android {
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}

flavorDimensions += listOf(NetworkDimension.DIMENSION_NAME, DistributionDimension.DIMENSION_NAME)

productFlavors {
create(NetworkDimension.TESTNET.value) {
dimension = NetworkDimension.DIMENSION_NAME
}

create(NetworkDimension.MAINNET.value) {
dimension = NetworkDimension.DIMENSION_NAME
}

create(DistributionDimension.STORE.value) {
dimension = DistributionDimension.DIMENSION_NAME
}

create(DistributionDimension.FOSS.value) {
dimension = DistributionDimension.DIMENSION_NAME
}
}
}

dependencies {
api(libs.androidx.annotation)
api(projects.crashLib)

implementation(platform(libs.firebase.bom))
api(libs.bundles.koin)

"storeImplementation"(platform(libs.firebase.bom))
"storeImplementation"(libs.firebase.crashlytics)
"storeImplementation"(libs.firebase.crashlytics.ndk)
"storeImplementation"(libs.firebase.installations)

implementation(libs.firebase.crashlytics)
implementation(libs.firebase.crashlytics.ndk)
implementation(libs.firebase.installations)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
implementation(projects.spackleAndroidLib)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package co.electriccoin.zcash.crash.android.internal

import android.content.Context
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter

class ListCrashReportersImpl : ListCrashReporters {
override fun provideReporters(context: Context): List<CrashReporter> {
return listOfNotNull(
LocalCrashReporter.getInstance(context),
)
}
}
18 changes: 1 addition & 17 deletions crash-android-lib/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>

<!-- For improved user privacy, don't allow Firebase to collect advertising IDs -->
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />

<!-- We want better control over the timing of Firebase initialization -->
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove" />

<provider
android:name=".internal.local.CrashProcessNameContentProvider"
android:authorities="${applicationId}.co.electriccoin.zcash.crash"
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
android:exported="false"
android:process=":crash" />

<receiver
android:name=".internal.local.ExceptionReceiver"
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
android:exported="false"
android:process=":crash" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import android.content.Context
import androidx.annotation.AnyThread
import androidx.annotation.MainThread
import co.electriccoin.zcash.crash.android.internal.CrashReporter
import co.electriccoin.zcash.crash.android.internal.firebase.FirebaseCrashReporter
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
import co.electriccoin.zcash.crash.android.internal.ListCrashReporters
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.spackle.process.ProcessNameCompat
import java.util.Collections
Expand All @@ -24,7 +23,10 @@ object GlobalCrashReporter {
* @return True if registration occurred and false if registration was skipped.
*/
@MainThread
fun register(context: Context): Boolean {
fun register(
context: Context,
reporters: ListCrashReporters
): Boolean {
if (isCrashProcess(context)) {
Twig.debug { "Skipping registration for $CRASH_PROCESS_NAME_SUFFIX process" } // $NON-NLS
return false
Expand All @@ -34,15 +36,7 @@ object GlobalCrashReporter {
if (registeredCrashReporters == null) {
registeredCrashReporters =
Collections.synchronizedList(
// To prevent a race condition, register the LocalCrashReporter first.
// FirebaseCrashReporter does some asynchronous registration internally, while
// LocalCrashReporter uses AndroidUncaughtExceptionHandler which needs to read
// and write the default UncaughtExceptionHandler. The only way to ensure
// interleaving doesn't happen is to register the LocalCrashReporter first.
listOfNotNull(
LocalCrashReporter.getInstance(context),
FirebaseCrashReporter(context),
)
reporters.provideReporters(context)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.electriccoin.zcash.crash.android.di

import co.electriccoin.zcash.crash.android.internal.ListCrashReportersImpl

class CrashReportersProvider {
operator fun invoke() = ListCrashReportersImpl()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package co.electriccoin.zcash.crash.android.di

import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.module

val crashProviderModule =
module {
factoryOf(::CrashReportersProvider)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.electriccoin.zcash.crash.android.internal

import android.content.Context

interface ListCrashReporters {
fun provideReporters(context: Context): List<CrashReporter>
}
3 changes: 0 additions & 3 deletions crash-android-lib/src/main/res/values/bools.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="co_electriccoin_zcash_crash_is_use_secondary_process">true</bool>
<!-- Expected to be overridden by a resource overlay in the app module, generated
based on the presence of the Firebase API keys -->
<bool name="co_electriccoin_zcash_crash_is_firebase_enabled">false</bool>
</resources>
32 changes: 32 additions & 0 deletions crash-android-lib/src/zcashmainnetStoreDebug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application>

<!-- For improved user privacy, don't allow Firebase to collect advertising IDs -->
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />

<!-- We want better control over the timing of Firebase initialization -->
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove" />

<provider
android:name=".internal.local.CrashProcessNameContentProvider"
android:authorities="${applicationId}.co.electriccoin.zcash.crash"
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
android:exported="false"
android:process=":crash" />

<receiver
android:name=".internal.local.ExceptionReceiver"
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
android:exported="false"
android:process=":crash" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package co.electriccoin.zcash.crash.android.internal

import android.content.Context
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter

class ListCrashReportersImpl : ListCrashReporters {
override fun provideReporters(context: Context): List<CrashReporter> {
// To prevent a race condition, register the LocalCrashReporter first.
// FirebaseCrashReporter does some asynchronous registration internally, while
// LocalCrashReporter uses AndroidUncaughtExceptionHandler which needs to read
// and write the default UncaughtExceptionHandler. The only way to ensure
// interleaving doesn't happen is to register the LocalCrashReporter first.
return listOfNotNull(
LocalCrashReporter.getInstance(context),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="co_electriccoin_zcash_crash_is_use_secondary_process">true</bool>
<!-- Expected to be overridden by a resource overlay in the app module, generated
based on the presence of the Firebase API keys -->
<bool name="co_electriccoin_zcash_crash_is_firebase_enabled">false</bool>
</resources>
32 changes: 32 additions & 0 deletions crash-android-lib/src/zcashmainnetStoreRelease/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application>

<!-- For improved user privacy, don't allow Firebase to collect advertising IDs -->
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />

<!-- We want better control over the timing of Firebase initialization -->
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove" />

<provider
android:name=".internal.local.CrashProcessNameContentProvider"
android:authorities="${applicationId}.co.electriccoin.zcash.crash"
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
android:exported="false"
android:process=":crash" />

<receiver
android:name=".internal.local.ExceptionReceiver"
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
android:exported="false"
android:process=":crash" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package co.electriccoin.zcash.crash.android.internal

import android.content.Context
import co.electriccoin.zcash.crash.android.internal.firebase.FirebaseCrashReporter
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter

class ListCrashReportersImpl : ListCrashReporters {
override fun provideReporters(context: Context): List<CrashReporter> {
// To prevent a race condition, register the LocalCrashReporter first.
// FirebaseCrashReporter does some asynchronous registration internally, while
// LocalCrashReporter uses AndroidUncaughtExceptionHandler which needs to read
// and write the default UncaughtExceptionHandler. The only way to ensure
// interleaving doesn't happen is to register the LocalCrashReporter first.
return listOfNotNull(
LocalCrashReporter.getInstance(context),
FirebaseCrashReporter(context),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package co.electriccoin.zcash.crash.android.internal.firebase

import android.content.Context
import com.google.firebase.FirebaseApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext

object FirebaseAppCache {
private val mutex = Mutex()

@Volatile
private var cachedFirebaseApp: FirebaseAppContainer? = null

fun peekFirebaseApp(): FirebaseApp? = cachedFirebaseApp?.firebaseApp

suspend fun getFirebaseApp(context: Context): FirebaseApp? {
mutex.withLock {
peekFirebaseApp()?.let {
return it
}

val firebaseAppContainer = getFirebaseAppContainer(context)

cachedFirebaseApp = firebaseAppContainer
}

return peekFirebaseApp()
}
}

private suspend fun getFirebaseAppContainer(context: Context): FirebaseAppContainer =
withContext(Dispatchers.IO) {
val firebaseApp = FirebaseApp.initializeApp(context)
FirebaseAppContainer(firebaseApp)
}

private class FirebaseAppContainer(val firebaseApp: FirebaseApp?)
Loading
Loading