Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
File Logger Introduction for Test Builds (EXPOSUREAPP-2640) (#1176)
Browse files Browse the repository at this point in the history
* Align TestFragment Crash behavior with background transaction crash behavior.

* Add logging mechanism to debug hotfix issue.

* Lint Resolvement, Nav Graph Issue clean, Enable Log for deviceForTesters, Correct Quota Tests due to now lacking exception

Co-authored-by: Matthias Urhahn <[email protected]>
Co-authored-by: d067928 <[email protected]>
  • Loading branch information
3 people authored Sep 17, 2020
1 parent e95ff83 commit 90ad5d3
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,33 +49,9 @@ 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.KeyFileHelper
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_api_enter_other_keys
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_api_get_check_exposure
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_api_get_exposure_keys
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_api_scan_qr_code
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_api_share_my_keys
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_api_submit_keys
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_api_test_start
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_calculate_risk_level
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_clear_db
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_insert_exposure_summary
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_retrieve_exposure_summary
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_tracing_duration_in_retention_period
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.button_tracing_intervals
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.label_exposure_summary_attenuation
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.label_exposure_summary_daysSinceLastExposure
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.label_exposure_summary_matchedKeyCount
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.label_exposure_summary_maximumRiskScore
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.label_exposure_summary_summationRiskScore
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.label_googlePlayServices_version
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.label_latest_key_date
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.label_my_keys
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.qr_code_viewpager
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.test_api_switch_last_three_hours_from_server
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.test_api_switch_background_notifications
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.text_my_keys
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.text_scanned_key
import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.joda.time.DateTime
Expand Down Expand Up @@ -275,6 +251,51 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel
showToast(TimeVariables.getActiveTracingDaysInRetentionPeriod().toString())
}
}

binding.testLogfileToggle.isChecked = CoronaWarnApplication.fileLogger?.isLogging ?: false
binding.testLogfileToggle.setOnClickListener { buttonView ->
CoronaWarnApplication.fileLogger?.let {
if (binding.testLogfileToggle.isChecked) {
it.start()
} else {
it.stop()
}
}
}

binding.testLogfileShare.setOnClickListener {
CoronaWarnApplication.fileLogger?.let {
lifecycleScope.launch {
val targetPath = withContext(Dispatchers.IO) {
async {
if (!it.logFile.exists()) return@async null

val externalPath = File(
requireContext().getExternalFilesDir(null),
"LogFile-${System.currentTimeMillis()}.log"
)

it.logFile.copyTo(externalPath)

return@async externalPath
}
}.await()
if (targetPath != null) {
Toast.makeText(
requireActivity(),
"Logfile copied to $targetPath",
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
requireActivity(),
"No log file available",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}

override fun onResume() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
import de.rki.coronawarnapp.util.KeyFileHelper
import de.rki.coronawarnapp.util.security.SecurityHelper
import kotlinx.android.synthetic.deviceForTesters.fragment_test_risk_level_calculation.transmission_number
import kotlinx.android.synthetic.deviceForTesters.fragment_test_risk_level_calculation.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -159,7 +159,7 @@ class TestRiskLevelCalculation : Fragment() {
try {
RetrieveDiagnosisKeysTransaction.start()
calculateRiskLevel()
} catch (e: TransactionException) {
} catch (e: Exception) {
e.report(ExceptionCategory.INTERNAL)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<layout xmlns:tools="http://schemas.android.com/tools"
tools:ignore="HardcodedText"
xmlns:android="http://schemas.android.com/apk/res/android">

<data>

Expand All @@ -25,6 +27,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />


<Switch
android:id="@+id/test_api_switch_last_three_hours_from_server"
style="@style/body1"
Expand All @@ -41,6 +44,27 @@
android:text="@string/test_api_switch_background_notifications"
android:theme="@style/switchBase" />

<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">

<Switch
android:id="@+id/test_logfile_toggle"
style="@style/body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Logfile enabled"
android:theme="@style/switchBase" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/test_logfile_share"
android:text="Share log" />

</LinearLayout>

<TextView
android:id="@+id/label_exposure_summary"
style="@style/headline6"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import de.rki.coronawarnapp.notification.NotificationHelper
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction
import de.rki.coronawarnapp.util.ConnectivityHelper
import de.rki.coronawarnapp.util.debug.FileLogger
import de.rki.coronawarnapp.worker.BackgroundWorkHelper
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import kotlinx.coroutines.launch
Expand All @@ -49,6 +50,7 @@ class CoronaWarnApplication : Application(), LifecycleObserver,
instance.applicationContext

const val TEN_MINUTE_TIMEOUT_IN_MS = 10 * 60 * 1000L
var fileLogger: FileLogger? = null
}

private lateinit var errorReceiver: ErrorReportReceiver
Expand All @@ -71,6 +73,9 @@ class CoronaWarnApplication : Application(), LifecycleObserver,
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
if ((BuildConfig.FLAVOR == "deviceForTesters" || BuildConfig.DEBUG)) {
fileLogger = FileLogger(this)
}

// notification to test the WakeUpService from Google when the app
// was force stopped
Expand All @@ -82,27 +87,9 @@ class CoronaWarnApplication : Application(), LifecycleObserver,
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: 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)
}
}

val wakeLock = createWakeLock()
// we keep a wifi lock to wake up the wifi connection in case the device is dozing
val wifiLock: WifiManager.WifiLock =
(getSystemService(Context.WIFI_SERVICE) as WifiManager).run {
createWifiLock(
WifiManager.WIFI_MODE_FULL_HIGH_PERF,
TAG + "-WIFI-" + UUID.randomUUID().toString()
).apply {
acquire()
}
}

val wifiLock = createWifiLock()
try {
BackgroundWorkHelper.sendDebugNotification(
"Automatic mode is on", "Check if we have downloaded keys already today"
Expand All @@ -129,6 +116,28 @@ class CoronaWarnApplication : Application(), LifecycleObserver,
}
}

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 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()
}
}

/**
* Callback when the app is open but backgrounded
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.Duration
import org.joda.time.Instant
import timber.log.Timber

/**
* This Calculator class takes multiple parameters to check if the Google API
Expand All @@ -28,34 +29,58 @@ class GoogleQuotaCalculator(
override var hasExceededQuota: Boolean = false

override fun calculateQuota(): Boolean {
if (Instant.now().isAfter(LocalData.nextTimeRateLimitingUnlocks)) {
val oldQuota = LocalData.googleAPIProvideDiagnosisKeysCallCount
var currentQuota = oldQuota

val now = Instant.now()
val nextUnlock = LocalData.nextTimeRateLimitingUnlocks

Timber.v(
"calculateQuota() start! (currentQuota=%s, timeNow=%s, timeReset=%s)",
oldQuota, now, nextUnlock
)
if (now.isAfter(nextUnlock)) {
LocalData.nextTimeRateLimitingUnlocks = DateTime
.now(quotaTimeZone)
.withChronology(quotaChronology)
.plus(quotaResetPeriod)
.withTimeAtStartOfDay()
.toInstant()
LocalData.googleAPIProvideDiagnosisKeysCallCount = 0
Timber.d("calculateQuota() quota reset to 0.")
currentQuota = 0
} else {
Timber.v("calculateQuota() can't be reset yet.")
}

if (LocalData.googleAPIProvideDiagnosisKeysCallCount <= quotaLimit) {
LocalData.googleAPIProvideDiagnosisKeysCallCount += incrementByAmount
if (currentQuota <= quotaLimit) {
currentQuota += incrementByAmount
}

hasExceededQuota = LocalData.googleAPIProvideDiagnosisKeysCallCount > quotaLimit
if (currentQuota != oldQuota) {
LocalData.googleAPIProvideDiagnosisKeysCallCount = currentQuota
}

return hasExceededQuota
return (currentQuota > quotaLimit).also {
hasExceededQuota = it
Timber.v(
"calculateQuota() done! -> oldQuota=%d, currentQuotaHm=%d, quotaLimit=%d, EXCEEDED=%b",
oldQuota, currentQuota, quotaLimit, it
)
}
}

override fun resetProgressTowardsQuota(newProgress: Int) {
if (newProgress > quotaLimit) {
throw IllegalArgumentException("cannot reset progress to a value higher than the quota limit")
Timber.w("cannot reset progress to a value higher than the quota limit")
return
}
if (newProgress % incrementByAmount != 0) {
throw IllegalArgumentException("supplied progress is no multiple of $incrementByAmount")
Timber.e("supplied progress is no multiple of $incrementByAmount")
return
}
LocalData.googleAPIProvideDiagnosisKeysCallCount = newProgress
hasExceededQuota = false
Timber.d("resetProgressTowardsQuota(newProgress=%d) done", newProgress)
}

override fun getProgressTowardsQuota(): Int = LocalData.googleAPIProvideDiagnosisKeysCallCount
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package de.rki.coronawarnapp.util.debug

import android.content.Context
import timber.log.Timber
import java.io.File

class FileLogger constructor(private val context: Context) {

val logFile = File(context.cacheDir, "FileLoggerTree.log")
val triggerFile = File(context.filesDir, "FileLoggerTree.trigger")
private var loggerTree: FileLoggerTree? = null

val isLogging: Boolean
get() = loggerTree != null

init {
if (triggerFile.exists()) {
start()
}
}

fun start() {
if (loggerTree != null) return

loggerTree = FileLoggerTree(logFile).also {
Timber.plant(it)
it.start()
triggerFile.createNewFile()
}
}

fun stop() {
loggerTree?.let {
it.stop()
logFile.delete()
triggerFile.delete()
loggerTree = null
}
}
}
Loading

0 comments on commit 90ad5d3

Please sign in to comment.