Commit ed9acb66 authored by Mygod's avatar Mygod

Migrate to Crashlytics

parent 96f5441e
......@@ -21,12 +21,16 @@ buildscript {
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0-beta03'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
classpath 'com.google.gms:google-services:4.0.1'
classpath 'io.fabric.tools:gradle:1.25.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
......
......@@ -3,6 +3,7 @@ import java.util.regex.Matcher
import java.util.regex.Pattern
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
......@@ -66,6 +67,7 @@ dependencies {
implementation "com.android.support:design:$supportLibraryVersion"
implementation "com.android.support:gridlayout-v7:$supportLibraryVersion"
implementation "com.android.support:support-v4:$supportLibraryVersion"
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.4'
implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5'
implementation "com.google.android.gms:play-services-ads:15.0.1"
implementation "com.google.android.gms:play-services-analytics:16.0.0"
......
......@@ -38,6 +38,8 @@
android:value="@integer/google_play_services_version"/>
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg"/>
<meta-data android:name="firebase_crashlytics_collection_enabled"
android:value="false"/>
<activity
android:name=".MainActivity"
......
......@@ -39,6 +39,7 @@ import android.support.annotation.RequiresApi
import android.support.v7.app.AppCompatDelegate
import android.util.Log
import androidx.work.WorkManager
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile
......@@ -49,11 +50,11 @@ import com.github.shadowsocks.preference.IconListPreference
import com.github.shadowsocks.utils.*
import com.google.android.gms.analytics.GoogleAnalytics
import com.google.android.gms.analytics.HitBuilders
import com.google.android.gms.analytics.StandardExceptionParser
import com.google.android.gms.analytics.Tracker
import com.google.firebase.FirebaseApp
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat
import io.fabric.sdk.android.Fabric
import java.io.File
import java.io.IOException
......@@ -67,7 +68,6 @@ class App : Application() {
val deviceContext: Context by lazy { if (Build.VERSION.SDK_INT < 24) this else DeviceContext(this) }
val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) }
private val exceptionParser by lazy { StandardExceptionParser(this, null) }
val info: PackageInfo by lazy { getPackageInfo(packageName) }
val directBootSupported by lazy {
Build.VERSION.SDK_INT >= 24 && getSystemService(DevicePolicyManager::class.java)
......@@ -100,18 +100,11 @@ class App : Application() {
.setAction(action)
.setLabel(BuildConfig.VERSION_NAME)
.build())
fun track(t: Throwable) = track(Thread.currentThread(), t)
fun track(thread: Thread, t: Throwable) {
tracker.send(HitBuilders.ExceptionBuilder()
.setDescription("${exceptionParser.getDescription(thread.name, t)} - ${t.message}")
.setFatal(false)
.build())
t.printStackTrace()
}
override fun onCreate() {
super.onCreate()
app = this
Fabric.with(this, Crashlytics()) // multiple processes needs manual set-up
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
BottomSheetPreferenceDialogFragment::class.java)
......@@ -128,7 +121,10 @@ class App : Application() {
FirebaseApp.initializeApp(deviceContext)
remoteConfig.setDefaults(R.xml.default_configs)
remoteConfig.fetch().addOnCompleteListener {
if (it.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config")
if (it.isSuccessful) remoteConfig.activateFetched() else {
Log.e(TAG, "Failed to fetch config")
Crashlytics.logException(it.exception)
}
}
WorkManager.initialize(deviceContext, androidx.work.Configuration.Builder().build())
......@@ -144,8 +140,7 @@ class App : Application() {
File(deviceContext.filesDir, file).outputStream().use { output -> input.copyTo(output) }
}
} catch (e: IOException) {
Log.e(TAG, e.message)
app.track(e)
printLog(e)
}
DataStore.publicStore.putLong(Key.assetUpdateTime, info.lastUpdateTime)
}
......
......@@ -45,6 +45,7 @@ import android.text.format.Formatter
import android.util.Log
import android.view.View
import android.widget.TextView
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.CustomRulesFragment
......@@ -138,7 +139,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
if (msg != null) {
Snackbar.make(findViewById(R.id.snackbar),
getString(R.string.vpn_error).format(Locale.ENGLISH, msg), Snackbar.LENGTH_LONG).show()
Log.e(TAG, "Error to start VPN service: $msg")
}
statusText.setText(R.string.not_connected)
}
......@@ -211,7 +211,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) app.startService() else {
Snackbar.make(findViewById(R.id.snackbar), R.string.vpn_permission_denied, Snackbar.LENGTH_LONG).show()
Log.e(TAG, "Failed to start VpnService: $data")
Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
}
}
......
......@@ -34,10 +34,12 @@ import android.util.Log
import android.util.SparseArray
import android.view.MenuItem
import android.widget.Toast
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.utils.openBitmap
import com.github.shadowsocks.utils.printLog
import com.github.shadowsocks.utils.resolveResourceId
import com.github.shadowsocks.utils.systemService
import com.google.android.gms.common.GoogleApiAvailability
......@@ -111,9 +113,7 @@ class ScannerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener, Ba
}
override fun onRetrievedMultiple(closetToClick: Barcode?, barcode: MutableList<BarcodeGraphic>?) = check(false)
override fun onBitmapScanned(sparseArray: SparseArray<Barcode>?) { }
override fun onRetrievedFailed(reason: String?) {
Log.w(TAG, reason)
}
override fun onRetrievedFailed(reason: String?) = Crashlytics.log(Log.WARN, TAG, reason)
override fun onPermissionRequestDenied() {
Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show()
startImport()
......@@ -146,7 +146,7 @@ class ScannerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener, Ba
success = true
}
} catch (e: Exception) {
app.track(e)
printLog(e)
}
Toast.makeText(this, if (success) R.string.action_import_msg else R.string.action_import_err,
Toast.LENGTH_SHORT).show()
......
......@@ -26,8 +26,10 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.VpnService
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService
......@@ -61,7 +63,10 @@ class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == RESULT_OK) app.startService() else Log.e(TAG, "Failed to start VpnService")
if (resultCode == RESULT_OK) app.startService() else {
Snackbar.make(findViewById(R.id.snackbar), R.string.vpn_permission_denied, Snackbar.LENGTH_LONG).show()
Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
}
finish()
}
......
......@@ -23,6 +23,7 @@ package com.github.shadowsocks.acl
import android.content.Context
import android.support.v7.util.SortedList
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Subnet
......@@ -164,7 +165,7 @@ class Acl {
continue
}
if (bypass != child.bypass) {
Log.w(TAG, "Imported network ACL has a conflicting mode set. " +
Crashlytics.log(Log.WARN, TAG, "Imported network ACL has a conflicting mode set. " +
"This will probably not work as intended. URL: $url")
// rules for the different mode are discarded
child.hostnames.clear()
......
......@@ -45,10 +45,7 @@ import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R
import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.resolveResourceId
import com.github.shadowsocks.utils.systemService
import com.github.shadowsocks.utils.*
import com.github.shadowsocks.widget.UndoSnackbarManager
import java.net.IDN
import java.net.MalformedURLException
......@@ -443,7 +440,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
} catch (exc: Exception) {
Snackbar.make(requireActivity().findViewById(R.id.snackbar), R.string.action_import_err,
Snackbar.LENGTH_LONG).show()
app.track(exc)
printLog(exc)
}
true
}
......
......@@ -31,6 +31,7 @@ import android.os.RemoteCallbackList
import android.support.v4.os.UserManagerCompat
import android.util.Base64
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.R
import com.github.shadowsocks.acl.Acl
......@@ -119,8 +120,7 @@ object BaseService {
if (bandwidthListeners.contains(item.asBinder()))
item.trafficUpdated(profile.id, txRate, rxRate, txTotal, rxTotal)
} catch (e: Exception) {
e.printStackTrace()
app.track(e)
printLog(e)
}
callbacks.finishBroadcast()
}
......@@ -164,8 +164,7 @@ object BaseService {
val item = callbacks.getBroadcastItem(i)
if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(profile.id)
} catch (e: Exception) {
e.printStackTrace()
app.track(e)
printLog(e)
}
}
callbacks.finishBroadcast()
......@@ -219,8 +218,7 @@ object BaseService {
for (i in 0 until n) try {
callbacks.getBroadcastItem(i).stateChanged(s, binder.profileName, msg)
} catch (e: Exception) {
e.printStackTrace()
app.track(e)
printLog(e)
}
callbacks.finishBroadcast()
}
......@@ -248,7 +246,7 @@ object BaseService {
stopRunner(false)
startRunner()
}
else -> Log.w(tag, "Illegal state when invoking use: $s")
else -> Crashlytics.log(Log.WARN, tag, "Illegal state when invoking use: $s")
}
}
......@@ -416,7 +414,7 @@ object BaseService {
stopRunner(true, getString(R.string.reboot_required))
} catch (exc: Throwable) {
stopRunner(true, "${getString(R.string.service_failed)}: ${exc.message}")
app.track(exc)
printLog(exc)
}
}
return Service.START_NOT_STICKY
......
......@@ -22,6 +22,7 @@ package com.github.shadowsocks.bg
import android.text.TextUtils
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
import java.io.File
......@@ -34,7 +35,7 @@ object Executable {
const val TUN2SOCKS = "libtun2socks.so"
const val OVERTURE = "liboverture.so"
val EXECUTABLES = setOf(SS_LOCAL, SS_TUNNEL, REDSOCKS, TUN2SOCKS, OVERTURE)
private val EXECUTABLES = setOf(SS_LOCAL, SS_TUNNEL, REDSOCKS, TUN2SOCKS, OVERTURE)
fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) {
......@@ -45,7 +46,10 @@ object Executable {
}.split(Character.MIN_VALUE, limit = 2).first())
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) {
val errno = JniHelper.sigkill(process.name.toInt())
if (errno != 0) Log.w("kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed with $errno")
if (errno != 0) {
Crashlytics.log(Log.WARN, "kill",
"SIGKILL ${exe.absolutePath} (${process.name}) failed with $errno")
}
}
}
}
......
......@@ -23,8 +23,8 @@ package com.github.shadowsocks.bg
import android.os.Build
import android.os.SystemClock
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.BuildConfig
import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.utils.Commandline
import com.github.shadowsocks.utils.thread
......@@ -62,7 +62,7 @@ class GuardedProcessPool {
try {
var callback: (() -> Unit)? = null
while (guardThreads.get() === host) {
if (BuildConfig.DEBUG) Log.d(TAG, "start process: " + Commandline.toString(cmd))
Crashlytics.log(Log.DEBUG, TAG, "start process: " + Commandline.toString(cmd))
val startTime = SystemClock.elapsedRealtime()
process = ProcessBuilder(cmd)
......@@ -79,11 +79,11 @@ class GuardedProcessPool {
process.waitFor()
if (SystemClock.elapsedRealtime() - startTime < 1000) {
Log.w(TAG, "process exit too fast, stop guard: $cmdName")
Crashlytics.log(Log.WARN, TAG, "process exit too fast, stop guard: $cmdName")
}
}
} catch (_: InterruptedException) {
if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: $cmdName")
Crashlytics.log(Log.DEBUG, TAG, "thread interrupt, destroy process: $cmdName")
} catch (e: IOException) {
pushException(e)
} finally {
......
......@@ -23,14 +23,14 @@ package com.github.shadowsocks.bg
import android.net.LocalServerSocket
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.utils.printLog
import java.io.File
import java.io.IOException
abstract class LocalSocketListener(protected val tag: String) : Thread(tag) {
init {
setUncaughtExceptionHandler(app::track)
setUncaughtExceptionHandler { _, t -> printLog(t) }
}
protected abstract val socketFile: File
......@@ -48,15 +48,14 @@ abstract class LocalSocketListener(protected val tag: String) : Thread(tag) {
localSocket.bind(LocalSocketAddress(socketFile.absolutePath, LocalSocketAddress.Namespace.FILESYSTEM))
LocalServerSocket(localSocket.fileDescriptor)
} catch (e: IOException) {
Log.e(tag, "unable to bind", e)
printLog(e)
return
}
while (running) {
try {
serverSocket.accept()
} catch (e: IOException) {
Log.e(tag, "Error when accept socket", e)
app.track(e)
printLog(e)
null
}?.use(this::accept)
}
......
......@@ -21,8 +21,8 @@
package com.github.shadowsocks.bg
import android.net.LocalSocket
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.utils.printLog
import java.io.File
import java.io.IOException
import java.nio.ByteBuffer
......@@ -38,8 +38,7 @@ class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
} catch (e: IOException) {
Log.e(tag, "Error when recv traffic stat", e)
app.track(e)
printLog(e)
}
}
}
......@@ -28,7 +28,6 @@ import android.net.*
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.MainActivity
......@@ -38,6 +37,7 @@ import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.printLog
import com.github.shadowsocks.utils.systemService
import java.io.File
import java.io.FileDescriptor
......@@ -74,32 +74,21 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override val socketFile: File = File(app.deviceContext.filesDir, "protect_path")
override fun accept(socket: LocalSocket) {
var success = false
try {
socket.inputStream.read()
val fd = socket.ancillaryFileDescriptors!!.single()!!
val fdInt = getInt.invoke(fd) as Int
try {
socket.outputStream.write(if (try {
val network = underlyingNetwork
success = if (network != null && Build.VERSION.SDK_INT >= 23) {
if (network != null && Build.VERSION.SDK_INT >= 23) {
network.bindSocket(fd)
true
} else protect(fdInt)
} catch (e: Exception) {
Log.e(tag, "Error when protect socket", e)
app.track(e)
} finally {
JniHelper.close(fdInt) // Trick to close file decriptor
}
} catch (e: Exception) {
Log.e(tag, "Error when receiving ancillary fd", e)
app.track(e)
}
try {
socket.outputStream.write(if (success) 0 else 1)
}) 0 else 1)
} catch (e: IOException) {
Log.e(tag, "Error when returning result in protect", e)
app.track(e)
printLog(e)
}
}
}
......@@ -207,7 +196,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
if (profile.bypass) builder.addDisallowedApplication(it)
else builder.addAllowedApplication(it)
} catch (ex: PackageManager.NameNotFoundException) {
Log.e(tag, "Invalid package name", ex)
printLog(ex)
}
}
if (!profile.bypass) builder.addAllowedApplication(me)
......
......@@ -21,11 +21,11 @@
package com.github.shadowsocks.database
import android.database.sqlite.SQLiteCantOpenDatabaseException
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.ProfilesFragment
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.printLog
import java.io.IOException
import java.sql.SQLException
......@@ -67,8 +67,7 @@ object ProfileManager {
PrivateDatabase.profileDao[id]
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getProfile", ex)
app.track(ex)
printLog(ex)
null
}
......@@ -84,8 +83,7 @@ object ProfileManager {
PrivateDatabase.profileDao.isNotEmpty()
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "isNotEmpty", ex)
app.track(ex)
printLog(ex)
false
}
......@@ -94,8 +92,7 @@ object ProfileManager {
PrivateDatabase.profileDao.list()
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getAllProfiles", ex)
app.track(ex)
printLog(ex)
null
}
}
......@@ -21,6 +21,7 @@
package com.github.shadowsocks.plugin
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.utils.Commandline
import java.util.*
......@@ -43,7 +44,7 @@ class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val se
}
}
} catch (exc: Exception) {
Log.w("PluginConfiguration", exc.message)
Crashlytics.log(Log.WARN, "PluginConfiguration", exc.message)
}
opt
} else PluginOptions(line)
......
......@@ -30,6 +30,7 @@ import android.net.Uri
import android.os.Bundle
import android.util.Base64
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.utils.Commandline
import com.github.shadowsocks.utils.signaturesCompat
......@@ -143,7 +144,8 @@ object PluginManager {
initNativeFast(cr, options, uri)
} catch (t: Throwable) {
t.printStackTrace()
Log.w("PluginManager", "Initializing native plugin fast mode failed. Falling back to slow mode.")
Crashlytics.log(Log.WARN, "PluginManager",
"Initializing native plugin fast mode failed. Falling back to slow mode.")
initNativeSlow(cr, options, uri)
}
}
......
......@@ -14,6 +14,7 @@ import android.support.annotation.AttrRes
import android.support.v4.content.ContextCompat
import android.support.v7.util.SortedList
import android.util.TypedValue
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
import java.net.InetAddress
......@@ -40,7 +41,7 @@ fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver =
fun thread(name: String? = null, start: Boolean = true, isDaemon: Boolean = false,
contextClassLoader: ClassLoader? = null, priority: Int = -1, block: () -> Unit): Thread {
val thread = kotlin.concurrent.thread(false, isDaemon, contextClassLoader, name, priority, block)
thread.setUncaughtExceptionHandler(app::track)
thread.setUncaughtExceptionHandler { _, t -> printLog(t) }
if (start) thread.start()
return thread
}
......@@ -75,3 +76,9 @@ private class SortedListIterator<out T>(private val list: SortedList<T>) : Itera
override fun next(): T = if (hasNext()) list[count++] else throw NoSuchElementException()
}
fun <T> SortedList<T>.asIterable(): Iterable<T> = SortedListIterable(this)
fun printLog(t: Throwable) {
Crashlytics.logException(t)
t.printStackTrace()
}
fun track(thread: Thread, t: Throwable) = printLog(t)
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