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 ...@@ -33,7 +33,6 @@ import com.google.zxing.BarcodeFormat
import com.google.zxing.integration.android.IntentIntegrator import com.google.zxing.integration.android.IntentIntegrator
import com.google.zxing.integration.android.IntentResult import com.google.zxing.integration.android.IntentResult
import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.qrcode.QRCodeWriter
import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.R import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.RiskLevelAndKeyRetrievalBenchmark import de.rki.coronawarnapp.RiskLevelAndKeyRetrievalBenchmark
import de.rki.coronawarnapp.databinding.FragmentTestForAPIBinding import de.rki.coronawarnapp.databinding.FragmentTestForAPIBinding
...@@ -55,6 +54,7 @@ import de.rki.coronawarnapp.storage.LocalData ...@@ -55,6 +54,7 @@ import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository
import de.rki.coronawarnapp.transaction.RiskLevelTransaction import de.rki.coronawarnapp.transaction.RiskLevelTransaction
import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
import de.rki.coronawarnapp.util.CWADebug
import de.rki.coronawarnapp.util.KeyFileHelper import de.rki.coronawarnapp.util.KeyFileHelper
import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.AppInjector
import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.di.AutoInject
...@@ -298,9 +298,9 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -298,9 +298,9 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
false false
} }
binding.testLogfileToggle.isChecked = CoronaWarnApplication.fileLogger?.isLogging ?: false binding.testLogfileToggle.isChecked = CWADebug.fileLogger?.isLogging ?: false
binding.testLogfileToggle.setOnClickListener { buttonView -> binding.testLogfileToggle.setOnClickListener { buttonView ->
CoronaWarnApplication.fileLogger?.let { CWADebug.fileLogger?.let {
if (binding.testLogfileToggle.isChecked) { if (binding.testLogfileToggle.isChecked) {
it.start() it.start()
} else { } else {
...@@ -310,7 +310,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -310,7 +310,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
} }
binding.testLogfileShare.setOnClickListener { binding.testLogfileShare.setOnClickListener {
CoronaWarnApplication.fileLogger?.let { CWADebug.fileLogger?.let {
lifecycleScope.launch { lifecycleScope.launch {
val targetPath = withContext(Dispatchers.IO) { val targetPath = withContext(Dispatchers.IO) {
async { async {
......
...@@ -6,14 +6,11 @@ import android.app.Application ...@@ -6,14 +6,11 @@ import android.app.Application
import android.content.Context import android.content.Context
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.net.wifi.WifiManager
import android.os.Bundle import android.os.Bundle
import android.os.PowerManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
...@@ -23,187 +20,115 @@ import dagger.android.HasAndroidInjector ...@@ -23,187 +20,115 @@ import dagger.android.HasAndroidInjector
import de.rki.coronawarnapp.exception.reporting.ErrorReportReceiver import de.rki.coronawarnapp.exception.reporting.ErrorReportReceiver
import de.rki.coronawarnapp.exception.reporting.ReportingConstants.ERROR_REPORT_LOCAL_BROADCAST_CHANNEL import de.rki.coronawarnapp.exception.reporting.ReportingConstants.ERROR_REPORT_LOCAL_BROADCAST_CHANNEL
import de.rki.coronawarnapp.notification.NotificationHelper 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.CWADebug
import de.rki.coronawarnapp.util.ConnectivityHelper import de.rki.coronawarnapp.util.WatchdogService
import de.rki.coronawarnapp.util.debug.FileLogger
import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.AppInjector
import de.rki.coronawarnapp.util.di.ApplicationComponent import de.rki.coronawarnapp.util.di.ApplicationComponent
import de.rki.coronawarnapp.worker.BackgroundWorkHelper import de.rki.coronawarnapp.worker.BackgroundWorkHelper
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import kotlinx.coroutines.launch
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
import timber.log.Timber import timber.log.Timber
import java.security.Security import java.security.Security
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
class CoronaWarnApplication : Application(), LifecycleObserver, class CoronaWarnApplication : Application(), HasAndroidInjector {
Application.ActivityLifecycleCallbacks, HasAndroidInjector {
companion object { @Inject lateinit var component: ApplicationComponent
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
}
private lateinit var errorReceiver: ErrorReportReceiver @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any>
@Inject
lateinit var component: ApplicationComponent
@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector override fun androidInjector(): AndroidInjector<Any> = androidInjector
@Inject lateinit var watchdogService: WatchdogService
override fun onCreate() { override fun onCreate() {
instance = this instance = this
super.onCreate() 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() Timber.v("onCreate(): Initializing WorkManager")
.setMinimumLoggingLevel(android.util.Log.DEBUG) Configuration.Builder()
.build() .apply { setMinimumLoggingLevel(android.util.Log.DEBUG) }.build()
WorkManager.initialize(this, configuration) .let { WorkManager.initialize(this, it) }
NotificationHelper.createNotificationChannel() NotificationHelper.createNotificationChannel()
// Enable Conscrypt for TLS1.3 Support below API Level 29 // Enable Conscrypt for TLS1.3 Support below API Level 29
Security.insertProviderAt(Conscrypt.newProvider(), 1) Security.insertProviderAt(Conscrypt.newProvider(), 1)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
registerActivityLifecycleCallbacks(this)
if (BuildConfig.DEBUG) { ProcessLifecycleOwner.get().lifecycle.addObserver(foregroundStateUpdater)
Timber.plant(Timber.DebugTree()) registerActivityLifecycleCallbacks(activityLifecycleCallback)
}
if ((BuildConfig.FLAVOR == "deviceForTesters" || BuildConfig.DEBUG)) {
fileLogger = FileLogger(this)
}
// notification to test the WakeUpService from Google when the app // notification to test the WakeUpService from Google when the app was force stopped
// was force stopped
BackgroundWorkHelper.sendDebugNotification( BackgroundWorkHelper.sendDebugNotification(
"Application onCreate", "App was woken up" "Application onCreate", "App was woken up"
) )
// Only do this if the background jobs are enabled watchdogService.launch()
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 private val foregroundStateUpdater = object : LifecycleObserver {
// job execution @OnLifecycleEvent(Lifecycle.Event.ON_START)
val wakeLock = createWakeLock() fun onAppForegrounded() {
// we keep a wifi lock to wake up the wifi connection in case the device is dozing isAppInForeground = true
val wifiLock = createWifiLock() Timber.v("App is in the foreground")
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 @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
// in case the app was force stopped and woken up again by the Google WakeUpService fun onAppBackgrounded() {
if (LocalData.onboardingCompletedTimestamp() != null) BackgroundWorkScheduler.startWorkScheduler() isAppInForeground = false
Timber.v("App is in the background")
} }
} }
private fun createWakeLock(): PowerManager.WakeLock = private val activityLifecycleCallback = object : ActivityLifecycleCallbacks {
(getSystemService(Context.POWER_SERVICE) as PowerManager) private val localBM by lazy {
.run { LocalBroadcastManager.getInstance(this@CoronaWarnApplication)
newWakeLock( }
PowerManager.PARTIAL_WAKE_LOCK, private var errorReceiver: ErrorReportReceiver? = null
TAG + "-WAKE-" + UUID.randomUUID().toString()
).apply {
acquire(TEN_MINUTE_TIMEOUT_IN_MS)
}
}
private fun createWifiLock(): WifiManager.WifiLock = override fun onActivityPaused(activity: Activity) {
(getSystemService(Context.WIFI_SERVICE) as WifiManager) errorReceiver?.let {
.run { localBM.unregisterReceiver(it)
createWifiLock( errorReceiver = null
WifiManager.WIFI_MODE_FULL_HIGH_PERF,
TAG + "-WIFI-" + UUID.randomUUID().toString()
).apply {
acquire()
}
} }
}
/** override fun onActivityStarted(activity: Activity) {}
* Callback when the app is open but backgrounded
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
isAppInForeground = false
Timber.v("App backgrounded")
}
/** override fun onActivityDestroyed(activity: Activity) {}
* Callback when the app is foregrounded
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForegrounded() {
isAppInForeground = true
Timber.v("App foregrounded")
}
override fun onActivityPaused(activity: Activity) { override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
// unregisters error receiver
LocalBroadcastManager.getInstance(this).unregisterReceiver(errorReceiver)
}
override fun onActivityStarted(activity: Activity) { override fun onActivityStopped(activity: Activity) {}
// does not override function. Empty on intention
}
override fun onActivityDestroyed(activity: Activity) { @SuppressLint("SourceLockedOrientationActivity")
// does not override function. Empty on intention override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
} activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { override fun onActivityResumed(activity: Activity) {
// does not override function. Empty on intention errorReceiver?.let {
} localBM.unregisterReceiver(it)
errorReceiver = null
}
override fun onActivityStopped(activity: Activity) { errorReceiver = ErrorReportReceiver(activity).also {
// does not override function. Empty on intention localBM.registerReceiver(it, IntentFilter(ERROR_REPORT_LOCAL_BROADCAST_CHANNEL))
}
}
} }
@SuppressLint("SourceLockedOrientationActivity") companion object {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { private lateinit var instance: CoronaWarnApplication
// set screen orientation to portrait
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT /* 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) { fun getAppContext(): Context = instance.applicationContext
errorReceiver =
ErrorReportReceiver(activity)
LocalBroadcastManager.getInstance(this)
.registerReceiver(errorReceiver, IntentFilter(ERROR_REPORT_LOCAL_BROADCAST_CHANNEL))
} }
} }
package de.rki.coronawarnapp.util package de.rki.coronawarnapp.util
import android.app.Application
import de.rki.coronawarnapp.BuildConfig import de.rki.coronawarnapp.BuildConfig
import de.rki.coronawarnapp.util.debug.FileLogger
import timber.log.Timber
object CWADebug { 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 val isDebugBuildOrMode: Boolean
get() = BuildConfig.DEBUG || BuildConfig.BUILD_VARIANT == "deviceForTesters" 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