SmokeScreen.kt 10.4 KB
Newer Older
1
2
3
package com.frostnerd.smokescreen

import android.app.Application
4
5
6
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
7
import android.content.Intent
8
import androidx.core.app.NotificationCompat
9
import com.frostnerd.smokescreen.activity.ErrorDialogActivity
10
import com.frostnerd.smokescreen.activity.LoggingDialogActivity
11
import com.frostnerd.smokescreen.activity.PinActivity
12
import com.frostnerd.smokescreen.database.AppDatabase
13
import com.frostnerd.smokescreen.util.Notifications
14
import com.frostnerd.smokescreen.util.RequestCodes
Daniel Wolf's avatar
Daniel Wolf committed
15
import com.frostnerd.smokescreen.util.crashhelpers.DataSavingSentryEventProcessor
Daniel Wolf's avatar
Daniel Wolf committed
16
import com.frostnerd.smokescreen.util.preferences.AppSettings
17
import com.frostnerd.smokescreen.util.preferences.Crashreporting
Daniel Wolf's avatar
Daniel Wolf committed
18
19
20
21
22
23
import io.sentry.android.core.*
import io.sentry.core.Integration
import io.sentry.core.Sentry
import io.sentry.core.SentryOptions
import io.sentry.core.UncaughtExceptionHandlerIntegration
import io.sentry.core.protocol.User
24
25
26
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
Daniel Wolf's avatar
Daniel Wolf committed
27
import leakcanary.LeakSentry
28
import java.net.InetAddress
29
import java.util.*
30
31
import kotlin.system.exitProcess

Daniel Wolf's avatar
Daniel Wolf committed
32
33
/*
 * Copyright (C) 2019 Daniel Wolf (Ch4t4r)
34
 *
Daniel Wolf's avatar
Daniel Wolf committed
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You can contact the developer at daniel.wolf@frostnerd.com.
49
50
 */
class SmokeScreen : Application() {
51
52
53
54
    companion object {
        var sentryReady:Boolean = false
            private set
    }
55
    private var defaultUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
56
    val customUncaughtExceptionHandler = EnrichableUncaughtExceptionHandler()
57
    private fun showCrashNotification() {
58
        val notification =
59
            NotificationCompat.Builder(this, Notifications.getHighPriorityChannelId(this))
60
61
62
                .setSmallIcon(R.drawable.ic_cloud_warn)
                .setOngoing(false)
                .setAutoCancel(true)
63
                .setPriority(NotificationCompat.PRIORITY_HIGH)
64
65
                .setContentIntent(
                    PendingIntent.getActivity(
66
                        this, RequestCodes.CRASH_NOTIFICATION,
67
                        PinActivity.openAppIntent(this), PendingIntent.FLAG_UPDATE_CURRENT
68
                    )
69
                )
70
                .setContentTitle(getString(R.string.notification_appcrash_title))
71
72
73
        if (getPreferences().loggingEnabled) {
            val pendingIntent = PendingIntent.getActivity(
                this,
74
                RequestCodes.CRASH_NOTIFICATION_SEND_LOGS,
75
76
77
78
                Intent(
                    this,
                    LoggingDialogActivity::class.java
                ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
79
80
                PendingIntent.FLAG_CANCEL_CURRENT
            )
81
82
83
84
85
            notification.addAction(
                R.drawable.ic_share,
                getString(R.string.title_send_logs),
                pendingIntent
            )
86
87
        }

88
89
        notification.setStyle(NotificationCompat.BigTextStyle(notification).bigText(getString(R.string.notification_appcrash_message)))
        (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(
Daniel Wolf's avatar
Daniel Wolf committed
90
            Notifications.ID_APP_CRASH,
91
92
93
94
            notification.build()
        )
    }

95
    override fun onCreate() {
96
        if(!BuildConfig.LEAK_DETECTION) LeakSentry.config = LeakSentry.config.copy(enabled = false)
97
        initSentry()
98
99
100
        defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
        Thread.setDefaultUncaughtExceptionHandler(customUncaughtExceptionHandler)
        super.onCreate()
101
        log("Application created.")
102
        LeakSentry.watchIfEnabled(this)
103
104
    }

105
106
107
108
109
    fun closeSentry() {
        Sentry.close()
        sentryReady = false
    }

110
    fun initSentry(forceStatus: Status = Status.NONE) {
Daniel Wolf's avatar
Daniel Wolf committed
111
        if (!BuildConfig.DEBUG && BuildConfig.SENTRY_DSN != "dummy") {
Daniel Wolf's avatar
Daniel Wolf committed
112
            GlobalScope.launch(Dispatchers.IO) {
113
114
115
116
117
118
119
120
121
                sentryReady = false
                try {
                    val hostName = InetAddress.getLocalHost().hostName
                    if(!hostName.startsWith("mars-sandbox", true)) {
                        val enabledType = getPreferences().crashreportingType
                        if (forceStatus != Status.DATASAVING && (enabledType == Crashreporting.FULL || forceStatus == Status.ENABLED)) {
                            // Enable Sentry in full mode
                            // This passes some device-related data, but nothing which allows user actions to be tracked across the app
                            // Info: Some data is attached by the AndroidEventBuilderHelper class, which is present by default
Daniel Wolf's avatar
Daniel Wolf committed
122

Daniel Wolf's avatar
Daniel Wolf committed
123
124
                            SentryAndroid.init(this@SmokeScreen) {
                                it.dsn = BuildConfig.SENTRY_DSN
125
                            }
Daniel Wolf's avatar
Daniel Wolf committed
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
                            Sentry.setUser(User().apply {
                                this.username = getPreferences().crashReportingUUID
                            })
                            Sentry.setTag("user.language", Locale.getDefault().displayLanguage)
                            Sentry.setTag(
                                "app.database_version",
                                AppDatabase.currentVersion.toString()
                            )
                            Sentry.setTag(
                                "app.dns_server_name",
                                getPreferences().dnsServerConfig.name
                            )
                            Sentry.setTag(
                                "app.dns_server_primary",
                                getPreferences().dnsServerConfig.servers[0].address.formatToString()
                            )
                            Sentry.setTag(
                                "app.dns_server_secondary",
                                getPreferences().dnsServerConfig.servers.getOrNull(1)?.address?.formatToString()
                                    ?: ""
                            )
                            Sentry.setTag(
                                "app.installer_package",
                                packageManager.getInstallerPackageName(packageName) ?: ""
                            )
                            Sentry.setTag("richdata", "true")
                            Sentry.setTag("app.fromCi", BuildConfig.FROM_CI.toString())
                            Sentry.setTag("app.commit", BuildConfig.COMMIT_HASH)
154
155
156
157
158
                            sentryReady = true
                        } else if (enabledType == Crashreporting.MINIMAL || forceStatus == Status.DATASAVING) {
                            // Inits Sentry in datasaving mode
                            // Only data absolutely necessary is transmitted (Android version, app version).
                            // Only crashes will be reported, no regular events.
Daniel Wolf's avatar
Daniel Wolf committed
159
160
161
                            SentryAndroid.init(this@SmokeScreen) {
                                it.dsn = BuildConfig.SENTRY_DSN
                                setupSentryForDatasaving(it)
162
                            }
Daniel Wolf's avatar
Daniel Wolf committed
163
164
165
166
167
168
169
                            Sentry.setUser(User().apply {
                                this.username = "anon-" + BuildConfig.VERSION_CODE
                            })
                            Sentry.setTag("richdata", "false")
                            Sentry.setTag("dist", BuildConfig.VERSION_CODE.toString())
                            Sentry.setTag("app.commit", BuildConfig.COMMIT_HASH)
                            Sentry.setTag("app.fromCi", BuildConfig.FROM_CI.toString())
170
                            sentryReady = true
171
172
                        }
                    }
173
174
                } catch(ex:Throwable) {
                    ex.printStackTrace()
175
                }
176
            }
177
        }
178
179
    }

Daniel Wolf's avatar
Daniel Wolf committed
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
    private fun setupSentryForDatasaving(sentryOptions: SentryOptions) {
        val remove = mutableListOf<Integration>()
        sentryOptions.integrations.forEach {
            if (it is PhoneStateBreadcrumbsIntegration ||
                it is SystemEventsBreadcrumbsIntegration ||
                it is TempSensorBreadcrumbsIntegration ||
                it is AppComponentsBreadcrumbsIntegration ||
                it is SystemEventsBreadcrumbsIntegration ||
                it is AppLifecycleIntegration
            ) remove.add(it)
        }
        remove.forEach { sentryOptions.integrations.remove(it) }
        sentryOptions.eventProcessors.add(DataSavingSentryEventProcessor())
    }

195
196
197
198
199
200
201
202
203
    override fun onLowMemory() {
        super.onLowMemory()
        log("The system seems to have low memory")
    }

    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        log("Memory has been trimmed with level $level")
    }
204

205
    inner class EnrichableUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
206
207
208
209
210
211
        private val extras = mutableMapOf<String, String>()

        override fun uncaughtException(t: Thread, e: Throwable) {
            e.printStackTrace()
            log(e, extras)
            extras.clear()
Daniel Wolf's avatar
Daniel Wolf committed
212
            val isPrerelease = !AppSettings.isReleaseVersion
213
            if (isPrerelease && getPreferences().loggingEnabled && getPreferences().crashreportingType == Crashreporting.OFF) {
214
215
216
217
218
219
                startActivity(
                    Intent(
                        this@SmokeScreen,
                        ErrorDialogActivity::class.java
                    ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                )
220
            } else if (isPrerelease && getPreferences().crashreportingType != Crashreporting.OFF) {
221
222
223
224
225
226
227
                showCrashNotification()
            }
            closeLogger()
            defaultUncaughtExceptionHandler?.uncaughtException(t, e)
            exitProcess(0)
        }

228
        fun addExtra(key: String, value: String) {
229
230
231
232
            extras[key] = value
        }

    }
233
234
235
236
}

enum class Status {
    ENABLED, DATASAVING, NONE
237
}