diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 098fe846891336dbe9c4465f59ebbba03506e4f1..0000000000000000000000000000000000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,136 +0,0 @@ -<component name="ProjectCodeStyleConfiguration"> - <code_scheme name="Project" version="173"> - <JetCodeStyleSettings> - <option name="PACKAGES_TO_USE_STAR_IMPORTS"> - <value> - <package name="kotlinx.android.synthetic" withSubpackages="true" static="false" /> - </value> - </option> - <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="99" /> - <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="99" /> - <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> - </JetCodeStyleSettings> - <codeStyleSettings language="XML"> - <indentOptions> - <option name="CONTINUATION_INDENT_SIZE" value="4" /> - </indentOptions> - <arrangement> - <rules> - <section> - <rule> - <match> - <AND> - <NAME>xmlns:android</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>xmlns:.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*:id</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*:name</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>name</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>style</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> - </AND> - </match> - <order>ANDROID_ATTRIBUTE_ORDER</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>.*</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - </rules> - </arrangement> - </codeStyleSettings> - <codeStyleSettings language="kotlin"> - <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> - <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" /> - <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> - <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" /> - <option name="FIELD_ANNOTATION_WRAP" value="1" /> - <indentOptions> - <option name="CONTINUATION_INDENT_SIZE" value="4" /> - </indentOptions> - </codeStyleSettings> - </code_scheme> -</component> \ No newline at end of file diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 33df807a140424a015990b7fca42410ee8d0bfb4..fabe098fea718602e1ef9b3473034211b41e0181 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -31,6 +31,17 @@ def environmentExtractor = { File path -> return "\"${escapedJson}\"" } +def getHash = { -> + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'rev-parse', '--short', 'HEAD' + standardOutput = stdout + } + def result = stdout.toString().trim() + return result +} + + android { println("Current VERSION_MAJOR: ${VERSION_MAJOR}") println("Current VERSION_MINOR: ${VERSION_MINOR}") @@ -64,6 +75,7 @@ android { def prodEnvJson = environmentExtractor(file("../prod_environments.json")) buildConfigField "String", "ENVIRONMENT_JSONDATA", prodEnvJson + buildConfigField "String", "GIT_COMMIT_SHORT_HASH", "\"${getHash()}\"" def devEnvironmentFile = file("../test_environments.json") if (devEnvironmentFile.exists()) { diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.bugreporting.storage.BugDatabase/1.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.bugreporting.storage.BugDatabase/1.json new file mode 100644 index 0000000000000000000000000000000000000000..2ce12fa899dae6730452501187001e24c798eb79 --- /dev/null +++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.bugreporting.storage.BugDatabase/1.json @@ -0,0 +1,112 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "0e8995814e331e0253eb0276f2e946a7", + "entities": [ + { + "tableName": "BugEventEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `tag` TEXT, `info` TEXT, `exceptionClass` TEXT NOT NULL, `exceptionMessage` TEXT, `stackTrace` TEXT NOT NULL, `deviceInfo` TEXT NOT NULL, `appVersionName` TEXT NOT NULL, `appVersionCode` INTEGER NOT NULL, `apiLevel` INTEGER NOT NULL, `androidVersion` TEXT NOT NULL, `shortCommitHash` TEXT NOT NULL, `logHistory` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "exceptionClass", + "columnName": "exceptionClass", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "exceptionMessage", + "columnName": "exceptionMessage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "stackTrace", + "columnName": "stackTrace", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deviceInfo", + "columnName": "deviceInfo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appVersionName", + "columnName": "appVersionName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appVersionCode", + "columnName": "appVersionCode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "apiLevel", + "columnName": "apiLevel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "androidVersion", + "columnName": "androidVersion", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortCommitHash", + "columnName": "shortCommitHash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "logHistory", + "columnName": "logHistory", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0e8995814e331e0253eb0276f2e946a7')" + ] + } +} \ No newline at end of file diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.storage.AppDatabase/2.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.storage.AppDatabase/2.json new file mode 100644 index 0000000000000000000000000000000000000000..a53d24a6c224fdae463d0afa90b6665929f5a580 --- /dev/null +++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.storage.AppDatabase/2.json @@ -0,0 +1,231 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "afee4b1ca6ab9abd7117fe308784bc99", + "entities": [ + { + "tableName": "exposure_summary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `daysSinceLastExposure` INTEGER NOT NULL, `matchedKeyCount` INTEGER NOT NULL, `maximumRiskScore` INTEGER NOT NULL, `summationRiskScore` INTEGER NOT NULL, `attenuationDurationsInMinutes` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "daysSinceLastExposure", + "columnName": "daysSinceLastExposure", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "matchedKeyCount", + "columnName": "matchedKeyCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "maximumRiskScore", + "columnName": "maximumRiskScore", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "summationRiskScore", + "columnName": "summationRiskScore", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attenuationDurationsInMinutes", + "columnName": "attenuationDurationsInMinutes", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_exposure_summary_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_exposure_summary_id` ON `${TABLE_NAME}` (`id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "date", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` TEXT NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_date_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_date_id` ON `${TABLE_NAME}` (`id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "tracing_interval", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`from` INTEGER NOT NULL, `to` INTEGER NOT NULL, PRIMARY KEY(`from`, `to`))", + "fields": [ + { + "fieldPath": "from", + "columnName": "from", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "to", + "columnName": "to", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "from", + "to" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_tracing_interval_from_to", + "unique": false, + "columnNames": [ + "from", + "to" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tracing_interval_from_to` ON `${TABLE_NAME}` (`from`, `to`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "CrashReportEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `deviceInfo` TEXT NOT NULL, `appVersionName` TEXT NOT NULL, `appVersionCode` INTEGER NOT NULL, `apiLevel` INTEGER NOT NULL, `androidVersion` TEXT NOT NULL, `shortID` TEXT NOT NULL, `message` TEXT NOT NULL, `stackTrace` TEXT NOT NULL, `tag` TEXT, `crashedAt` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceInfo", + "columnName": "deviceInfo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appVersionName", + "columnName": "appVersionName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appVersionCode", + "columnName": "appVersionCode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "apiLevel", + "columnName": "apiLevel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "androidVersion", + "columnName": "androidVersion", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortID", + "columnName": "shortID", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stackTrace", + "columnName": "stackTrace", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "crashedAt", + "columnName": "crashedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'afee4b1ca6ab9abd7117fe308784bc99')" + ] + } +} \ No newline at end of file diff --git a/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/bugreporting/BugReportingModule.kt b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/bugreporting/BugReportingModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..769b9e9e78558992d255b9eaaaec8358041761ae --- /dev/null +++ b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/bugreporting/BugReportingModule.kt @@ -0,0 +1,28 @@ +package de.rki.coronawarnapp.bugreporting + +import dagger.Module +import dagger.Provides +import de.rki.coronawarnapp.bugreporting.loghistory.LogHistoryTree +import timber.log.Timber +import javax.inject.Singleton + +@Module +class BugReportingModule { + + @Singleton + @Provides + fun reporter(): BugReporter = object : BugReporter { + override fun report(throwable: Throwable, tag: String?, info: String?) { + // NOOP + } + } + + @Singleton + @LogHistoryTree + @Provides + fun loggingHistory(): Timber.Tree = object : Timber.Tree() { + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + // NOOP + } + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/BugReportingModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/BugReportingModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..90bdfde710ad6101dc80a6af94421b954eb19997 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/BugReportingModule.kt @@ -0,0 +1,41 @@ +package de.rki.coronawarnapp.bugreporting + +import dagger.Module +import dagger.Provides +import de.rki.coronawarnapp.bugreporting.loghistory.LogHistoryTree +import de.rki.coronawarnapp.bugreporting.loghistory.RollingLogHistory +import de.rki.coronawarnapp.bugreporting.processor.BugProcessor +import de.rki.coronawarnapp.bugreporting.processor.DefaultBugProcessor +import de.rki.coronawarnapp.bugreporting.reporter.DefaultBugReporter +import de.rki.coronawarnapp.bugreporting.storage.BugDatabase +import de.rki.coronawarnapp.bugreporting.storage.dao.DefaultBugDao +import de.rki.coronawarnapp.bugreporting.storage.repository.BugRepository +import de.rki.coronawarnapp.bugreporting.storage.repository.DefaultBugRepository +import timber.log.Timber +import javax.inject.Singleton + +@Module +class BugReportingModule { + + @Singleton + @Provides + fun reporter(reporter: DefaultBugReporter): BugReporter = reporter + + @Singleton + @Provides + fun repository(repository: DefaultBugRepository): BugRepository = repository + + @Singleton + @Provides + fun processor(processor: DefaultBugProcessor): BugProcessor = processor + + @Singleton + @LogHistoryTree + @Provides + fun loggingHistory(loggingHistory: RollingLogHistory): Timber.Tree = loggingHistory + + @Singleton + @Provides + fun bugEventDao(bugDatabaseFactory: BugDatabase.Factory): DefaultBugDao = + bugDatabaseFactory.create().defaultBugDao() +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/BugEvent.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/BugEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..80603278fe0a59f7043f69e1bb41f695bb0f0429 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/BugEvent.kt @@ -0,0 +1,21 @@ +package de.rki.coronawarnapp.bugreporting.event + +import org.joda.time.Instant +import java.util.UUID + +interface BugEvent { + val id: UUID + val createdAt: Instant + val tag: String? + val info: String? + val exceptionClass: String + val exceptionMessage: String? + val stackTrace: String + val appVersionName: String + val appVersionCode: Long + val deviceInfo: String + val apiLevel: Int + val androidVersion: String + val shortCommitHash: String + val logHistory: List<String> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/BugEventEntity.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/BugEventEntity.kt new file mode 100644 index 0000000000000000000000000000000000000000..5585cac6ed7d761a29d4d9ed71f1e33a100657b3 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/BugEventEntity.kt @@ -0,0 +1,24 @@ +package de.rki.coronawarnapp.bugreporting.event + +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.joda.time.Instant +import java.util.UUID + +@Entity +data class BugEventEntity( + @PrimaryKey override var id: UUID = UUID.randomUUID(), + override val createdAt: Instant = Instant.now(), + override var tag: String? = null, + override val info: String? = null, + override val exceptionClass: String, + override val exceptionMessage: String? = null, + override val stackTrace: String, + override val deviceInfo: String, + override val appVersionName: String, + override val appVersionCode: Long, + override val apiLevel: Int, + override val androidVersion: String, + override val shortCommitHash: String, + override val logHistory: List<String> +) : BugEvent diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/DefaultBugEvent.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/DefaultBugEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..d9a0d010623fd30a392927ec89da4a4b6094c06f --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/event/DefaultBugEvent.kt @@ -0,0 +1,21 @@ +package de.rki.coronawarnapp.bugreporting.event + +import org.joda.time.Instant +import java.util.UUID + +class DefaultBugEvent( + override val id: UUID = UUID.randomUUID(), + override val createdAt: Instant, + override val tag: String?, + override val info: String?, + override val exceptionClass: String, + override val exceptionMessage: String?, + override val stackTrace: String, + override val deviceInfo: String, + override val appVersionName: String, + override val appVersionCode: Long, + override val apiLevel: Int, + override val androidVersion: String, + override val shortCommitHash: String, + override val logHistory: List<String> +) : BugEvent diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/loghistory/RollingLogHistory.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/loghistory/RollingLogHistory.kt new file mode 100644 index 0000000000000000000000000000000000000000..97696cca28e4abd2f57f2d7cdf30c8bf17d8343e --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/loghistory/RollingLogHistory.kt @@ -0,0 +1,64 @@ +package de.rki.coronawarnapp.bugreporting.loghistory + +import android.util.Log +import de.rki.coronawarnapp.util.coroutine.AppScope +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.plus +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.math.min + +@Singleton +class RollingLogHistory @Inject constructor( + @AppScope private val scope: CoroutineScope, + dispatcherProvider: DispatcherProvider +) : Timber.DebugTree() { + + private val bufferLock = Mutex() + private val buffer: ArrayDeque<String> = ArrayDeque(BUFFER_SIZE) + private val logQueue = MutableStateFlow("") + + init { + logQueue + .filter { it.isNotBlank() } + .onEach { + bufferLock.withLock { + buffer.addFirst(it) + if (buffer.size >= BUFFER_SIZE) { + buffer.removeLast() + } + } + } + .launchIn(scope + dispatcherProvider.IO) + } + + suspend fun getLoglines(count: Int): List<String> = bufferLock.withLock { + buffer.subList(0, min(count, buffer.size)) + } + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + val formatLine = + "${System.currentTimeMillis()} ${priorityToString(priority)}/$tag: $message\n" + logQueue.value = formatLine + } + + companion object { + private const val BUFFER_SIZE = 100 + private fun priorityToString(priority: Int): String = when (priority) { + Log.ERROR -> "E" + Log.WARN -> "W" + Log.INFO -> "I" + Log.DEBUG -> "D" + Log.VERBOSE -> "V" + else -> priority.toString() + } + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/processor/BugProcessor.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/processor/BugProcessor.kt new file mode 100644 index 0000000000000000000000000000000000000000..e9365038253e5d66611358ff6d241216d9b11fe5 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/processor/BugProcessor.kt @@ -0,0 +1,7 @@ +package de.rki.coronawarnapp.bugreporting.processor + +import de.rki.coronawarnapp.bugreporting.event.BugEvent + +interface BugProcessor { + suspend fun processor(throwable: Throwable, tag: String?, info: String?): BugEvent +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/processor/DefaultBugProcessor.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/processor/DefaultBugProcessor.kt new file mode 100644 index 0000000000000000000000000000000000000000..12c569990d6ca9a63ebccfaa7651240997557846 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/processor/DefaultBugProcessor.kt @@ -0,0 +1,52 @@ +package de.rki.coronawarnapp.bugreporting.processor + +import android.content.Context +import android.os.Build +import android.util.Log +import de.rki.coronawarnapp.BuildConfig +import de.rki.coronawarnapp.bugreporting.event.BugEvent +import de.rki.coronawarnapp.bugreporting.event.DefaultBugEvent +import de.rki.coronawarnapp.bugreporting.loghistory.RollingLogHistory +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.tryFormattedError +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class DefaultBugProcessor @Inject constructor( + @AppContext private val context: Context, + private val timeStamper: TimeStamper, + private val rollingLogHistory: RollingLogHistory +) : BugProcessor { + + override suspend fun processor(throwable: Throwable, tag: String?, info: String?): BugEvent { + val crashedAt = timeStamper.nowUTC + val exceptionMessage = throwable.tryFormattedError(context) + val exceptionClass = throwable::class.java.simpleName + val stacktrace = Log.getStackTraceString(throwable) + val deviceInfo = "${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})" + val appVersionName = BuildConfig.VERSION_NAME + val appVersionCode = BuildConfig.VERSION_CODE + val apiLevel = Build.VERSION.SDK_INT + val androidVersion = Build.VERSION.RELEASE + val shortID = BuildConfig.GIT_COMMIT_SHORT_HASH + val logHistory = rollingLogHistory.getLoglines(50) + + return DefaultBugEvent( + createdAt = crashedAt, + tag = tag, + info = info, + exceptionClass = exceptionClass, + exceptionMessage = exceptionMessage, + stackTrace = stacktrace, + deviceInfo = deviceInfo, + appVersionName = appVersionName, + appVersionCode = appVersionCode.toLong(), + apiLevel = apiLevel, + androidVersion = androidVersion, + shortCommitHash = shortID, + logHistory = logHistory + ) + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/reporter/DefaultBugReporter.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/reporter/DefaultBugReporter.kt new file mode 100644 index 0000000000000000000000000000000000000000..50bf3b51d4bdd295fc4b0d231ddbf9b0d86d5a86 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/reporter/DefaultBugReporter.kt @@ -0,0 +1,29 @@ +package de.rki.coronawarnapp.bugreporting.reporter + +import de.rki.coronawarnapp.bugreporting.BugReporter +import de.rki.coronawarnapp.bugreporting.processor.BugProcessor +import de.rki.coronawarnapp.bugreporting.storage.repository.BugRepository +import de.rki.coronawarnapp.util.coroutine.AppScope +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class DefaultBugReporter @Inject constructor( + private val repository: BugRepository, + private val processor: BugProcessor, + @AppScope private val scope: CoroutineScope, + private val dispatcherProvider: DispatcherProvider +) : BugReporter { + + override fun report(throwable: Throwable, tag: String?, info: String?) { + Timber.e(throwable, "Processing reported bug (info=$info) from $tag.") + scope.launch(context = dispatcherProvider.IO) { + val event = processor.processor(throwable, tag, info) + repository.save(event) + } + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/BugDatabase.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/BugDatabase.kt new file mode 100644 index 0000000000000000000000000000000000000000..ab7bd2db9206c736664b68352a1e5b7d2f64561b --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/BugDatabase.kt @@ -0,0 +1,33 @@ +package de.rki.coronawarnapp.bugreporting.storage + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import de.rki.coronawarnapp.bugreporting.event.BugEventEntity +import de.rki.coronawarnapp.bugreporting.storage.dao.DefaultBugDao +import de.rki.coronawarnapp.util.database.CommonConverters +import de.rki.coronawarnapp.util.di.AppContext +import javax.inject.Inject + +@Database( + entities = [BugEventEntity::class], + version = 1, + exportSchema = true +) +@TypeConverters(CommonConverters::class) +abstract class BugDatabase : RoomDatabase() { + + abstract fun defaultBugDao(): DefaultBugDao + + class Factory @Inject constructor(@AppContext private val context: Context) { + fun create(): BugDatabase = Room + .databaseBuilder(context, BugDatabase::class.java, BUG_DATABASE_NAME) + .build() + } + + companion object { + private const val BUG_DATABASE_NAME = "bugreport-db" + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/dao/BugDao.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/dao/BugDao.kt new file mode 100644 index 0000000000000000000000000000000000000000..f613d95fa604929430388c6cbb1e838592a3cfc5 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/dao/BugDao.kt @@ -0,0 +1,12 @@ +package de.rki.coronawarnapp.bugreporting.storage.dao + +import de.rki.coronawarnapp.bugreporting.event.BugEvent +import kotlinx.coroutines.flow.Flow + +interface BugDao<T : BugEvent> { + suspend fun insertBugEvent(bugEvent: T) + fun getBugEvent(id: Long): Flow<T> + fun getAllBugEvents(): Flow<List<T>> + suspend fun deleteBugEvent(id: Long) + suspend fun deleteAllBugEvents() +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/dao/DefaultBugDao.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/dao/DefaultBugDao.kt new file mode 100644 index 0000000000000000000000000000000000000000..1162899c79aa29c394261bf171d57a2a46aead73 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/dao/DefaultBugDao.kt @@ -0,0 +1,26 @@ +package de.rki.coronawarnapp.bugreporting.storage.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import de.rki.coronawarnapp.bugreporting.event.BugEventEntity +import kotlinx.coroutines.flow.Flow + +@Dao +interface DefaultBugDao : BugDao<BugEventEntity> { + + @Insert + override suspend fun insertBugEvent(bugEvent: BugEventEntity) + + @Query("SELECT * FROM BugEventEntity WHERE id = :id") + override fun getBugEvent(id: Long): Flow<BugEventEntity> + + @Query("SELECT * FROM BugEventEntity") + override fun getAllBugEvents(): Flow<List<BugEventEntity>> + + @Query("DELETE FROM BugEventEntity") + override suspend fun deleteAllBugEvents() + + @Query("DELETE FROM BugEventEntity WHERE id = :id") + override suspend fun deleteBugEvent(id: Long) +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/repository/BugRepository.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/repository/BugRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..3b06c71d828d09518ddd4270d5813f5dd6a2045b --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/repository/BugRepository.kt @@ -0,0 +1,11 @@ +package de.rki.coronawarnapp.bugreporting.storage.repository + +import de.rki.coronawarnapp.bugreporting.event.BugEvent +import kotlinx.coroutines.flow.Flow + +interface BugRepository { + fun getAll(): Flow<List<BugEvent>> + fun get(id: Long): Flow<BugEvent> + suspend fun save(bugEvent: BugEvent) + suspend fun clear() +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/repository/DefaultBugRepository.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/repository/DefaultBugRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..4b75a49be75c0a60712b6ecf636123e702b456fb --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/storage/repository/DefaultBugRepository.kt @@ -0,0 +1,50 @@ +package de.rki.coronawarnapp.bugreporting.storage.repository + +import de.rki.coronawarnapp.bugreporting.event.BugEvent +import de.rki.coronawarnapp.bugreporting.event.BugEventEntity +import de.rki.coronawarnapp.bugreporting.storage.dao.DefaultBugDao +import kotlinx.coroutines.flow.Flow +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class DefaultBugRepository @Inject constructor( + private val bugDao: DefaultBugDao +) : BugRepository { + + override fun getAll(): Flow<List<BugEvent>> = bugDao.getAllBugEvents() + + override fun get(id: Long): Flow<BugEvent> = bugDao.getBugEvent(id) + + override suspend fun save(bugEvent: BugEvent) { + val bugEventEntity: BugEventEntity = bugEvent.mapToBugEventEntity() + bugDao.insertBugEvent(bugEventEntity) + } + + private fun BugEvent.mapToBugEventEntity(): BugEventEntity = + when (this is BugEventEntity) { + true -> this + else -> BugEventEntity( + id = id, + createdAt = createdAt, + tag = tag, + info = info, + exceptionClass = exceptionClass, + exceptionMessage = exceptionMessage, + stackTrace = stackTrace, + deviceInfo = deviceInfo, + appVersionName = appVersionName, + appVersionCode = appVersionCode, + apiLevel = apiLevel, + androidVersion = androidVersion, + shortCommitHash = shortCommitHash, + logHistory = logHistory + ) + } + + override suspend fun clear() { + Timber.d("Deleting all bug events!") + bugDao.deleteAllBugEvents() + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/DeviceForTestersModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/DeviceForTestersModule.kt index 554f4f82c58966fb0969199dfa6d349e060af6b2..64f9befbc354757dc3b60b3c3b3bec8131853e39 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/DeviceForTestersModule.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/DeviceForTestersModule.kt @@ -1,11 +1,13 @@ package de.rki.coronawarnapp.test import dagger.Module +import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragmentModule import de.rki.coronawarnapp.test.tasks.TaskControllerTestModule @Module( includes = [ - TaskControllerTestModule::class + TaskControllerTestModule::class, + SettingsCrashReportFragmentModule::class ] ) class DeviceForTestersModule diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/CrashReportAdapter.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/CrashReportAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..afdfb4358aa590da76289fb57cf592c9b0efc2f1 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/CrashReportAdapter.kt @@ -0,0 +1,47 @@ +package de.rki.coronawarnapp.test.crash.ui + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import de.rki.coronawarnapp.bugreporting.event.BugEvent + +import de.rki.coronawarnapp.databinding.ViewCrashreportListItemBinding +import org.joda.time.DateTimeZone + +class CrashReportAdapter(private val itemClickListener: (bugEvent: BugEvent) -> Unit) : + RecyclerView.Adapter<CrashReportAdapter.CrashHolder>() { + + private var crashReports = listOf<BugEvent>() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrashHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ViewCrashreportListItemBinding.inflate(inflater) + return CrashHolder( + binding + ) + } + + override fun onBindViewHolder(holder: CrashHolder, position: Int) { + val crashReport = crashReports[position] + holder.bind(crashReport, position) + holder.itemView.setOnClickListener { itemClickListener(crashReport) } + } + + override fun getItemCount() = crashReports.size + + fun updateCrashReports(crashReportList: List<BugEvent>) { + crashReports = crashReportList + notifyDataSetChanged() + } + + class CrashHolder(private val binding: ViewCrashreportListItemBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(bugEvent: BugEvent, pos: Int) { + binding.crashReportTitle = "Error #${pos + 1}" + binding.message = bugEvent.exceptionMessage + binding.crashReportDateFormatted = + bugEvent.createdAt.toDateTime(DateTimeZone.getDefault()).toString() + .replace("T", " ") + } + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportDetailsFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportDetailsFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..f7b9ce4dbc002399282e8297029ed970fa3ec50b --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportDetailsFragment.kt @@ -0,0 +1,55 @@ +package de.rki.coronawarnapp.test.crash.ui + +import android.os.Bundle +import android.view.View +import androidx.core.app.ShareCompat +import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentSettingsCrashReportDetailsBinding +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import javax.inject.Inject + +class SettingsCrashReportDetailsFragment : + Fragment(R.layout.fragment_settings_crash_report_details), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: SettingsCrashReportViewModel by cwaViewModels( + ownerProducer = { requireActivity().viewModelStore }, + factoryProducer = { viewModelFactory } + ) + private val fragmentSettingsCrashReportDetailsBinding: FragmentSettingsCrashReportDetailsBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + vm.selectedCrashReport.observe2(this) { + fragmentSettingsCrashReportDetailsBinding.buttonCrashReportShare.visibility = View.VISIBLE + fragmentSettingsCrashReportDetailsBinding.buttonCrashReportShare.setOnClickListener { shareCrashReport() } + } + + vm.selectedCrashReportFormattedText.observe2(this) { + fragmentSettingsCrashReportDetailsBinding.selectedCrashReportFormattedText = it + } + } + + private fun shareCrashReport() { + activity?.let { activity -> + val shareIntent = ShareCompat.IntentBuilder + .from(activity) + .setType("text/plain") + .setText(fragmentSettingsCrashReportDetailsBinding.textViewCrashReportDetails.text) + .createChooserIntent() + + if (shareIntent.resolveActivity(activity.packageManager) != null) { + startActivity(shareIntent) + } + } + } + + companion object { + private val TAG = SettingsCrashReportDetailsFragment::class.java.simpleName + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..9268d0cdefb3d4c4250d39ae10dea7bced9c412d --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportFragment.kt @@ -0,0 +1,67 @@ +package de.rki.coronawarnapp.test.crash.ui + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.bugreporting.event.BugEvent +import de.rki.coronawarnapp.databinding.FragmentCrashreporterOverviewBinding +import de.rki.coronawarnapp.test.menu.ui.TestMenuItem +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.doNavigate +import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import timber.log.Timber +import javax.inject.Inject + +class SettingsCrashReportFragment : Fragment(R.layout.fragment_crashreporter_overview), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: SettingsCrashReportViewModel by cwaViewModels( + ownerProducer = { requireActivity().viewModelStore }, + factoryProducer = { viewModelFactory } + ) + + private val fragmentCrashreporterOverviewBinding: FragmentCrashreporterOverviewBinding by viewBindingLazy() + private lateinit var adapter: CrashReportAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + adapter = CrashReportAdapter { crashReportClicked(it) } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + fragmentCrashreporterOverviewBinding.list.adapter = adapter + + vm.crashReports.observe2(this) { + adapter.updateCrashReports(it) + } + + fragmentCrashreporterOverviewBinding.buttonClearCrashReportList.setOnClickListener { + vm.deleteAllCrashReports() + } + + fragmentCrashreporterOverviewBinding.buttonTestItemForCrashReport.setOnClickListener { + vm.simulateException() + } + } + + private fun crashReportClicked(crashReport: BugEvent) { + Timber.d("Clicked on crash report ${crashReport.id}") + vm.selectCrashReport(crashReport) + doNavigate(SettingsCrashReportFragmentDirections.actionCrashReportFragmentToSettingsCrashReportDetailsFragment()) + } + + companion object { + val TAG = SettingsCrashReportFragment::class.java.simpleName + val MENU_ITEM = TestMenuItem( + title = "Bug & Problem Reporter", + description = "List of Bugs & Exceptions with share option.", + targetId = R.id.action_testMenuFragment_to_settingsCrashReportFragment + ) + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportFragmentModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..ecde683f1ec9ec04ed81cf9eb5002894d0c2fd78 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportFragmentModule.kt @@ -0,0 +1,24 @@ +package de.rki.coronawarnapp.test.crash.ui + +import dagger.Binds +import dagger.Module +import dagger.android.ContributesAndroidInjector +import dagger.multibindings.IntoMap +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey + +@Module +abstract class SettingsCrashReportFragmentModule { + + @Binds + @IntoMap + @CWAViewModelKey(SettingsCrashReportViewModel::class) + abstract fun settingsCrashReportFragment(factory: SettingsCrashReportViewModel.Factory): CWAViewModelFactory<out CWAViewModel> + + @ContributesAndroidInjector + abstract fun settingsCrashReportFragment(): SettingsCrashReportFragment + + @ContributesAndroidInjector + abstract fun settingsCrashReportDetailsFragment(): SettingsCrashReportDetailsFragment +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..234022907e2c603253e6ac70c8abffa1dd72143d --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportViewModel.kt @@ -0,0 +1,62 @@ +package de.rki.coronawarnapp.test.crash.ui + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData +import androidx.lifecycle.map +import androidx.lifecycle.viewModelScope +import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.bugreporting.event.BugEvent +import de.rki.coronawarnapp.bugreporting.reportProblem +import de.rki.coronawarnapp.bugreporting.storage.repository.BugRepository +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import timber.log.Timber +import java.lang.Exception + +class SettingsCrashReportViewModel @AssistedInject constructor( + private val crashReportRepository: BugRepository +) : CWAViewModel() { + + val crashReports = crashReportRepository.getAll().asLiveData() + + private val selectedCrashReportMutable: MutableLiveData<BugEvent> = MutableLiveData() + val selectedCrashReport: LiveData<BugEvent> = selectedCrashReportMutable + val selectedCrashReportFormattedText: LiveData<String> = selectedCrashReportMutable.map { + createBugEventFormattedText(it) + } + + fun deleteAllCrashReports() = viewModelScope.launch(Dispatchers.IO) { + crashReportRepository.clear() + } + + fun simulateException() { + try { + val a = 2 / 0 + } catch (e: Exception) { + Timber.e(e, "Msg: ${e.message}") + e.reportProblem(SettingsCrashReportViewModel::class.java.simpleName, e.message) + } + } + + fun selectCrashReport(bugEvent: BugEvent) { + selectedCrashReportMutable.postValue(bugEvent) + } + + private fun createBugEventFormattedText(bugEvent: BugEvent): String = + "Selected crash report ${bugEvent.id} \n" + + " # appeared at: ${bugEvent.createdAt} \n\n" + + " # Device: ${bugEvent.deviceInfo} \n" + + " # Android Version ${bugEvent.androidVersion} \n" + + " # Android API-Level ${bugEvent.apiLevel} \n\n" + + " # AppVersion: ${bugEvent.appVersionName} \n" + + " # AppVersionCode ${bugEvent.appVersionCode} \n" + + " # C-Hash ${bugEvent.shortCommitHash} \n\n\n" + + " ${bugEvent.stackTrace}\n\n" + + " # Corresponding Log: \n\n ${bugEvent.logHistory}" + + @AssistedInject.Factory + interface Factory : SimpleCWAViewModelFactory<SettingsCrashReportViewModel> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt index 7e6439dae9d7c53260b5bf13a10c8915c9c17f8f..aa58711c95116a6c9629216464e667b16fc3a9ce 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.test.menu.ui import androidx.lifecycle.MutableLiveData import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.test.api.ui.TestForAPIFragment +import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment import de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragment import de.rki.coronawarnapp.util.ui.SingleLiveEvent @@ -13,6 +14,7 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() { val testMenuData by lazy { listOf( + SettingsCrashReportFragment.MENU_ITEM, TestForAPIFragment.MENU_ITEM, TestRiskLevelCalculationFragment.MENU_ITEM, TestTaskControllerFragment.MENU_ITEM diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_crashreporter_overview.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_crashreporter_overview.xml new file mode 100644 index 0000000000000000000000000000000000000000..31bd7395a73e64aa03d8909449f5adf2d9ae243a --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_crashreporter_overview.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ui.main.MainActivity"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbarErrorReport" + style="@style/Widget.AppCompat.Toolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navigationIcon="@drawable/ic_bug" + app:subtitle="For testers and developers" + app:title="Error Report" /> + + <Button + android:id="@+id/buttonClearCrashReportList" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:text="Clear List" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toEndOf="@+id/buttonTestItemForCrashReport" + app:layout_constraintTop_toBottomOf="@+id/toolbarErrorReport" + app:layout_constraintVertical_chainStyle="packed" /> + + <Button + android:id="@+id/buttonTestItemForCrashReport" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="1dp" + android:text="Test Item (+1)" + app:layout_constraintEnd_toStartOf="@+id/buttonClearCrashReportList" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/toolbarErrorReport" + app:layout_constraintVertical_chainStyle="packed" /> + + <TextView + android:id="@+id/topinfo" + style="@style/TextAppearance.AppCompat.Caption" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/colorCalendarLayoutFocusOn" + android:gravity="center" + android:padding="8dp" + android:text="Note: Select a card to view and share details." + android:textColor="@color/colorTextSixteenWhite" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/buttonTestItemForCrashReport" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="0dp" + android:background="@color/colorCalendarLayoutFocusOn" + android:paddingBottom="8dp" + app:layoutManager="LinearLayoutManager" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/topinfo" + tools:context="SettingsCrashReporterFragment" + tools:listitem="@layout/view_crashreport_list_item" /> + + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_settings_crash_report_details.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_settings_crash_report_details.xml new file mode 100644 index 0000000000000000000000000000000000000000..d5d49dc2094f7bf586f72876918e04b9802a9f50 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_settings_crash_report_details.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + + <data> + <variable + name="selectedCrashReportFormattedText" + type="String" /> + </data> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".test.crash.ui.SettingsCrashReportDetailsFragment"> + + + <ScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_margin="5dp" + android:scrollbars="vertical" + app:layout_constraintBottom_toTopOf="@+id/buttonCrashReportShare" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <TextView + android:id="@+id/textViewCrashReportDetails" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@{selectedCrashReportFormattedText}" /> + + </ScrollView> + + <Button + android:id="@+id/buttonCrashReportShare" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:text="Share" + android:background="@color/colorCalendarLayoutFocusOn" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_menu.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_menu.xml index 32e387a62f941248a3155341818bf9ad30c82d6c..1746c0c05c91002998bef221502d0b3b8b009abd 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_menu.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_menu.xml @@ -10,16 +10,17 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" - app:navigationIcon="@drawable/ic_coffee" app:layout_constraintStart_toStartOf="parent" - app:subtitle="For testers ;)" app:layout_constraintTop_toTopOf="parent" + app:navigationIcon="@drawable/ic_coffee" + app:subtitle="For testers ;)" app:title="Test Menu" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/test_menu_list" android:layout_width="match_parent" android:layout_height="0dp" + android:layout_marginTop="12dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/view_crashreport_list_item.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/view_crashreport_list_item.xml new file mode 100644 index 0000000000000000000000000000000000000000..d820d54e752ee1ab99c20c29f5fcf07054638567 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/view_crashreport_list_item.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <data> + + <variable + name="crashReportTitle" + type="String" /> + + <variable + name="crashReportDateFormatted" + type="String" /> + + <variable + name="message" + type="String" /> + </data> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:orientation="vertical" + android:paddingBottom="2dp"> + + <androidx.constraintlayout.widget.ConstraintLayout + style="@style/card" + android:layout_width="390dp" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny" + android:orientation="vertical"> + + <TextView + android:id="@+id/textViewCrashReportTitle" + style="@style/body1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@{crashReportTitle}" + android:textStyle="bold" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/textViewCrashReportDate" + style="@style/body1" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:text="@{crashReportDateFormatted}" + android:textAppearance="?attr/textAppearanceListItem" + android:textStyle="italic" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textViewCrashReportTitle" /> + + <TextView + android:id="@+id/textViewCrashReportShortMessage" + style="@style/body1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="24dp" + android:ellipsize="end" + android:inputType="none" + android:maxLines="1" + android:text="@{message}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textViewCrashReportDate" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + </LinearLayout> +</layout> \ No newline at end of file diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml index b86da2f4908b0bd7958f4fb0091f1c46adafcaf0..3aff7a83c201775dc37583d1da61ee3844b3eb99 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml @@ -9,6 +9,9 @@ android:id="@+id/test_menu_fragment" android:name="de.rki.coronawarnapp.test.menu.ui.TestMenuFragment" android:label="TestMenuFragment"> + <action + android:id="@+id/action_testMenuFragment_to_settingsCrashReportFragment" + app:destination="@id/test_bug_report_fragment" /> <action android:id="@+id/action_testMenuFragment_to_testForAPIFragment" app:destination="@id/test_for_api_fragment" /> @@ -26,6 +29,22 @@ android:label="@layout/fragment_test_for_a_p_i" tools:layout="@layout/fragment_test_for_a_p_i" /> + <fragment + android:id="@+id/test_bug_report_fragment" + android:name="de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment" + android:label="crashreporter" + tools:layout="@layout/fragment_crashreporter_overview"> + <action + android:id="@+id/action_crashReportFragment_to_settingsCrashReportDetailsFragment" + app:destination="@id/settingsCrashReportDetailsFragment" /> + </fragment> + + <fragment + android:id="@+id/settingsCrashReportDetailsFragment" + android:name="de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportDetailsFragment" + android:label="fragment_settings_crash_report_details" + tools:layout="@layout/fragment_settings_crash_report_details" /> + <fragment android:id="@+id/test_risklevel_calculation_fragment" android:name="de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment" @@ -33,15 +52,14 @@ tools:layout="@layout/fragment_test_risk_level_calculation"> <argument android:name="exampleArgument" - app:argType="string" android:defaultValue="null" + app:argType="string" app:nullable="true" /> </fragment> <fragment android:id="@+id/test_taskcontroller_fragment" android:name="de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragment" - - tools:layout="@layout/fragment_test_task_controller" - android:label="TestTaskControllerFragment" /> + android:label="TestTaskControllerFragment" + tools:layout="@layout/fragment_test_task_controller" /> </navigation> diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt index 922c0ab30e5db8e345fe228a722d3b54ff73a9af..fa995ee845809e391c5c5da7e4623f779febc009 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt @@ -12,6 +12,7 @@ import androidx.work.WorkManager import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector +import de.rki.coronawarnapp.bugreporting.loghistory.LogHistoryTree 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 @@ -35,11 +36,13 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { @Inject lateinit var component: ApplicationComponent @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> + override fun androidInjector(): AndroidInjector<Any> = androidInjector @Inject lateinit var watchdogService: WatchdogService @Inject lateinit var taskController: TaskController @Inject lateinit var foregroundState: ForegroundState + @LogHistoryTree @Inject lateinit var rollingLogHistory: Timber.Tree override fun onCreate() { instance = this @@ -49,6 +52,8 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { Timber.v("onCreate(): Initializing Dagger") AppInjector.init(this) + Timber.plant(rollingLogHistory) + Timber.v("onCreate(): Initializing WorkManager") Configuration.Builder() .apply { setMinimumLoggingLevel(android.util.Log.DEBUG) }.build() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReporter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReporter.kt new file mode 100644 index 0000000000000000000000000000000000000000..d3ae63b5936f0cb30d67f227d1640aef9603d987 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReporter.kt @@ -0,0 +1,12 @@ +package de.rki.coronawarnapp.bugreporting + +import de.rki.coronawarnapp.util.di.AppInjector + +interface BugReporter { + fun report(throwable: Throwable, tag: String? = null, info: String? = null) +} + +fun Throwable.reportProblem(tag: String? = null, info: String? = null) { + val reporter = AppInjector.component.bugReporter + reporter.report(this, tag, info) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/loghistory/LogHistoryTree.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/loghistory/LogHistoryTree.kt new file mode 100644 index 0000000000000000000000000000000000000000..0852ac4c9530ceb72ebc44cfa96ad21cdedea88f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/loghistory/LogHistoryTree.kt @@ -0,0 +1,8 @@ +package de.rki.coronawarnapp.bugreporting.loghistory + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class LogHistoryTree diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/reporting/ExceptionReporter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/reporting/ExceptionReporter.kt index a29ddabf3f4b86efdebbae8310fae5952be4600e..8f602075d2af674dfa4b9853c9d9c22428e36777 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/reporting/ExceptionReporter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/reporting/ExceptionReporter.kt @@ -5,6 +5,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.google.android.gms.common.api.ApiException import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.ReportingConstants.STATUS_CODE_GOOGLE_API_FAIL import de.rki.coronawarnapp.exception.reporting.ReportingConstants.STATUS_CODE_GOOGLE_UPDATE_NEEDED @@ -21,6 +22,7 @@ fun Throwable.report( prefix: String?, suffix: String? ) { + reportProblem(tag = prefix, info = suffix) val context = CoronaWarnApplication.getAppContext() val intent = Intent(ReportingConstants.ERROR_REPORT_LOCAL_BROADCAST_CHANNEL) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt index 454055162dc1c14c57945c1ae1b3e3b97f177631..66fc87e9d7b3b18e41d0a565b070c6c8e4eb2ad4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt @@ -20,7 +20,11 @@ import net.sqlcipher.database.SupportFactory import java.io.File @Database( - entities = [ExposureSummaryEntity::class, KeyCacheLegacyEntity::class, TracingIntervalEntity::class], + entities = [ + ExposureSummaryEntity::class, + KeyCacheLegacyEntity::class, + TracingIntervalEntity::class + ], version = 1, exportSchema = true ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt index cbbf8366f3d47ce6b05c2917b8a471ab7e685d91..9e5ce2d20f6407093dc93fcd451926eeed9a6813 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt @@ -23,6 +23,7 @@ import androidx.room.TypeConverter import com.google.gson.Gson import com.google.gson.reflect.TypeToken import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode +import de.rki.coronawarnapp.util.gson.fromJson import org.joda.time.Instant import org.joda.time.LocalDate import org.joda.time.LocalTime @@ -43,6 +44,14 @@ class CommonConverters { return gson.toJson(list) } + @TypeConverter + fun toStringList(string: String?): List<String>? = + string?.let { gson.fromJson(it) } + + @TypeConverter + fun fromStringList(strings: List<String>?): String? = + strings?.let { gson.toJson(it) } + @TypeConverter fun toUUID(value: String?): UUID? = value?.let { UUID.fromString(it) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt index bfb0644bf67a04882cfe9e716c514ef51b2e2f32..1e6ce322a1190b8d56172992a4b3281bde9fb965 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt @@ -7,6 +7,8 @@ import dagger.android.support.AndroidSupportInjectionModule import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.appconfig.AppConfigModule import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.bugreporting.BugReporter +import de.rki.coronawarnapp.bugreporting.BugReportingModule import de.rki.coronawarnapp.diagnosiskeys.DiagnosisKeysModule import de.rki.coronawarnapp.diagnosiskeys.download.KeyFileDownloader import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository @@ -63,7 +65,9 @@ import javax.inject.Singleton PlaybookModule::class, TaskModule::class, DeviceForTestersModule::class, + BugReportingModule::class, SerializationModule::class + ] ) interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { @@ -93,6 +97,8 @@ interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { @AppScope val appScope: AppCoroutineScope + val bugReporter: BugReporter + @Component.Factory interface Factory { fun create(@BindsInstance app: CoronaWarnApplication): ApplicationComponent diff --git a/Corona-Warn-App/src/main/res/menu/menu_main.xml b/Corona-Warn-App/src/main/res/menu/menu_main.xml index 811d32f472235221f4f0022d1c690fe4883b126b..8fdae95ebad8a9f6d7f61889fa6b69008dab6321 100644 --- a/Corona-Warn-App/src/main/res/menu/menu_main.xml +++ b/Corona-Warn-App/src/main/res/menu/menu_main.xml @@ -1,5 +1,5 @@ -<menu xmlns:tools="http://schemas.android.com/tools" - xmlns:android="http://schemas.android.com/apk/res/android"> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> <item android:id="@+id/menu_help" android:title="@string/menu_help" /> @@ -11,7 +11,7 @@ android:title="@string/menu_settings" /> <item android:id="@+id/menu_test" - android:visible="false" android:title="Test Menu" + android:visible="false" tools:ignore="HardcodedText" /> </menu> diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml index c5dbb30805719b2d3a7f9cee9404d2189cf76b45..6d734cbc2904ca373f763df1ffa89578a6648666 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -96,7 +96,7 @@ <fragment android:id="@+id/onboardingDeltaInteroperabilityFragment" android:name="de.rki.coronawarnapp.ui.onboarding.OnboardingDeltaInteroperabilityFragment" - android:label="OnboardingDeltaInteroperabilityFragment" > + android:label="OnboardingDeltaInteroperabilityFragment"> <action android:id="@+id/action_onboardingDeltaInteroperabilityFragment_to_informationTermsFragment" app:destination="@id/informationTermsFragment" /> @@ -359,6 +359,7 @@ android:id="@+id/submissionSymptomIntroductionFragment" android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionSymptomIntroductionFragment" android:label="SubmissionSymptomIntroductionFragment" > + <action android:id="@+id/action_submissionSymptomIntroductionFragment_to_submissionSymptomCalendarFragment" app:destination="@id/submissionSymptomCalendarFragment" /> @@ -381,4 +382,5 @@ app:destination="@id/submissionResultPositiveOtherWarningFragment" /> </fragment> + </navigation> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index ff0ae9749ef306cf35e9a1cdd891dade56734fa8..923b29cb40751c9d778f7a7e9a70c595e7e68836 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -1398,4 +1398,6 @@ <string name="interoperability_onboarding_list_subtitle_failrequest_no_network">"Your Internet connection may have been lost. Please ensure that you are connected to the Internet."</string> <!-- XBUT: Title for the interoperability onboarding Settings-Button if no network is available --> <string name="interoperability_onboarding_list_button_title_no_network">"Open Device Settings"</string> + + </resources> \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 053c4359772c1f8f3f1d6de5dafbef0553327916..9de4cffa4778ef74a87f615dc794885569704cf4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,6 +18,6 @@ org.gradle.parallel=true org.gradle.dependency.verification.console=verbose # Versioning, this is used by the app & pipelines to calculate the current versionCode & versionName VERSION_MAJOR=1 -VERSION_MINOR=6 +VERSION_MINOR=7 VERSION_PATCH=0 -VERSION_BUILD=5 +VERSION_BUILD=1