Skip to content
Snippets Groups Projects
Unverified Commit 0d43121c authored by Matthias Urhahn's avatar Matthias Urhahn Committed by GitHub
Browse files

Fix various code smells and refactor our Application class (#1221)

* Fix various code smells and some refactoring to make SonarCloud happy.

* Fix refactoring regression.

* Additional log output to determine how long certain steps take on cold-start.
parent 96d94824
No related branches found
No related tags found
No related merge requests found
......@@ -33,7 +33,6 @@ import com.google.zxing.BarcodeFormat
import com.google.zxing.integration.android.IntentIntegrator
import com.google.zxing.integration.android.IntentResult
import com.google.zxing.qrcode.QRCodeWriter
import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.RiskLevelAndKeyRetrievalBenchmark
import de.rki.coronawarnapp.databinding.FragmentTestForAPIBinding
......@@ -55,6 +54,7 @@ import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository
import de.rki.coronawarnapp.transaction.RiskLevelTransaction
import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
import de.rki.coronawarnapp.util.CWADebug
import de.rki.coronawarnapp.util.KeyFileHelper
import de.rki.coronawarnapp.util.di.AppInjector
import de.rki.coronawarnapp.util.di.AutoInject
......@@ -298,9 +298,9 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
false
}
binding.testLogfileToggle.isChecked = CoronaWarnApplication.fileLogger?.isLogging ?: false
binding.testLogfileToggle.isChecked = CWADebug.fileLogger?.isLogging ?: false
binding.testLogfileToggle.setOnClickListener { buttonView ->
CoronaWarnApplication.fileLogger?.let {
CWADebug.fileLogger?.let {
if (binding.testLogfileToggle.isChecked) {
it.start()
} else {
......@@ -310,7 +310,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
}
binding.testLogfileShare.setOnClickListener {
CoronaWarnApplication.fileLogger?.let {
CWADebug.fileLogger?.let {
lifecycleScope.launch {
val targetPath = withContext(Dispatchers.IO) {
async {
......
......@@ -6,14 +6,11 @@ import android.app.Application
import android.content.Context
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.net.wifi.WifiManager
import android.os.Bundle
import android.os.PowerManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.work.Configuration
import androidx.work.WorkManager
......@@ -23,187 +20,115 @@ import dagger.android.HasAndroidInjector
import de.rki.coronawarnapp.exception.reporting.ErrorReportReceiver
import de.rki.coronawarnapp.exception.reporting.ReportingConstants.ERROR_REPORT_LOCAL_BROADCAST_CHANNEL
import de.rki.coronawarnapp.notification.NotificationHelper
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction
import de.rki.coronawarnapp.util.CWADebug
import de.rki.coronawarnapp.util.ConnectivityHelper
import de.rki.coronawarnapp.util.debug.FileLogger
import de.rki.coronawarnapp.util.WatchdogService
import de.rki.coronawarnapp.util.di.AppInjector
import de.rki.coronawarnapp.util.di.ApplicationComponent
import de.rki.coronawarnapp.worker.BackgroundWorkHelper
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import kotlinx.coroutines.launch
import org.conscrypt.Conscrypt
import timber.log.Timber
import java.security.Security
import java.util.UUID
import javax.inject.Inject
class CoronaWarnApplication : Application(), LifecycleObserver,
Application.ActivityLifecycleCallbacks, HasAndroidInjector {
class CoronaWarnApplication : Application(), HasAndroidInjector {
companion object {
val TAG: String? = CoronaWarnApplication::class.simpleName
private lateinit var instance: CoronaWarnApplication
/* describes if the app is in foreground
* Initialized to false, because app could also be started by a background job.
* For the cases where the app is started via the launcher icon, the onAppForegrounded
* event will be called, setting it to true
*/
var isAppInForeground = false
fun getAppContext(): Context =
instance.applicationContext
const val TEN_MINUTE_TIMEOUT_IN_MS = 10 * 60 * 1000L
var fileLogger: FileLogger? = null
}
@Inject lateinit var component: ApplicationComponent
private lateinit var errorReceiver: ErrorReportReceiver
@Inject
lateinit var component: ApplicationComponent
@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
@Inject lateinit var androidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector
@Inject lateinit var watchdogService: WatchdogService
override fun onCreate() {
instance = this
super.onCreate()
AppInjector.init(this)
CWADebug.init(this)
if (CWADebug.isDebugBuildOrMode) System.setProperty("kotlinx.coroutines.debug", "on")
Timber.v("onCreate(): Initializing Dagger")
AppInjector.init(this)
val configuration = Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.DEBUG)
.build()
WorkManager.initialize(this, configuration)
Timber.v("onCreate(): Initializing WorkManager")
Configuration.Builder()
.apply { setMinimumLoggingLevel(android.util.Log.DEBUG) }.build()
.let { WorkManager.initialize(this, it) }
NotificationHelper.createNotificationChannel()
// Enable Conscrypt for TLS1.3 Support below API Level 29
Security.insertProviderAt(Conscrypt.newProvider(), 1)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
registerActivityLifecycleCallbacks(this)
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
if ((BuildConfig.FLAVOR == "deviceForTesters" || BuildConfig.DEBUG)) {
fileLogger = FileLogger(this)
}
ProcessLifecycleOwner.get().lifecycle.addObserver(foregroundStateUpdater)
registerActivityLifecycleCallbacks(activityLifecycleCallback)
// notification to test the WakeUpService from Google when the app
// was force stopped
// notification to test the WakeUpService from Google when the app was force stopped
BackgroundWorkHelper.sendDebugNotification(
"Application onCreate", "App was woken up"
)
// Only do this if the background jobs are enabled
if (ConnectivityHelper.autoModeEnabled(applicationContext)) {
ProcessLifecycleOwner.get().lifecycleScope.launch {
// we want a wakelock as the OS does not handle this for us like in the background
// job execution
val wakeLock = createWakeLock()
// we keep a wifi lock to wake up the wifi connection in case the device is dozing
val wifiLock = createWifiLock()
try {
BackgroundWorkHelper.sendDebugNotification(
"Automatic mode is on", "Check if we have downloaded keys already today"
)
RetrieveDiagnosisKeysTransaction.startWithConstraints()
} catch (e: Exception) {
BackgroundWorkHelper.sendDebugNotification(
"RetrieveDiagnosisKeysTransaction failed",
(e.localizedMessage
?: "Unknown exception occurred in onCreate") + "\n\n" + (e.cause
?: "Cause is unknown").toString()
)
// retry the key retrieval in case of an error with a scheduled work
BackgroundWorkScheduler.scheduleDiagnosisKeyOneTimeWork()
}
if (wifiLock.isHeld) wifiLock.release()
if (wakeLock.isHeld) wakeLock.release()
}
watchdogService.launch()
}
private val foregroundStateUpdater = object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForegrounded() {
isAppInForeground = true
Timber.v("App is in the foreground")
}
// if the user is onboarded we will schedule period background jobs
// in case the app was force stopped and woken up again by the Google WakeUpService
if (LocalData.onboardingCompletedTimestamp() != null) BackgroundWorkScheduler.startWorkScheduler()
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
isAppInForeground = false
Timber.v("App is in the background")
}
}
private fun createWakeLock(): PowerManager.WakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager)
.run {
newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
TAG + "-WAKE-" + UUID.randomUUID().toString()
).apply {
acquire(TEN_MINUTE_TIMEOUT_IN_MS)
}
}
private val activityLifecycleCallback = object : ActivityLifecycleCallbacks {
private val localBM by lazy {
LocalBroadcastManager.getInstance(this@CoronaWarnApplication)
}
private var errorReceiver: ErrorReportReceiver? = null
private fun createWifiLock(): WifiManager.WifiLock =
(getSystemService(Context.WIFI_SERVICE) as WifiManager)
.run {
createWifiLock(
WifiManager.WIFI_MODE_FULL_HIGH_PERF,
TAG + "-WIFI-" + UUID.randomUUID().toString()
).apply {
acquire()
}
override fun onActivityPaused(activity: Activity) {
errorReceiver?.let {
localBM.unregisterReceiver(it)
errorReceiver = null
}
}
/**
* Callback when the app is open but backgrounded
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
isAppInForeground = false
Timber.v("App backgrounded")
}
override fun onActivityStarted(activity: Activity) {}
/**
* Callback when the app is foregrounded
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForegrounded() {
isAppInForeground = true
Timber.v("App foregrounded")
}
override fun onActivityDestroyed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {
// unregisters error receiver
LocalBroadcastManager.getInstance(this).unregisterReceiver(errorReceiver)
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityStarted(activity: Activity) {
// does not override function. Empty on intention
}
override fun onActivityStopped(activity: Activity) {}
override fun onActivityDestroyed(activity: Activity) {
// does not override function. Empty on intention
}
@SuppressLint("SourceLockedOrientationActivity")
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
// does not override function. Empty on intention
}
override fun onActivityResumed(activity: Activity) {
errorReceiver?.let {
localBM.unregisterReceiver(it)
errorReceiver = null
}
override fun onActivityStopped(activity: Activity) {
// does not override function. Empty on intention
errorReceiver = ErrorReportReceiver(activity).also {
localBM.registerReceiver(it, IntentFilter(ERROR_REPORT_LOCAL_BROADCAST_CHANNEL))
}
}
}
@SuppressLint("SourceLockedOrientationActivity")
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
// set screen orientation to portrait
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
}
companion object {
private lateinit var instance: CoronaWarnApplication
/* describes if the app is in foreground
* Initialized to false, because app could also be started by a background job.
* For the cases where the app is started via the launcher icon, the onAppForegrounded
* event will be called, setting it to true
*/
var isAppInForeground = false
override fun onActivityResumed(activity: Activity) {
errorReceiver =
ErrorReportReceiver(activity)
LocalBroadcastManager.getInstance(this)
.registerReceiver(errorReceiver, IntentFilter(ERROR_REPORT_LOCAL_BROADCAST_CHANNEL))
fun getAppContext(): Context = instance.applicationContext
}
}
package de.rki.coronawarnapp.util
import android.app.Application
import de.rki.coronawarnapp.BuildConfig
import de.rki.coronawarnapp.util.debug.FileLogger
import timber.log.Timber
object CWADebug {
var fileLogger: FileLogger? = null
fun init(application: Application) {
if (isDebugBuildOrMode) System.setProperty("kotlinx.coroutines.debug", "on")
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
if ((BuildConfig.FLAVOR == "deviceForTesters" || BuildConfig.DEBUG)) {
fileLogger = FileLogger(application)
}
}
val isDebugBuildOrMode: Boolean
get() = BuildConfig.DEBUG || BuildConfig.BUILD_VARIANT == "deviceForTesters"
}
package de.rki.coronawarnapp.util
import android.content.Context
import android.net.wifi.WifiManager
import android.os.PowerManager
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction
import de.rki.coronawarnapp.worker.BackgroundWorkHelper
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class WatchdogService @Inject constructor(private val context: Context) {
private val powerManager by lazy {
context.getSystemService(Context.POWER_SERVICE) as PowerManager
}
private val wifiManager by lazy {
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
}
fun launch() {
// Only do this if the background jobs are enabled
if (!ConnectivityHelper.autoModeEnabled(context)) {
Timber.d("Background jobs are not enabled, aborting.")
return
}
Timber.v("Acquiring wakelocks for watchdog routine.")
ProcessLifecycleOwner.get().lifecycleScope.launch {
// A wakelock as the OS does not handle this for us like in the background job execution
val wakeLock = createWakeLock()
// A wifi lock to wake up the wifi connection in case the device is dozing
val wifiLock = createWifiLock()
try {
BackgroundWorkHelper.sendDebugNotification(
"Automatic mode is on", "Check if we have downloaded keys already today"
)
RetrieveDiagnosisKeysTransaction.startWithConstraints()
} catch (e: Exception) {
BackgroundWorkHelper.sendDebugNotification(
"RetrieveDiagnosisKeysTransaction failed",
(e.localizedMessage
?: "Unknown exception occurred in onCreate") + "\n\n" + (e.cause
?: "Cause is unknown").toString()
)
// retry the key retrieval in case of an error with a scheduled work
BackgroundWorkScheduler.scheduleDiagnosisKeyOneTimeWork()
}
if (wifiLock.isHeld) wifiLock.release()
if (wakeLock.isHeld) wakeLock.release()
}
// if the user is onboarded we will schedule period background jobs
// in case the app was force stopped and woken up again by the Google WakeUpService
if (LocalData.onboardingCompletedTimestamp() != null) BackgroundWorkScheduler.startWorkScheduler()
}
private fun createWakeLock(): PowerManager.WakeLock = powerManager
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CWA-WAKE-${UUID.randomUUID()}")
.apply { acquire(TEN_MINUTE_TIMEOUT_IN_MS) }
private fun createWifiLock(): WifiManager.WifiLock = wifiManager
.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "CWA-WIFI-${UUID.randomUUID()}")
.apply { acquire() }
companion object {
private const val TEN_MINUTE_TIMEOUT_IN_MS = 10 * 60 * 1000L
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment