Commit 8e2c5812 authored by Mygod's avatar Mygod

Migrate to open source Firebase crashlytics

Fixes #2439.
parent 6cb488a9
......@@ -22,9 +22,6 @@ buildscript {
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
maven {
url "https://plugins.gradle.org/m2/"
}
......@@ -34,10 +31,10 @@ buildscript {
classpath 'com.android.tools.build:gradle:4.0.0-beta04'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.28.0'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta04'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.11.1'
classpath 'gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.3'
classpath 'io.fabric.tools:gradle:1.31.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
......
......@@ -82,11 +82,12 @@ dependencies {
api "androidx.room:room-runtime:$roomVersion"
api "androidx.work:work-runtime-ktx:$workVersion"
api "androidx.work:work-gcm:$workVersion"
api 'com.crashlytics.sdk.android:crashlytics:2.10.1'
api 'com.google.android.gms:play-services-oss-licenses:17.0.0'
api 'com.google.code.gson:gson:2.8.6'
api 'com.google.firebase:firebase-analytics:17.3.0'
api 'com.google.firebase:firebase-config:19.1.3'
api 'com.google.firebase:firebase-analytics-ktx:17.3.0'
api 'com.google.firebase:firebase-config-ktx:19.1.3'
api 'com.google.firebase:firebase-crashlytics:17.0.0-beta04'
api 'com.jakewharton.timber:timber:4.7.1'
api 'dnsjava:dnsjava:3.0.2'
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion"
......
......@@ -29,35 +29,38 @@ import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.os.Build
import android.os.UserManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.work.Configuration
import androidx.work.WorkManager
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.core.BuildConfig
import com.github.shadowsocks.core.R
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.subscription.SubscriptionService
import com.github.shadowsocks.utils.*
import com.google.firebase.FirebaseApp
import com.google.firebase.analytics.FirebaseAnalytics
import io.fabric.sdk.android.Fabric
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.DeviceStorageApp
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.google.firebase.ktx.initialize
import kotlinx.coroutines.DEBUG_PROPERTY_NAME
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.io.IOException
import kotlin.reflect.KClass
object Core {
const val TAG = "Core"
lateinit var app: Application
@VisibleForTesting set
lateinit var configureIntent: (Context) -> PendingIntent
......@@ -66,7 +69,6 @@ object Core {
val notification by lazy { app.getSystemService<NotificationManager>()!! }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) }
val directBootSupported by lazy {
Build.VERSION.SDK_INT >= 24 && app.getSystemService<DevicePolicyManager>()?.storageEncryptionStatus ==
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
......@@ -104,8 +106,18 @@ object Core {
// overhead of debug mode is minimal: https://github.com/Kotlin/kotlinx.coroutines/blob/f528898/docs/debugging.md#debug-mode
System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
Fabric.with(deviceStorage, Crashlytics()) // multiple processes needs manual set-up
FirebaseApp.initializeApp(deviceStorage)
Firebase.initialize(deviceStorage) // multiple processes needs manual set-up
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)
FirebaseCrashlytics.getInstance().log("${"XXVDIWEF".getOrElse(priority) { 'X' }}/$tag: $message")
} else {
if (priority >= Log.WARN || priority == Log.DEBUG) Log.println(priority, tag, message)
if (priority >= Log.INFO) FirebaseCrashlytics.getInstance().recordException(t)
}
}
})
WorkManager.initialize(deviceStorage, Configuration.Builder().apply {
setExecutor { GlobalScope.launch { it.run() } }
setTaskExecutor { GlobalScope.launch { it.run() } }
......@@ -121,7 +133,7 @@ object Core {
File(deviceStorage.noBackupFilesDir, file).outputStream().use { output -> input.copyTo(output) }
}
} catch (e: IOException) {
printLog(e)
Timber.w(e)
}
DataStore.publicStore.putLong(Key.assetUpdateTime, packageInfo.lastUpdateTime)
}
......
......@@ -26,19 +26,17 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.VpnService
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.core.R
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.broadcastReceiver
import timber.log.Timber
class VpnRequestActivity : AppCompatActivity() {
companion object {
private const val TAG = "VpnRequestActivity"
private const val REQUEST_CONNECT = 1
}
......@@ -66,7 +64,7 @@ class VpnRequestActivity : AppCompatActivity() {
if (requestCode != REQUEST_CONNECT) return super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) Core.startService() else {
Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show()
Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
Timber.e("Failed to start VpnService from onActivityResult: $data")
}
finish()
}
......
......@@ -21,9 +21,7 @@
package com.github.shadowsocks.acl
import android.content.Context
import android.util.Log
import androidx.recyclerview.widget.SortedList
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore
......@@ -33,6 +31,7 @@ import com.github.shadowsocks.utils.asIterable
import kotlinx.coroutines.Job
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.Reader
......@@ -42,7 +41,6 @@ import kotlin.coroutines.coroutineContext
class Acl {
companion object {
const val TAG = "Acl"
const val ALL = "all"
const val BYPASS_LAN = "bypass-lan"
const val BYPASS_CHN = "bypass-china"
......@@ -159,7 +157,7 @@ class Acl {
val child = Acl().fromReader(connect(url).getInputStream().bufferedReader(), bypass)
child.flatten(depth - 1, connect)
if (bypass != child.bypass) {
Crashlytics.log(Log.WARN, TAG, "Imported network ACL has a conflicting mode set. " +
Timber.w("Imported network ACL has a conflicting mode set. " +
"This will probably not work as intended. URL: $url")
child.subnets.clear() // subnets for the different mode are discarded
child.bypass = bypass
......
......@@ -24,6 +24,7 @@ import android.content.Context
import androidx.work.*
import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.useCancellable
import timber.log.Timber
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
......@@ -52,7 +53,7 @@ class AclSyncer(context: Context, workerParams: WorkerParameters) : CoroutineWor
Acl.getFile(route).printWriter().use { it.write(acl) }
Result.success()
} catch (e: IOException) {
e.printStackTrace()
Timber.d(e)
if (runAttemptCount > 5) Result.failure() else Result.retry()
}
}
......@@ -25,10 +25,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.*
import android.util.Log
import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
......@@ -40,9 +37,16 @@ import com.github.shadowsocks.core.R
import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.net.HostsFile
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.*
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.broadcastReceiver
import com.github.shadowsocks.utils.readableMessage
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.analytics.ktx.logEvent
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.*
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.net.URL
......@@ -122,7 +126,7 @@ object BaseService {
work(callbacks.getBroadcastItem(it))
} catch (_: RemoteException) {
} catch (e: Exception) {
printLog(e)
Timber.w(e)
}
}
} finally {
......@@ -229,7 +233,7 @@ object BaseService {
when {
s == State.Stopped -> startRunner()
s.canStop -> stopRunner(true)
else -> Crashlytics.log(Log.WARN, tag, "Illegal state when invoking use: $s")
else -> Timber.w("Illegal state $s when invoking use")
}
}
......@@ -271,7 +275,7 @@ object BaseService {
// channge the state
data.changeState(State.Stopping)
GlobalScope.launch(Dispatchers.Main.immediate) {
Core.analytics.logEvent("stop", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag)))
Firebase.analytics.logEvent("stop") { param(FirebaseAnalytics.Param.METHOD, tag) }
data.connectingJob?.cancelAndJoin() // ensure stop connecting first
this@Interface as Service
// we use a coroutineScope here to allow clean-up in parallel
......@@ -344,7 +348,7 @@ object BaseService {
}
data.notification = createNotification(profile.formattedName)
Core.analytics.logEvent("start", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag)))
Firebase.analytics.logEvent("start") { param(FirebaseAnalytics.Param.METHOD, tag) }
data.changeState(State.Connecting)
data.connectingJob = GlobalScope.launch(Dispatchers.Main) {
......@@ -365,7 +369,7 @@ object BaseService {
}
data.processes = GuardedProcessPool {
printLog(it)
Timber.w(it)
stopRunner(false, it.readableMessage)
}
startProcesses(hosts)
......@@ -379,7 +383,7 @@ object BaseService {
} catch (_: UnknownHostException) {
stopRunner(false, getString(R.string.invalid_server))
} catch (exc: Throwable) {
if (exc is ExpectedException) exc.printStackTrace() else printLog(exc)
if (exc is ExpectedException) Timber.d(exc) else Timber.w(exc)
stopRunner(false, "${getString(R.string.service_failed)}: ${exc.readableMessage}")
} finally {
data.connectingJob = null
......
......@@ -24,8 +24,7 @@ import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import android.text.TextUtils
import android.util.Log
import com.crashlytics.android.Crashlytics
import timber.log.Timber
import java.io.File
import java.io.IOException
......@@ -47,9 +46,8 @@ object Executable {
Os.kill(process.name.toInt(), OsConstants.SIGKILL)
} catch (e: ErrnoException) {
if (e.errno != OsConstants.ESRCH) {
e.printStackTrace()
Crashlytics.log(Log.WARN, "kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed")
Crashlytics.logException(e)
Timber.w("SIGKILL ${exe.absolutePath} (${process.name}) failed")
Timber.w(e)
}
}
}
......
......@@ -25,13 +25,12 @@ import android.os.SystemClock
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import android.util.Log
import androidx.annotation.MainThread
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.Commandline
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.InputStream
......@@ -39,7 +38,6 @@ import kotlin.concurrent.thread
class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : CoroutineScope {
companion object {
private const val TAG = "GuardedProcessPool"
private val pid by lazy {
Class.forName("java.lang.ProcessManager\$ProcessImpl").getDeclaredField("pid").apply { isAccessible = true }
}
......@@ -63,10 +61,10 @@ class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : C
try {
while (true) {
thread(name = "stderr-$cmdName") {
streamLogger(process.errorStream) { Crashlytics.log(Log.ERROR, cmdName, it) }
streamLogger(process.errorStream) { Timber.tag(cmdName).e(it) }
}
thread(name = "stdout-$cmdName") {
streamLogger(process.inputStream) { Crashlytics.log(Log.VERBOSE, cmdName, it) }
streamLogger(process.inputStream) { Timber.tag(cmdName).v(it) }
// this thread also acts as a daemon thread for waitFor
runBlocking { exitChannel.send(process.waitFor()) }
}
......@@ -76,17 +74,16 @@ class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : C
when {
SystemClock.elapsedRealtime() - startTime < 1000 -> throw IOException(
"$cmdName exits too fast (exit code: $exitCode)")
exitCode == 128 + OsConstants.SIGKILL -> Crashlytics.log(Log.WARN, TAG, "$cmdName was killed")
else -> Crashlytics.logException(IOException("$cmdName unexpectedly exits with code $exitCode"))
exitCode == 128 + OsConstants.SIGKILL -> Timber.w("$cmdName was killed")
else -> Timber.w(IOException("$cmdName unexpectedly exits with code $exitCode"))
}
Crashlytics.log(Log.DEBUG, TAG,
"restart process: ${Commandline.toString(cmd)} (last exit code: $exitCode)")
Timber.i("restart process: ${Commandline.toString(cmd)} (last exit code: $exitCode)")
start()
running = true
onRestartCallback?.invoke()
}
} catch (e: IOException) {
Crashlytics.log(Log.WARN, TAG, "error occurred. stop guard: " + Commandline.toString(cmd))
Timber.w("error occurred. stop guard: ${Commandline.toString(cmd)}")
GlobalScope.launch(Dispatchers.Main) { onFatal(e) }
} finally {
if (running) withContext(NonCancellable) { // clean-up cannot be cancelled
......@@ -94,9 +91,9 @@ class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : C
try {
Os.kill(pid.get(process) as Int, OsConstants.SIGTERM)
} catch (e: ErrnoException) {
if (e.errno != OsConstants.ESRCH) Crashlytics.logException(e)
if (e.errno != OsConstants.ESRCH) Timber.w(e)
} catch (e: ReflectiveOperationException) {
Crashlytics.logException(e)
Timber.w(e)
}
if (withTimeoutOrNull(500) { exitChannel.receive() } != null) return@withContext
}
......@@ -115,7 +112,7 @@ class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : C
@MainThread
fun start(cmd: List<String>, onRestartCallback: (suspend () -> Unit)? = null) {
Crashlytics.log(Log.DEBUG, TAG, "start process: " + Commandline.toString(cmd))
Timber.i("start process: ${Commandline.toString(cmd)}")
Guard(cmd).apply {
start() // if start fails, IOException will be thrown directly
launch { looper(onRestartCallback) }
......
package com.github.shadowsocks.bg
import android.net.LocalSocket
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.ConcurrentLocalSocketListener
import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.launch
import org.xbill.DNS.Message
import org.xbill.DNS.Rcode
import timber.log.Timber
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
......@@ -31,10 +29,10 @@ class LocalDnsWorker(private val resolver: suspend (ByteArray) -> ByteArray) : C
resolver(query)
} catch (e: Exception) {
when (e) {
is TimeoutCancellationException -> Crashlytics.log(Log.WARN, name, "Resolving timed out")
is TimeoutCancellationException -> Timber.w("Resolving timed out")
is CancellationException -> { } // ignore
is IOException -> Crashlytics.log(Log.WARN, name, e.message)
else -> printLog(e)
is IOException -> Timber.d(e)
else -> Timber.w(e)
}
try {
DnsResolverCompat.prepareDnsResponse(Message(query)).apply {
......
......@@ -34,6 +34,7 @@ import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.signaturesCompat
import com.github.shadowsocks.utils.useCancellable
import com.google.firebase.remoteconfig.ktx.get
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
......@@ -60,7 +61,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
scheduleConfigUpdate = !success
val conn = withContext(Dispatchers.IO) {
// Network.openConnection might use networking, see https://issuetracker.google.com/issues/135242093
service.openConnection(URL(config.getString("proxy_url"))) as HttpURLConnection
service.openConnection(URL(config["proxy_url"].asString())) as HttpURLConnection
}
conn.requestMethod = "POST"
conn.doOutput = true
......
......@@ -20,19 +20,20 @@
package com.github.shadowsocks.bg
import android.util.Log
import androidx.core.os.bundleOf
import com.github.shadowsocks.Core
import com.github.shadowsocks.core.R
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.analytics.ktx.logEvent
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.tasks.await
import timber.log.Timber
object RemoteConfig {
private val config = GlobalScope.async(Dispatchers.Main.immediate) {
FirebaseRemoteConfig.getInstance().apply { setDefaultsAsync(R.xml.default_configs).await() }
Firebase.remoteConfig.apply { setDefaultsAsync(R.xml.default_configs).await() }
}
fun fetchAsync() = GlobalScope.async(Dispatchers.Main.immediate) { fetch() }
......@@ -42,8 +43,8 @@ object RemoteConfig {
fetch().await()
this to true
} catch (e: Exception) {
Log.w("RemoteConfig", e)
Core.analytics.logEvent("femote_config_failure", bundleOf(Pair(javaClass.simpleName, e.message)))
Timber.d(e)
Firebase.analytics.logEvent("femote_config_failure") { param("message", e.toString()) }
this to false
}
}
......
......@@ -39,9 +39,13 @@ import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.closeQuietly
import com.github.shadowsocks.utils.int
import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.*
import java.io.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
import java.net.URL
import android.net.VpnService as BaseVpnService
......@@ -67,13 +71,13 @@ class VpnService : BaseVpnService(), BaseService.Interface {
} catch (e: IOException) {
when ((e.cause as? ErrnoException)?.errno) {
// also suppress ENONET (Machine is not on the network)
OsConstants.EPERM, 64 -> e.printStackTrace()
else -> printLog(e)
OsConstants.EPERM, 64 -> Timber.d(e)
else -> Timber.w(e)
}
return@let false
} catch (e: ReflectiveOperationException) {
check(Build.VERSION.SDK_INT < 23)
printLog(e)
Timber.w(e)
}
protect(fd.int)
}) 0 else 1)
......@@ -169,7 +173,7 @@ class VpnService : BaseVpnService(), BaseService.Interface {
if (profile.bypass) builder.addDisallowedApplication(it)
else builder.addAllowedApplication(it)
} catch (ex: PackageManager.NameNotFoundException) {
printLog(ex)
Timber.w(ex)
}
}
if (!profile.bypass) builder.addAllowedApplication(me)
......
......@@ -24,7 +24,6 @@ import android.annotation.TargetApi
import android.net.Uri
import android.os.Parcelable
import android.util.Base64
import android.util.Log
import android.util.LongSparseArray
import androidx.core.net.toUri
import androidx.room.*
......@@ -40,6 +39,7 @@ import com.google.gson.JsonPrimitive
import kotlinx.android.parcel.Parcelize
import org.json.JSONArray
import org.json.JSONObject
import timber.log.Timber
import java.io.Serializable
import java.net.URI
import java.net.URISyntaxException
......@@ -100,7 +100,6 @@ data class Profile(
}
companion object {
private const val TAG = "ShadowParser"
private const val serialVersionUID = 1L
private const val sponsored = "198.199.101.152"
private val pattern =
......@@ -124,7 +123,7 @@ data class Profile(
profile.name = uri.fragment
profile
} else {
Log.e(TAG, "Unrecognized URI: ${it.value}")
Timber.e("Unrecognized URI: ${it.value}")
null
}
} else {
......@@ -147,16 +146,16 @@ data class Profile(
profile.name = uri.fragment ?: ""
profile
} catch (e: URISyntaxException) {
Log.e(TAG, "Invalid URI: ${it.value}")
Timber.e("Invalid URI: ${it.value}")
null
}
} else {
Log.e(TAG, "Unknown user info: ${it.value}")
Timber.e("Unknown user info: ${it.value}")
null
}
}
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Invalid base64 detected: ${it.value}")
Timber.e("Invalid base64 detected: ${it.value}")
null
}
}.filterNotNull()
......
......@@ -26,9 +26,9 @@ import com.github.shadowsocks.Core
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.forEachTry
import com.github.shadowsocks.utils.printLog
import com.google.gson.JsonStreamParser
import org.json.JSONArray
import timber.log.Timber
import java.io.IOException
import java.io.InputStream
import java.sql.SQLException
......@@ -94,7 +94,7 @@ object ProfileManager {
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
printLog(ex)
Timber.w(ex)
null
}
......@@ -122,7 +122,7 @@ object ProfileManager {
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
printLog(ex)
Timber.w(ex)
false
}
if (!nonEmpty) DataStore.profileId = createProfile().id
......@@ -134,7 +134,7 @@ object ProfileManager {
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
printLog(ex)
Timber.w(ex)
null
}
......@@ -144,7 +144,7 @@ object ProfileManager {
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
printLog(ex)
Timber.w(ex)
null
}
}
......@@ -21,13 +21,13 @@
package com.github.shadowsocks.net
import android.net.LocalSocket
import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.*
import timber.log.Timber
import java.io.File
abstract class ConcurrentLocalSocketListener(name: String, socketFile: File) : LocalSocketListener(name, socketFile),
CoroutineScope {
override val coroutineContext = Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler { _, t -> printLog(t) }
override val coroutineContext = Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler { _, t -> Timber.w(t) }
override fun accept(socket: LocalSocket) {
launch { super.accept(socket) }
......
......@@ -27,12 +27,12 @@ import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import java.net.UnknownHostException
object DefaultNetworkListener {
......@@ -124,7 +124,7 @@ object DefaultNetworkListener {
Core.connectivity.requestNetwork(request, Callback)
} catch (e: SecurityException) {
// known bug: https://stackoverflow.com/a/33509180/2245107
if (Build.VERSION.SDK_INT != 23) printLog(e)
if (Build.VERSION.SDK_INT != 23) Timber.w(e)
fallback = true
}
}
......
......@@ -34,9 +34,9 @@ import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.closeQuietly
import com.github.shadowsocks.utils.int
import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.*
import org.xbill.DNS.*
import timber.log.Timber
import java.io.FileDescriptor
import java.io.IOException
import java.net.Inet4Address
......@@ -80,7 +80,7 @@ sealed class DnsResolverCompat {
false
} catch (e: ReflectiveOperationException) {
check(Build.VERSION.SDK_INT < 23)
printLog(e)
Timber.w(e)
checkConnectivity(network, addr)
}
private fun checkConnectivity(network: Network, addr: InetAddress): Boolean {
......@@ -88,7 +88,7 @@ sealed class DnsResolverCompat {
try {
it.matches(addr)
} catch (e: RuntimeException) {
printLog(e)
Timber.w(e)
false
}
} == true
......
......@@ -26,11 +26,11 @@ import android.net.LocalSocketAddress
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.io.IOException
......@@ -55,7 +55,7 @@ abstract class LocalSocketListener(name: String, socketFile: File) : Thread(name
try {
accept(serverSocket.accept())
} catch (e: IOException) {
if (running) printLog(e)
if (running) Timber.w(e)
continue
}
}
......
......@@ -20,9 +20,8 @@
package com.github.shadowsocks.plugin
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.utils.Commandline
import timber.log.Timber
import java.util.*
class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val selected: String) {
......@@ -44,7 +43,7 @@ class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val se
}
}
} catch (exc: Exception) {
Crashlytics.log(Log.WARN, "PluginConfiguration", exc.message)
Timber.w(exc)
}
opt
} else PluginOptions(line)
......
......@@ -30,23 +30,20 @@ import android.content.pm.ProviderInfo
import android.content.pm.Signature
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.system.Os
import android.util.Base64
import android.util.Log
import androidx.core.os.bundleOf
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.listenForPackageChanges
import com.github.shadowsocks.utils.printLog
import com.github.shadowsocks.utils.signaturesCompat
import timber.log.Timber
import java.io.File
import java.io.FileNotFoundException
object PluginManager {
private const val TAG = "PluginManager"
class PluginNotFoundException(private val plugin: String) : FileNotFoundException(plugin),
BaseService.ExpectedException {
override fun getLocalizedMessage() = app.getString(com.github.shadowsocks.core.R.string.plugin_unknown, plugin)
......@@ -130,7 +127,7 @@ object PluginManager {
val result = initNative(configuration)
if (result != null) return result
} catch (t: Throwable) {
if (throwable == null) throwable = t else printLog(t)
if (throwable == null) throwable = t else Timber.w(t)
}
// add other plugin types here
......@@ -139,11 +136,12 @@ object PluginManager {
}
private fun initNative(configuration: PluginConfiguration): Pair<String, PluginOptions>? {
var flags = PackageManager.GET_META_DATA
if (Build.VERSION.SDK_INT >= 24) {
flags = flags or PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE
}
val providers = app.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(configuration.selected)),
PackageManager.GET_META_DATA or
PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE
)
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(configuration.selected)), flags)
if (providers.isEmpty()) return null
val provider = providers.single().providerInfo
val options = configuration.getOptions { provider.loadString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
......@@ -151,7 +149,7 @@ object PluginManager {
try {
initNativeFaster(provider)?.also { return it to options }
} catch (t: Throwable) {
Crashlytics.log(Log.WARN, TAG, "Initializing native plugin faster mode failed")
Timber.w("Initializing native plugin faster mode failed")
failure = t
}
......@@ -162,7 +160,7 @@ object PluginManager {
try {
return initNativeFast(app.contentResolver, options, uri)?.let { it to options }
} catch (t: Throwable) {
Crashlytics.log(Log.WARN, TAG, "Initializing native plugin fast mode failed")
Timber.w("Initializing native plugin fast mode failed")
failure?.also { t.addSuppressed(it) }
failure = t
}
......
......@@ -27,7 +27,6 @@ import android.app.Service
import android.content.Intent
import android.content.IntentFilter
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
......@@ -42,6 +41,7 @@ import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.*
import com.google.gson.JsonStreamParser
import kotlinx.coroutines.*
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.InputStream
......@@ -59,7 +59,7 @@ class SubscriptionService : Service(), CoroutineScope {
app.getText(R.string.service_subscription), NotificationManager.IMPORTANCE_LOW)
}
override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, t -> printLog(t) }
override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, t -> Timber.w(t) }
private var worker: Job? = null
private val cancelReceiver = broadcastReceiver { _, _ -> worker?.cancel() }
private var counter = 0
......@@ -135,7 +135,7 @@ class SubscriptionService : Service(), CoroutineScope {
}
return tempFile
} catch (e: IOException) {
e.printStackTrace()
Timber.d(e)
launch(Dispatchers.Main) {
Toast.makeText(this@SubscriptionService, e.readableMessage, Toast.LENGTH_LONG).show()
}
......@@ -175,8 +175,8 @@ class SubscriptionService : Service(), CoroutineScope {
subscriptions.compute(it.name to it.formattedAddress) { _, oldProfile ->
when (oldProfile?.subscription) {
Profile.SubscriptionStatus.Active -> {
Log.w("SubscriptionService", "Duplicate profiles detected. Please use different profile " +
"names and/or address:port for better subscription support.")
Timber.w("Duplicate profiles detected. Please use different profile names and/or " +
"address:port for better subscription support.")
oldProfile
}
Profile.SubscriptionStatus.Obsolete -> {
......@@ -195,7 +195,7 @@ class SubscriptionService : Service(), CoroutineScope {
}!!
}
} catch (e: Exception) {
e.printStackTrace()
Timber.d(e)
Toast.makeText(this, e.readableMessage, Toast.LENGTH_LONG).show()
}
......
......@@ -34,11 +34,11 @@ import android.system.OsConstants
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.preference.Preference
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import timber.log.Timber
import java.io.FileDescriptor
import java.net.HttpURLConnection
import java.net.InetAddress
......@@ -53,7 +53,7 @@ fun <T> Iterable<T>.forEachTry(action: (T) -> Unit) {
if (result == null) result = e else result.addSuppressed(e)
}
if (result != null) {
result.printStackTrace()
Timber.d(result)
throw result
}
}
......@@ -140,9 +140,4 @@ fun Resources.Theme.resolveResourceId(@AttrRes resId: Int): Int {
val Intent.datas get() = listOfNotNull(data) + (clipData?.asIterable()?.mapNotNull { it.uri } ?: emptyList())
fun printLog(t: Throwable) {
Crashlytics.logException(t)
t.printStackTrace()
}
fun Preference.remove() = parent!!.removePreference(this)
......@@ -26,7 +26,7 @@ import android.util.AttributeSet
import android.view.MotionEvent
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.view.isGone
import com.github.shadowsocks.utils.printLog
import timber.log.Timber
class AutoCollapseTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) :
......@@ -40,13 +40,13 @@ class AutoCollapseTextView @JvmOverloads constructor(context: Context, attrs: At
override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) = try {
super.onFocusChanged(focused, direction, previouslyFocusedRect)
} catch (e: IndexOutOfBoundsException) {
printLog(e)
Timber.w(e)
}
override fun onTouchEvent(event: MotionEvent?) = try {
super.onTouchEvent(event)
} catch (e: IndexOutOfBoundsException) {
printLog(e)
Timber.w(e)
false
}
}
......@@ -4,8 +4,8 @@ import java.util.regex.Pattern
apply plugin: 'com.android.application'
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
......
......@@ -28,7 +28,6 @@ import android.net.VpnService
import android.os.Bundle
import android.os.Handler
import android.os.RemoteException
import android.util.Log
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabColorSchemeParams
......@@ -40,7 +39,6 @@ import androidx.core.view.GravityCompat
import androidx.core.view.updateLayoutParams
import androidx.drawerlayout.widget.DrawerLayout
import androidx.preference.PreferenceDataStore
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.acl.CustomRulesFragment
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.ShadowsocksConnection
......@@ -56,11 +54,14 @@ import com.github.shadowsocks.widget.ServiceButton
import com.github.shadowsocks.widget.StatsBar
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.analytics.ktx.logEvent
import com.google.firebase.ktx.Firebase
import timber.log.Timber
class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPreferenceDataStoreChangeListener,
NavigationView.OnNavigationItemSelectedListener {
companion object {
private const val TAG = "ShadowsocksMainActivity"
private const val REQUEST_CONNECT = 1
var stateListener: ((BaseService.State) -> Unit)? = null
......@@ -148,7 +149,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
resultCode == Activity.RESULT_OK -> Core.startService()
else -> {
snackbar().setText(R.string.vpn_permission_denied).show()
Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
Timber.e("Failed to start VpnService from onActivityResult: $data")
}
}
}
......@@ -208,7 +209,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
}
R.id.globalSettings -> displayFragment(GlobalSettingsFragment())
R.id.about -> {
Core.analytics.logEvent("about", Bundle())
Firebase.analytics.logEvent("about") { }
displayFragment(AboutFragment())
}
R.id.faq -> {
......
......@@ -44,7 +44,6 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.*
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile
......@@ -54,7 +53,6 @@ import com.github.shadowsocks.plugin.showAllowingStateLoss
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.printLog
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.widget.ListHolderListener
import com.github.shadowsocks.widget.MainListListener
......@@ -69,6 +67,7 @@ import com.google.zxing.MultiFormatWriter
import com.google.zxing.WriterException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.nio.charset.StandardCharsets
class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
......@@ -146,7 +145,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
})
}
} catch (e: WriterException) {
Crashlytics.logException(e)
Timber.w(e)
(activity as MainActivity).snackbar().setText(e.readableMessage).show()
dismiss()
null
......@@ -515,7 +514,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
return true
}
} catch (exc: Exception) {
exc.printStackTrace()
Timber.d(exc)
}
(activity as MainActivity).snackbar().setText(R.string.action_import_err).show()
true
......@@ -600,7 +599,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
it.write(profiles.toString(2))
}
} catch (e: Exception) {
printLog(e)
Timber.w(e)
(activity as MainActivity).snackbar(e.readableMessage).show()
}
}
......
......@@ -29,7 +29,6 @@ import android.hardware.camera2.CameraManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.SparseArray
import android.view.Menu
import android.view.MenuItem
......@@ -37,21 +36,23 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.core.util.forEach
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.utils.*
import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.forEachTry
import com.github.shadowsocks.utils.openBitmap
import com.github.shadowsocks.utils.readableMessage
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.samples.vision.barcodereader.BarcodeCapture
import com.google.android.gms.samples.vision.barcodereader.BarcodeGraphic
import com.google.android.gms.vision.Frame
import com.google.android.gms.vision.barcode.Barcode
import com.google.android.gms.vision.barcode.BarcodeDetector
import timber.log.Timber
import xyz.belvi.mobilevisionbarcodescanner.BarcodeRetriever
class ScannerActivity : AppCompatActivity(), BarcodeRetriever {
companion object {
private const val TAG = "ScannerActivity"
private const val REQUEST_IMPORT = 2
private const val REQUEST_IMPORT_OR_FINISH = 3
private const val REQUEST_GOOGLE_API = 4
......@@ -109,7 +110,7 @@ class ScannerActivity : AppCompatActivity(), BarcodeRetriever {
}
override fun onRetrievedMultiple(closetToClick: Barcode?, barcode: MutableList<BarcodeGraphic>?) = check(false)
override fun onBitmapScanned(sparseArray: SparseArray<Barcode>?) { }
override fun onRetrievedFailed(reason: String?) = Crashlytics.log(Log.WARN, TAG, reason)
override fun onRetrievedFailed(reason: String?) = Timber.w(reason)
override fun onPermissionRequestDenied() {
Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show()
startImport()
......
......@@ -32,7 +32,10 @@ import android.os.Parcelable
import android.text.Editable
import android.text.TextWatcher
import android.view.*
import android.widget.*
import android.widget.AdapterView
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
......@@ -57,6 +60,7 @@ import com.github.shadowsocks.widget.UndoSnackbarManager
import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.parcel.Parcelize
import me.zhanghai.android.fastscroll.FastScrollerBuilder
import timber.log.Timber
import java.net.IDN
import java.net.MalformedURLException
import java.net.URL
......@@ -449,7 +453,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
check(adapter.addToProxy(clipboard.primaryClip!!.getItemAt(0).text.toString()) != null)
} catch (exc: Exception) {
(activity as MainActivity).snackbar().setText(R.string.action_import_err).show()
exc.printStackTrace()
Timber.d(exc)
}
true
}
......
......@@ -4,8 +4,8 @@ import java.util.regex.Pattern
apply plugin: 'com.android.application'
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
......
......@@ -29,13 +29,11 @@ import android.os.Bundle
import android.os.Handler
import android.os.RemoteException
import android.text.format.Formatter
import android.util.Log
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.leanback.preference.LeanbackPreferenceFragmentCompat
import androidx.lifecycle.observe
import androidx.preference.*
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core
import com.github.shadowsocks.aidl.IShadowsocksService
......@@ -50,9 +48,9 @@ import com.github.shadowsocks.preference.HostsSummaryProvider
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.printLog
import com.github.shadowsocks.utils.readableMessage
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import timber.log.Timber
class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksConnection.Callback,
OnPreferenceDataStoreChangeListener {
......@@ -61,7 +59,6 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private const val REQUEST_REPLACE_PROFILES = 2
private const val REQUEST_EXPORT_PROFILES = 3
private const val REQUEST_HOSTS = 4
private const val TAG = "MainPreferenceFragment"
}
private lateinit var fab: ListPreference
......@@ -266,7 +263,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
when (requestCode) {
REQUEST_CONNECT -> if (resultCode == Activity.RESULT_OK) Core.startService() else {
Toast.makeText(requireContext(), R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show()
Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
Timber.e("Failed to start VpnService from onActivityResult: $data")
}
REQUEST_REPLACE_PROFILES -> {
if (resultCode != Activity.RESULT_OK) return
......@@ -276,7 +273,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
context.contentResolver.openInputStream(it)
}.filterNotNull(), true)
} catch (e: Exception) {
printLog(e)
Timber.w(e)
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
}
populateProfiles()
......@@ -290,7 +287,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
it.write(profiles.toString(2))
}
} catch (e: Exception) {
printLog(e)
Timber.w(e)
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment