Migrate to open source firebase-crashlytics

This commit is contained in:
Mygod
2020-01-24 22:22:48 +08:00
parent b55ad234ac
commit 3cee5a4782
18 changed files with 67 additions and 125 deletions

View File

@@ -7,15 +7,12 @@ buildscript {
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0-alpha09'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta01'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'io.fabric.tools:gradle:1.31.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}

View File

@@ -1,20 +1,11 @@
apply plugin: 'com.android.application'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
if (!getGradle().getStartParameter().getTaskRequests().toString().contains("Fdroid")) {
apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric'
}
def javaVersion = JavaVersion.VERSION_1_8
def aux = [
'com.crashlytics.sdk.android:crashlytics:2.10.1',
'com.google.firebase:firebase-analytics:17.2.2',
]
def lifecycleVersion = '2.2.0'
def roomVersion = '2.2.3'
android {
compileSdkVersion 29
@@ -29,8 +20,8 @@ android {
minSdkVersion 21
targetSdkVersion 29
resConfigs 'it', 'ru', 'zh-rCN'
versionCode 220
versionName '2.7.1'
versionCode 221
versionName '2.7.2'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions.annotationProcessorOptions.arguments = [
"room.incremental": "true",
@@ -55,12 +46,8 @@ android {
flavorDimensions("freedom")
packagingOptions.exclude '**/*.kotlin_*'
productFlavors {
fdroid {
dimension "freedom"
}
freedom {
dimension "freedom"
isDefault = true
}
google {
dimension "freedom"
@@ -76,6 +63,8 @@ androidExtensions {
experimental = true
}
def lifecycleVersion = '2.2.0'
def roomVersion = '2.2.3'
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.4'
kapt "androidx.room:room-compiler:$roomVersion"
@@ -93,6 +82,8 @@ dependencies {
implementation 'com.android.billingclient:billing-ktx:2.1.0'
implementation 'com.github.topjohnwu.libsu:core:2.5.1'
implementation 'com.google.android.material:material:1.1.0-rc02'
implementation 'com.google.firebase:firebase-analytics:17.2.2'
implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta01'
implementation 'com.google.zxing:core:3.4.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.linkedin.dexmaker:dexmaker:2.25.1'
@@ -100,10 +91,6 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
implementation 'org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
for (dep in aux) {
freedomImplementation dep
googleImplementation dep
}
testImplementation 'junit:junit:4.13'
androidTestImplementation "androidx.room:room-testing:$roomVersion"
androidTestImplementation 'androidx.test:runner:1.2.0'

View File

@@ -1,13 +0,0 @@
package be.mygod.vpnhotspot
import android.os.Bundle
import androidx.annotation.Size
import timber.log.Timber
object DebugHelper {
fun init() = Timber.plant(Timber.DebugTree())
fun log(tag: String?, message: String?) = Timber.tag(tag).d(message)
fun setString(key: String, value: String?) = Timber.tag(key).d(value)
fun logEvent(@Size(min = 1L, max = 40L) event: String, extras: Bundle? = null) =
Timber.tag("logEvent").d("$event: $extras")
}

View File

@@ -1,42 +0,0 @@
package be.mygod.vpnhotspot
import android.os.Bundle
import android.util.Log
import androidx.annotation.Size
import be.mygod.vpnhotspot.App.Companion.app
import com.crashlytics.android.Crashlytics
import com.google.firebase.analytics.FirebaseAnalytics
import io.fabric.sdk.android.Fabric
import timber.log.Timber
object DebugHelper {
private val analytics by lazy { FirebaseAnalytics.getInstance(app.deviceStorage) }
fun init() {
Fabric.with(app.deviceStorage, Crashlytics())
Timber.plant(object : Timber.DebugTree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (t == null) Crashlytics.log(priority, tag, message) else {
// Crashlytics.logException doesn't print to logcat
if (priority >= Log.WARN || priority == Log.DEBUG) Log.println(priority, tag, message)
if (priority >= Log.INFO) Crashlytics.logException(t)
}
}
})
}
fun log(tag: String?, message: String?) {
if (BuildConfig.DEBUG) Timber.tag(tag).d(message) else Crashlytics.log("$tag: $message")
}
fun setString(key: String, value: String?) = Crashlytics.setString(key, value)
/**
* This method is used to log "expected" and well-handled errors, i.e. we care less about logs, etc.
* logException is inappropriate sometimes because it flushes all logs that could be used to investigate other bugs.
*/
fun logEvent(@Size(min = 1L, max = 40L) event: String, extras: Bundle? = null) {
Timber.i(if (extras == null) event else "$event, extras: $extras")
analytics.logEvent(event, extras)
}
}

View File

@@ -1 +0,0 @@
freedom

View File

@@ -168,8 +168,7 @@
android:resource="@xml/log_paths" />
</provider>
<provider android:name="com.crashlytics.android.CrashlyticsInitProvider"
tools:ignore="MissingClass"
<provider android:name="com.google.firebase.provider.FirebaseInitProvider"
tools:node="remove"/>
<service android:name="com.google.firebase.components.ComponentDiscoveryService"
android:directBootAware="true"

View File

@@ -8,6 +8,9 @@ import android.content.res.Configuration
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.annotation.Size
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
@@ -20,6 +23,9 @@ import be.mygod.vpnhotspot.net.DhcpWorkaround
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.util.DeviceStorageApp
import be.mygod.vpnhotspot.util.RootSession
import com.google.firebase.FirebaseApp
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
@@ -40,7 +46,18 @@ class App : Application() {
deviceStorage.moveSharedPreferencesFrom(this, PreferenceManager(this).sharedPreferencesName)
deviceStorage.moveDatabaseFrom(this, AppDatabase.DB_NAME)
} else deviceStorage = this
DebugHelper.init()
FirebaseApp.initializeApp(deviceStorage)
Timber.plant(object : Timber.DebugTree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (t == null) {
if (priority != Log.DEBUG || BuildConfig.DEBUG) Log.println(priority, tag, message)
crashlytics.log("${"XXVDIWEF".getOrElse(priority) { 'X' }}/$tag: $message")
} else {
if (priority >= Log.WARN || priority == Log.DEBUG) Log.println(priority, tag, message)
if (priority >= Log.INFO) crashlytics.recordException(t)
}
}
})
ServiceNotification.updateNotificationChannels()
EmojiCompat.init(FontRequestEmojiCompatConfig(deviceStorage, FontRequest(
"com.google.android.gms.fonts",
@@ -49,7 +66,7 @@ class App : Application() {
R.array.com_google_android_gms_fonts_certs)).apply {
setEmojiSpanIndicatorEnabled(BuildConfig.DEBUG)
registerInitCallback(object : EmojiCompat.InitCallback() {
override fun onInitialized() = DebugHelper.log("EmojiCompat", "Initialized")
override fun onInitialized() = Timber.d("EmojiCompat initialized")
override fun onFailed(throwable: Throwable?) = Timber.d(throwable)
})
})
@@ -67,7 +84,18 @@ class App : Application() {
if (level >= TRIM_MEMORY_RUNNING_CRITICAL) GlobalScope.launch { RootSession.trimMemory() }
}
/**
* This method is used to log "expected" and well-handled errors, i.e. we care less about logs, etc.
* logException is inappropriate sometimes because it flushes all logs that could be used to investigate other bugs.
*/
fun logEvent(@Size(min = 1L, max = 40L) event: String, extras: Bundle? = null) {
Timber.i(if (extras == null) event else "$event, extras: $extras")
analytics.logEvent(event, extras)
}
lateinit var deviceStorage: Application
private val analytics by lazy { FirebaseAnalytics.getInstance(app.deviceStorage) }
val crashlytics by lazy { FirebaseCrashlytics.getInstance() }
val english by lazy {
createConfigurationContext(Configuration(resources.configuration).apply {
setLocale(Locale.ENGLISH)

View File

@@ -17,13 +17,10 @@ import be.mygod.vpnhotspot.util.StickyEvent1
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.*
import timber.log.Timber
@RequiresApi(26)
class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
companion object {
private const val TAG = "LocalOnlyHotspotService"
}
inner class Binder : android.os.Binder() {
/**
* null represents IDLE, "" represents CONNECTING, "something" represents CONNECTED.
@@ -60,7 +57,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent ->
val ifaces = intent.localOnlyTetheredIfaces ?: return@broadcastReceiver
DebugHelper.log(TAG, "onTetherStateChangedLocked: $ifaces")
Timber.d("onTetherStateChangedLocked: $ifaces")
check(ifaces.size <= 1)
val iface = ifaces.singleOrNull()
binder.iface = iface
@@ -96,7 +93,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
}
override fun onStopped() {
DebugHelper.log(TAG, "LOHCallback.onStopped")
Timber.d("LOHCallback.onStopped")
reservation = null
}

View File

@@ -36,7 +36,6 @@ import java.lang.reflect.InvocationTargetException
class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListener,
SharedPreferences.OnSharedPreferenceChangeListener {
companion object {
private const val TAG = "RepeaterService"
private const val KEY_NETWORK_NAME = "service.repeater.networkName"
private const val KEY_PASSPHRASE = "service.repeater.passphrase"
private const val KEY_OPERATING_BAND = "service.repeater.band"
@@ -347,7 +346,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
* Used during step 2, also called when connection changed
*/
private fun onP2pConnectionChanged(info: WifiP2pInfo, group: WifiP2pGroup?) = launch {
DebugHelper.log(TAG, "P2P connection changed: $info\n$group")
Timber.d("P2P connection changed: $info\n$group")
when {
!info.groupFormed || !info.isGroupOwner || group?.isGroupOwner != true -> {
if (routingManager != null) cleanLocked()

View File

@@ -16,7 +16,7 @@ import android.widget.Toast
import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.DebugHelper
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.LocalOnlyHotspotService
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
@@ -61,7 +61,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager()
context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
Toast.makeText(context, R.string.tethering_temp_hotspot_location, Toast.LENGTH_LONG).show()
} catch (e: ActivityNotFoundException) {
DebugHelper.logEvent("location_settings", bundleOf(Pair("message", e.message)))
app.logEvent("location_settings", bundleOf("message" to e.message))
SmartSnackbar.make(R.string.tethering_temp_hotspot_location).show()
}
return

View File

@@ -7,7 +7,7 @@ import android.view.View
import androidx.core.os.bundleOf
import androidx.databinding.BaseObservable
import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.DebugHelper
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.databinding.ListitemManageBinding
import be.mygod.vpnhotspot.net.TetherOffloadManager
@@ -37,9 +37,9 @@ object ManageBar : Manager() {
} catch (e1: RuntimeException) {
try {
context.startActivity(Intent().setClassName(SETTINGS_PACKAGE, SETTINGS_2))
DebugHelper.logEvent(TAG, bundleOf(Pair(SETTINGS_1, e1.message)))
app.logEvent(TAG, bundleOf(SETTINGS_1 to e1.message))
} catch (e2: RuntimeException) {
DebugHelper.logEvent(TAG, bundleOf(Pair(SETTINGS_1, e1.message), Pair(SETTINGS_2, e2.message)))
app.logEvent(TAG, bundleOf(SETTINGS_1 to e1.message, SETTINGS_2 to e2.message))
}
}
}

View File

@@ -13,7 +13,6 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.DebugHelper
import be.mygod.vpnhotspot.MainActivity
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
@@ -49,7 +48,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
"package:${mainActivity.packageName}".toUri()))
return
} catch (e: RuntimeException) {
DebugHelper.logEvent("manage_write_settings", bundleOf(Pair("message", e.message)))
app.logEvent("manage_write_settings", bundleOf("message" to e.message))
}
val started = manager.isStarted
try {
@@ -93,7 +92,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override fun onTetheringStarted() = data.notifyChange()
override fun onTetheringFailed() {
DebugHelper.log(javaClass.simpleName, "onTetheringFailed")
Timber.d(javaClass.simpleName, "onTetheringFailed")
data.notifyChange()
}

View File

@@ -9,7 +9,6 @@ import android.service.quicksettings.Tile
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import be.mygod.vpnhotspot.DebugHelper
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.TetheringService
import be.mygod.vpnhotspot.net.TetherType
@@ -110,7 +109,7 @@ sealed class TetheringTileService : TetherListeningTileService(), TetheringManag
override fun onTetheringStarted() = updateTile()
override fun onTetheringFailed() {
DebugHelper.log(javaClass.simpleName, "onTetheringFailed")
Timber.d("onTetheringFailed")
updateTile()
}

View File

@@ -3,7 +3,6 @@ package be.mygod.vpnhotspot.net.monitor
import android.system.ErrnoException
import android.system.OsConstants
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.DebugHelper
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.util.RootSession
import be.mygod.vpnhotspot.widget.SmartSnackbar
@@ -64,7 +63,7 @@ abstract class IpMonitor : Runnable {
}
err.join()
process.waitFor()
DebugHelper.log("IpMonitor", "Monitor process exited with ${process.exitValue()}")
Timber.d("Monitor process exited with ${process.exitValue()}")
}
init {
@@ -78,7 +77,7 @@ abstract class IpMonitor : Runnable {
}
handleProcess(ProcessBuilder("su", "-c", "exec ip monitor $monitoredObject"))
if (destroyed) return@thread
DebugHelper.logEvent("ip_monitor_failure")
app.logEvent("ip_monitor_failure")
}
val pool = Executors.newScheduledThreadPool(1)
pool.scheduleAtFixedRate(this, 1, 1, TimeUnit.SECONDS)
@@ -113,7 +112,7 @@ abstract class IpMonitor : Runnable {
if (currentMode != Mode.PollRoot) try {
return poll()
} catch (e: IOException) {
DebugHelper.logEvent("ip_poll_failure")
app.logEvent("ip_poll_failure")
Timber.d(e)
}
try {
@@ -125,7 +124,7 @@ abstract class IpMonitor : Runnable {
processLines(result.out.asSequence())
}
} catch (e: RuntimeException) {
DebugHelper.logEvent("ip_su_poll_failure")
app.logEvent("ip_su_poll_failure")
Timber.w(e)
}
}

View File

@@ -2,7 +2,6 @@ package be.mygod.vpnhotspot.net.monitor
import android.util.LongSparseArray
import androidx.core.os.postDelayed
import be.mygod.vpnhotspot.DebugHelper
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.room.TrafficRecord
@@ -15,7 +14,6 @@ import java.net.InetAddress
import java.util.concurrent.TimeUnit
object TrafficRecorder {
private const val TAG = "TrafficRecorder"
private const val ANYWHERE = "0.0.0.0/0"
private var scheduled = false
@@ -27,14 +25,14 @@ object TrafficRecorder {
val record = TrafficRecord(mac = mac, ip = ip, downstream = downstream)
AppDatabase.instance.trafficRecordDao.insert(record)
synchronized(this) {
DebugHelper.log(TAG, "Registering $ip%$downstream")
Timber.d("Registering $ip%$downstream")
check(records.putIfAbsent(Pair(ip, downstream), record) == null)
scheduleUpdateLocked()
}
}
fun unregister(ip: InetAddress, downstream: String) = synchronized(this) {
update() // flush stats before removing
DebugHelper.log(TAG, "Unregistering $ip%$downstream")
Timber.d("Unregistering $ip%$downstream")
if (records.remove(Pair(ip, downstream)) == null) Timber.w("Failed to find traffic record for $ip%$downstream.")
}
@@ -142,7 +140,7 @@ object TrafficRecorder {
fun clean() = synchronized(this) {
update()
unscheduleUpdateLocked()
DebugHelper.log(TAG, "Cleaning records")
Timber.d("Cleaning records")
records.clear()
}

View File

@@ -2,12 +2,9 @@ package be.mygod.vpnhotspot.net.monitor
import android.net.*
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.DebugHelper
import timber.log.Timber
object VpnMonitor : UpstreamMonitor() {
private const val TAG = "VpnMonitor"
private val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
@@ -66,7 +63,7 @@ object VpnMonitor : UpstreamMonitor() {
if (available.isNotEmpty()) {
val next = available.entries.first()
currentNetwork = next.key
DebugHelper.log(TAG, "Switching to ${next.value.interfaceName} as VPN interface")
Timber.d("Switching to ${next.value.interfaceName} as VPN interface")
callbacks.forEach { it.onAvailable(next.value.interfaceName!!, next.value) }
} else currentNetwork = null
}

View File

@@ -4,7 +4,7 @@ import android.annotation.SuppressLint
import android.net.wifi.WpsInfo
import android.net.wifi.p2p.WifiP2pGroup
import android.net.wifi.p2p.WifiP2pManager
import be.mygod.vpnhotspot.DebugHelper
import be.mygod.vpnhotspot.App.Companion.app
import com.android.dx.stock.ProxyBuilder
import timber.log.Timber
import java.lang.reflect.Proxy
@@ -30,7 +30,7 @@ object WifiP2pManagerHelper {
try {
setWifiP2pChannels.invoke(this, c, lc, oc, listener)
} catch (_: NoSuchMethodException) {
DebugHelper.logEvent("NoSuchMethod_setWifiP2pChannels")
app.logEvent("NoSuchMethod_setWifiP2pChannels")
listener.onFailure(UNSUPPORTED)
}
}
@@ -46,7 +46,7 @@ object WifiP2pManagerHelper {
WifiP2pManager::class.java.getDeclaredMethod("startWps",
WifiP2pManager.Channel::class.java, WpsInfo::class.java, WifiP2pManager.ActionListener::class.java)
} catch (_: NoSuchMethodException) {
DebugHelper.logEvent("NoSuchMethod_startWps")
app.logEvent("NoSuchMethod_startWps")
null
}
}
@@ -69,7 +69,7 @@ object WifiP2pManagerHelper {
try {
deletePersistentGroup.invoke(this, c, netId, listener)
} catch (_: NoSuchMethodException) {
DebugHelper.logEvent("NoSuchMethod_deletePersistentGroup")
app.logEvent("NoSuchMethod_deletePersistentGroup")
listener.onFailure(UNSUPPORTED)
}
}

View File

@@ -3,7 +3,6 @@ package be.mygod.vpnhotspot.net.wifi.configuration
import android.net.wifi.p2p.WifiP2pGroup
import android.os.Build
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.DebugHelper
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.util.RootSession
import java.io.File
@@ -109,9 +108,9 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup, ownerAddress:
}
Triple(result, target!!, shell.err.isNotEmpty())
} catch (e: RuntimeException) {
DebugHelper.setString(TAG, parser.lines.joinToString("\n"))
DebugHelper.setString("$TAG.ownerAddress", ownerAddress)
DebugHelper.setString("$TAG.p2pGroup", group.toString())
app.crashlytics.setCustomKey(TAG, parser.lines.joinToString("\n"))
app.crashlytics.setCustomKey("$TAG.ownerAddress", ownerAddress.toString())
app.crashlytics.setCustomKey("$TAG.p2pGroup", group.toString())
throw e
}
}