Skip to content
Snippets Groups Projects
Unverified Commit 36921e58 authored by Jakob Möller's avatar Jakob Möller Committed by GitHub
Browse files

0.8.0

parents 90c49ae4 472f095f
No related branches found
No related tags found
No related merge requests found
Showing
with 177 additions and 85 deletions
...@@ -7,11 +7,10 @@ Before submitting, please take the time to check the points below and provide so ...@@ -7,11 +7,10 @@ Before submitting, please take the time to check the points below and provide so
* [ ] Test your changes as thoroughly as possible before you commit them. Preferably, automate your test by unit/integration tests. * [ ] Test your changes as thoroughly as possible before you commit them. Preferably, automate your test by unit/integration tests.
* [ ] If this PR comes from a fork, please [Allow edits from maintainers](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) * [ ] If this PR comes from a fork, please [Allow edits from maintainers](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)
* [ ] Set a speaking title. Format: {task_name} (closes #{issue_number}). For example: Use logger (closes #41) * [ ] Set a speaking title. Format: {task_name} (closes #{issue_number}). For example: Use logger (closes # 41)
* [ ] [Link your Pull Request to an issue](https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) (if applicable) * [ ] [Link your Pull Request to an issue](https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) (if applicable)
* [ ] Create Work In Progress [WIP] pull requests only if you need clarification or an explicit review before you can continue your work item. * [ ] Create Work In Progress [WIP] pull requests only if you need clarification or an explicit review before you can continue your work item.
* [ ] Make sure that your PR is not introducing _unncessary_ reformatting (e.g., introduced by on-save hooks in your IDE) * [ ] Make sure that your PR is not introducing _unnecessary_ reformatting (e.g., introduced by on-save hooks in your IDE)
* [ ] Check our [Contribution Guidelines](https://github.com/corona-warn-app/cwa-app-android/blob/master/CONTRIBUTING.md)
## Description ## Description
<!-- Please be brief in describing which issue is solved by your PR or which enhancement it brings --> <!-- Please be brief in describing which issue is solved by your PR or which enhancement it brings -->
...@@ -46,7 +46,7 @@ The following rule governs documentation contributions: ...@@ -46,7 +46,7 @@ The following rule governs documentation contributions:
## Pull Request Checklist ## Pull Request Checklist
* Branch from the master branch and, if needed, rebase to the current master branch before submitting your pull request. If it doesn't merge cleanly with master, you may be asked to rebase your changes. * Branch from the dev branch and ensure it is up to date with the current dev branch before submitting your pull request. If it doesn't merge cleanly with dev, you may be asked to resolve the conflicts. Pull requests to master will be closed.
* Commits should be as small as possible while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests). * Commits should be as small as possible while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests).
......
...@@ -32,8 +32,8 @@ android { ...@@ -32,8 +32,8 @@ android {
applicationId 'de.rki.coronawarnapp' applicationId 'de.rki.coronawarnapp'
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 29
versionCode 6 versionCode 7
versionName "0.5.6" versionName "0.8.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "DOWNLOAD_CDN_URL", "\"$DOWNLOAD_CDN_URL\"" buildConfigField "String", "DOWNLOAD_CDN_URL", "\"$DOWNLOAD_CDN_URL\""
......
...@@ -2,17 +2,52 @@ package de.rki.coronawarnapp ...@@ -2,17 +2,52 @@ package de.rki.coronawarnapp
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import de.rki.coronawarnapp.notification.NotificationHelper
class CoronaWarnApplication : Application() { class CoronaWarnApplication : Application(), LifecycleObserver {
companion object { companion object {
val TAG: String? = CoronaWarnApplication::class.simpleName
private lateinit var instance: CoronaWarnApplication 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 = fun getAppContext(): Context =
instance.applicationContext instance.applicationContext
} }
override fun onCreate() { override fun onCreate() {
instance = this instance = this
NotificationHelper.createNotificationChannel()
super.onCreate() super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
/**
* Callback when the app is open but backgrounded
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
isAppInForeground = false
Log.v(TAG, "App backgrounded")
}
/**
* Callback when the app is foregrounded
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForegrounded() {
isAppInForeground = true
Log.v(TAG, "App foregrounded")
} }
} }
package de.rki.coronawarnapp.exception package de.rki.coronawarnapp.exception
class RiskLevelCalculationException(cause: Throwable) : class RiskLevelCalculationException(cause: Throwable) :
Exception("an exception occured during risk level calculation", cause) Exception("an exception occurred during risk level calculation", cause)
...@@ -192,7 +192,7 @@ object WebRequestBuilder { ...@@ -192,7 +192,7 @@ object WebRequestBuilder {
) = ) =
suspendCoroutine<String> { cont -> suspendCoroutine<String> { cont ->
val requestID = UUID.randomUUID() val requestID = UUID.randomUUID()
val getTestResultRequest = val getTANRequest =
TanRequest( TanRequest(
url, url,
requestID, requestID,
...@@ -209,7 +209,7 @@ object WebRequestBuilder { ...@@ -209,7 +209,7 @@ object WebRequestBuilder {
}, },
RequestErrorListener(requestID, cont) RequestErrorListener(requestID, cont)
) )
RequestQueueHolder.addToRequestQueue(getTestResultRequest) RequestQueueHolder.addToRequestQueue(getTANRequest)
Log.d(TAG, "$requestID: Added $url to queue.") Log.d(TAG, "$requestID: Added $url to queue.")
} }
...@@ -254,7 +254,7 @@ object WebRequestBuilder { ...@@ -254,7 +254,7 @@ object WebRequestBuilder {
Response.ErrorListener { Response.ErrorListener {
override fun onErrorResponse(error: VolleyError?) { override fun onErrorResponse(error: VolleyError?) {
if (error != null) { if (error != null) {
val webRequestException = WebRequestException("an error occured during a webrequest", error) val webRequestException = WebRequestException("an error occurred during a webrequest", error)
webRequestException.report(de.rki.coronawarnapp.exception.ExceptionCategory.HTTP) webRequestException.report(de.rki.coronawarnapp.exception.ExceptionCategory.HTTP)
cont.resumeWithException(webRequestException) cont.resumeWithException(webRequestException)
} else { } else {
......
...@@ -17,7 +17,7 @@ object NotificationConstants { ...@@ -17,7 +17,7 @@ object NotificationConstants {
/** /**
* Notification small icon String.xml path * Notification small icon String.xml path
*/ */
const val NOTIFICATION_SMALL_ICON = R.drawable.ic_app_launch_icon const val NOTIFICATION_SMALL_ICON = R.drawable.ic_splash_logo
/** /**
* Notification channel name String.xml path * Notification channel name String.xml path
......
...@@ -19,6 +19,7 @@ import kotlin.random.Random ...@@ -19,6 +19,7 @@ import kotlin.random.Random
/** /**
* Singleton class for notification handling * Singleton class for notification handling
* Notifications should only be sent when the app is not in foreground.
* The helper uses externalised constants for readability. * The helper uses externalised constants for readability.
* *
* @see NotificationConstants * @see NotificationConstants
...@@ -91,14 +92,23 @@ object NotificationHelper { ...@@ -91,14 +92,23 @@ object NotificationHelper {
private fun buildNotification(title: String, content: String, visibility: Int): Notification? { private fun buildNotification(title: String, content: String, visibility: Int): Notification? {
val builder = NotificationCompat.Builder(CoronaWarnApplication.getAppContext(), channelId) val builder = NotificationCompat.Builder(CoronaWarnApplication.getAppContext(), channelId)
.setSmallIcon(NotificationConstants.NOTIFICATION_SMALL_ICON) .setSmallIcon(NotificationConstants.NOTIFICATION_SMALL_ICON)
.setContentTitle(title)
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.setVisibility(visibility) .setVisibility(visibility)
.setContentIntent(createPendingIntentToMainActivity()) .setContentIntent(createPendingIntentToMainActivity())
.setAutoCancel(true) .setAutoCancel(true)
if (title.isNotEmpty()) {
builder.setContentTitle(title)
}
if (visibility == NotificationCompat.VISIBILITY_PRIVATE) { if (visibility == NotificationCompat.VISIBILITY_PRIVATE) {
builder.setPublicVersion(buildNotification(title, content, NotificationCompat.VISIBILITY_PUBLIC)) builder.setPublicVersion(
buildNotification(
title,
content,
NotificationCompat.VISIBILITY_PUBLIC
)
)
} else if (visibility == NotificationCompat.VISIBILITY_PUBLIC) { } else if (visibility == NotificationCompat.VISIBILITY_PUBLIC) {
builder.setContentText(content) builder.setContentText(content)
} }
...@@ -128,7 +138,6 @@ object NotificationHelper { ...@@ -128,7 +138,6 @@ object NotificationHelper {
* @param visibility: Int * @param visibility: Int
*/ */
fun sendNotification(title: String, content: String, visibility: Int) { fun sendNotification(title: String, content: String, visibility: Int) {
createNotificationChannel()
val notification = buildNotification(title, content, visibility) ?: return val notification = buildNotification(title, content, visibility) ?: return
with(NotificationManagerCompat.from(CoronaWarnApplication.getAppContext())) { with(NotificationManagerCompat.from(CoronaWarnApplication.getAppContext())) {
notify(Random.nextInt(), notification) notify(Random.nextInt(), notification)
...@@ -137,16 +146,16 @@ object NotificationHelper { ...@@ -137,16 +146,16 @@ object NotificationHelper {
/** /**
* Send notification * Send notification
* Build and send notification with predefined title and content. * Build and send notification with content and visibility.
* Visibility is auto set to NotificationCompat.VISIBILITY_PRIVATE * Notification is only sent if app is not in foreground.
* *
* @param title: String
* @param content: String * @param content: String
* * @param visibility: Int
* @see NotificationCompat.VISIBILITY_PRIVATE
*/ */
fun sendNotification(title: String, content: String) { fun sendNotification(content: String, visibility: Int) {
sendNotification(title, content, NotificationCompat.VISIBILITY_PRIVATE) if (!CoronaWarnApplication.isAppInForeground) {
sendNotification("", content, visibility)
}
} }
/** /**
......
...@@ -40,5 +40,41 @@ enum class RiskLevel(val raw: Int) { ...@@ -40,5 +40,41 @@ enum class RiskLevel(val raw: Int) {
else -> UNDETERMINED else -> UNDETERMINED
} }
} }
// risk level categories
private val HIGH_RISK_LEVELS = arrayOf(INCREASED_RISK)
private val LOW_RISK_LEVELS = arrayOf(
UNKNOWN_RISK_INITIAL,
NO_CALCULATION_POSSIBLE_TRACING_OFF,
LOW_LEVEL_RISK,
UNKNOWN_RISK_OUTDATED_RESULTS,
UNDETERMINED
)
/**
* Checks if the RiskLevel has change from a high to low or from low to high
*
* @param previousRiskLevel previously persisted RiskLevel
* @param currentRiskLevel newly calculated RiskLevel
* @return
*/
fun riskLevelChangedBetweenLowAndHigh(
previousRiskLevel: RiskLevel,
currentRiskLevel: RiskLevel
): Boolean {
var riskLevelChangedBetweenLowAndHigh = false
if (HIGH_RISK_LEVELS.contains(previousRiskLevel) && LOW_RISK_LEVELS.contains(
currentRiskLevel
)
) {
riskLevelChangedBetweenLowAndHigh = true
} else if (LOW_RISK_LEVELS.contains(previousRiskLevel) && HIGH_RISK_LEVELS.contains(
currentRiskLevel
)
) {
riskLevelChangedBetweenLowAndHigh = true
}
return riskLevelChangedBetweenLowAndHigh
}
} }
} }
...@@ -22,6 +22,7 @@ object SubmissionService { ...@@ -22,6 +22,7 @@ object SubmissionService {
testTAN != null -> asyncRegisterDeviceViaTAN(testTAN) testTAN != null -> asyncRegisterDeviceViaTAN(testTAN)
else -> throw NoGUIDOrTANSetException() else -> throw NoGUIDOrTANSetException()
} }
LocalData.devicePairingSuccessfulTimestamp(System.currentTimeMillis())
} }
private suspend fun asyncRegisterDeviceViaGUID(guid: String) { private suspend fun asyncRegisterDeviceViaGUID(guid: String) {
...@@ -73,6 +74,7 @@ object SubmissionService { ...@@ -73,6 +74,7 @@ object SubmissionService {
fun deleteRegistrationToken() { fun deleteRegistrationToken() {
LocalData.registrationToken(null) LocalData.registrationToken(null)
LocalData.devicePairingSuccessfulTimestamp(0L)
} }
private fun deleteAuthCode() { private fun deleteAuthCode() {
......
...@@ -10,9 +10,9 @@ import de.rki.coronawarnapp.storage.keycache.KeyCacheEntity ...@@ -10,9 +10,9 @@ import de.rki.coronawarnapp.storage.keycache.KeyCacheEntity
import de.rki.coronawarnapp.storage.tracing.TracingIntervalDao import de.rki.coronawarnapp.storage.tracing.TracingIntervalDao
import de.rki.coronawarnapp.storage.tracing.TracingIntervalEntity import de.rki.coronawarnapp.storage.tracing.TracingIntervalEntity
import de.rki.coronawarnapp.util.Converters import de.rki.coronawarnapp.util.Converters
import de.rki.coronawarnapp.util.security.SecurityHelper
import net.sqlcipher.database.SQLiteDatabase import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SupportFactory import net.sqlcipher.database.SupportFactory
import java.util.UUID
@Database( @Database(
entities = [ExposureSummaryEntity::class, KeyCacheEntity::class, TracingIntervalEntity::class], entities = [ExposureSummaryEntity::class, KeyCacheEntity::class, TracingIntervalEntity::class],
...@@ -39,11 +39,8 @@ abstract class AppDatabase : RoomDatabase() { ...@@ -39,11 +39,8 @@ abstract class AppDatabase : RoomDatabase() {
fun resetInstance(context: Context) = { instance = null }.also { getInstance(context) } fun resetInstance(context: Context) = { instance = null }.also { getInstance(context) }
private fun buildDatabase(context: Context): AppDatabase { private fun buildDatabase(context: Context): AppDatabase {
if (LocalData.databasePassword() == null) {
LocalData.databasePassword(UUID.randomUUID().toString().toCharArray())
}
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.openHelperFactory(SupportFactory(SQLiteDatabase.getBytes(LocalData.databasePassword()))) .openHelperFactory(SupportFactory(SQLiteDatabase.getBytes(SecurityHelper.getDBPassword())))
.build() .build()
} }
} }
......
package de.rki.coronawarnapp.storage package de.rki.coronawarnapp.storage
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit
import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.R import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.util.security.SecurityHelper.globalEncryptedSharedPreferencesInstance import de.rki.coronawarnapp.util.security.SecurityHelper.globalEncryptedSharedPreferencesInstance
...@@ -37,12 +38,11 @@ object LocalData { ...@@ -37,12 +38,11 @@ object LocalData {
* *
* @param value boolean if onboarding was completed * @param value boolean if onboarding was completed
*/ */
fun isOnboarded(value: Boolean) = with(getSharedPreferenceInstance().edit()) { fun isOnboarded(value: Boolean) = getSharedPreferenceInstance().edit(true) {
putBoolean( putBoolean(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_onboarding_completed), value .getString(R.string.preference_onboarding_completed), value
) )
commit()
} }
/**************************************************** /****************************************************
...@@ -73,7 +73,7 @@ object LocalData { ...@@ -73,7 +73,7 @@ object LocalData {
* @param value timestamp in ms * @param value timestamp in ms
*/ */
fun initialTracingActivationTimestamp(value: Long) = fun initialTracingActivationTimestamp(value: Long) =
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putLong( putLong(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_initial_tracing_activation_time), .getString(R.string.preference_initial_tracing_activation_time),
...@@ -106,7 +106,7 @@ object LocalData { ...@@ -106,7 +106,7 @@ object LocalData {
* *
* @param value timestamp in ms * @param value timestamp in ms
*/ */
fun lastNonActiveTracingTimestamp(value: Long?) = with(getSharedPreferenceInstance().edit()) { fun lastNonActiveTracingTimestamp(value: Long?) = getSharedPreferenceInstance().edit(true) {
// TODO need this for nullable ref, shout not be goto for nullable storage // TODO need this for nullable ref, shout not be goto for nullable storage
putLong( putLong(
CoronaWarnApplication.getAppContext().getString( CoronaWarnApplication.getAppContext().getString(
...@@ -139,7 +139,7 @@ object LocalData { ...@@ -139,7 +139,7 @@ object LocalData {
*/ */
fun totalNonActiveTracing(value: Long?) { fun totalNonActiveTracing(value: Long?) {
// TODO need this for nullable ref, shout not be goto for nullable storage // TODO need this for nullable ref, shout not be goto for nullable storage
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putLong( putLong(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_total_non_active_tracing), .getString(R.string.preference_total_non_active_tracing),
...@@ -180,7 +180,7 @@ object LocalData { ...@@ -180,7 +180,7 @@ object LocalData {
*/ */
fun lastTimeDiagnosisKeysFromServerFetch(value: Date?) { fun lastTimeDiagnosisKeysFromServerFetch(value: Date?) {
// TODO need this for nullable ref, shout not be goto for nullable storage // TODO need this for nullable ref, shout not be goto for nullable storage
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putLong( putLong(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_m_timestamp_diagnosis_keys_fetch), .getString(R.string.preference_m_timestamp_diagnosis_keys_fetch),
...@@ -205,7 +205,7 @@ object LocalData { ...@@ -205,7 +205,7 @@ object LocalData {
* Sets the last timestamp the user manually triggered the key retrieval process * Sets the last timestamp the user manually triggered the key retrieval process
*/ */
fun lastTimeManualDiagnosisKeysRetrieved(value: Long) = fun lastTimeManualDiagnosisKeysRetrieved(value: Long) =
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putLong( putLong(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_m_timestamp_manual_diagnosis_keys_retrieval), .getString(R.string.preference_m_timestamp_manual_diagnosis_keys_retrieval),
...@@ -234,7 +234,7 @@ object LocalData { ...@@ -234,7 +234,7 @@ object LocalData {
* *
* @param value UUID as string * @param value UUID as string
*/ */
fun googleApiToken(value: String?) = with(getSharedPreferenceInstance().edit()) { fun googleApiToken(value: String?) = getSharedPreferenceInstance().edit(true) {
putString( putString(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_m_string_google_api_token), .getString(R.string.preference_m_string_google_api_token),
...@@ -262,7 +262,7 @@ object LocalData { ...@@ -262,7 +262,7 @@ object LocalData {
* Toggles the user decision if notification should be enabled for a risk change * Toggles the user decision if notification should be enabled for a risk change
* *
*/ */
fun toggleNotificationsRiskEnabled() = with(getSharedPreferenceInstance().edit()) { fun toggleNotificationsRiskEnabled() = getSharedPreferenceInstance().edit(true) {
putBoolean( putBoolean(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_notifications_risk_enabled), .getString(R.string.preference_notifications_risk_enabled),
...@@ -277,7 +277,7 @@ object LocalData { ...@@ -277,7 +277,7 @@ object LocalData {
true true
) )
fun toggleNotificationsTestEnabled() = with(getSharedPreferenceInstance().edit()) { fun toggleNotificationsTestEnabled() = getSharedPreferenceInstance().edit(true) {
putBoolean( putBoolean(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_notifications_test_enabled), .getString(R.string.preference_notifications_test_enabled),
...@@ -300,7 +300,7 @@ object LocalData { ...@@ -300,7 +300,7 @@ object LocalData {
* Toggles the decision if background jobs are enabled * Toggles the decision if background jobs are enabled
* *
*/ */
fun toggleBackgroundJobEnabled() = with(getSharedPreferenceInstance().edit()) { fun toggleBackgroundJobEnabled() = getSharedPreferenceInstance().edit(true) {
putBoolean( putBoolean(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_background_job_allowed), .getString(R.string.preference_background_job_allowed),
...@@ -323,7 +323,7 @@ object LocalData { ...@@ -323,7 +323,7 @@ object LocalData {
* Toggles the boolean if the user has mobile data enabled * Toggles the boolean if the user has mobile data enabled
* *
*/ */
fun toggleMobileDataEnabled() = with(getSharedPreferenceInstance().edit()) { fun toggleMobileDataEnabled() = getSharedPreferenceInstance().edit(true) {
putBoolean( putBoolean(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_mobile_data_allowed), .getString(R.string.preference_mobile_data_allowed),
...@@ -353,7 +353,7 @@ object LocalData { ...@@ -353,7 +353,7 @@ object LocalData {
* @param value registration token as string * @param value registration token as string
*/ */
fun registrationToken(value: String?) { fun registrationToken(value: String?) {
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putString( putString(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_m_registration_token), .getString(R.string.preference_m_registration_token),
...@@ -364,7 +364,7 @@ object LocalData { ...@@ -364,7 +364,7 @@ object LocalData {
} }
fun inititalTestResultReceivedTimestamp(value: Long) = fun inititalTestResultReceivedTimestamp(value: Long) =
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putLong( putLong(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_initial_result_received_time), .getString(R.string.preference_initial_result_received_time),
...@@ -386,8 +386,26 @@ object LocalData { ...@@ -386,8 +386,26 @@ object LocalData {
return timestamp return timestamp
} }
fun numberOfSuccessfulSubmissions(value: Int) = fun devicePairingSuccessfulTimestamp(value: Long) =
with(getSharedPreferenceInstance().edit()) { with(getSharedPreferenceInstance().edit()) {
putLong(
CoronaWarnApplication.getAppContext()
.getString(R.string.preference_device_pairing_successful_time),
value
)
commit()
}
fun devicePairingSuccessfulTimestamp(): Long? {
return getSharedPreferenceInstance().getLong(
CoronaWarnApplication.getAppContext()
.getString(R.string.preference_device_pairing_successful_time),
0L
)
}
fun numberOfSuccessfulSubmissions(value: Int) =
getSharedPreferenceInstance().edit(true) {
putInt( putInt(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_number_successful_submissions), .getString(R.string.preference_number_successful_submissions),
...@@ -411,7 +429,7 @@ object LocalData { ...@@ -411,7 +429,7 @@ object LocalData {
) )
fun testGUID(value: String?) { fun testGUID(value: String?) {
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putString( putString(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_m_test_guid), .getString(R.string.preference_m_test_guid),
...@@ -428,7 +446,7 @@ object LocalData { ...@@ -428,7 +446,7 @@ object LocalData {
) )
fun authCode(value: String?) { fun authCode(value: String?) {
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putString( putString(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_m_auth_code), .getString(R.string.preference_m_auth_code),
...@@ -439,7 +457,7 @@ object LocalData { ...@@ -439,7 +457,7 @@ object LocalData {
} }
fun isAllowedToSubmitDiagnosisKeys(isAllowedToSubmitDiagnosisKeys: Boolean) { fun isAllowedToSubmitDiagnosisKeys(isAllowedToSubmitDiagnosisKeys: Boolean) {
with(getSharedPreferenceInstance().edit()) { getSharedPreferenceInstance().edit(true) {
putBoolean( putBoolean(
CoronaWarnApplication.getAppContext() CoronaWarnApplication.getAppContext()
.getString(R.string.preference_m_is_allowed_to_submit_diagnosis_keys), .getString(R.string.preference_m_is_allowed_to_submit_diagnosis_keys),
...@@ -457,7 +475,7 @@ object LocalData { ...@@ -457,7 +475,7 @@ object LocalData {
) )
} }
fun teletan(value: String?) = with(getSharedPreferenceInstance().edit()) { fun teletan(value: String?) = getSharedPreferenceInstance().edit(true) {
putString( putString(
CoronaWarnApplication.getAppContext().getString(R.string.preference_teletan), CoronaWarnApplication.getAppContext().getString(R.string.preference_teletan),
value value
...@@ -469,27 +487,6 @@ object LocalData { ...@@ -469,27 +487,6 @@ object LocalData {
CoronaWarnApplication.getAppContext().getString(R.string.preference_teletan), null CoronaWarnApplication.getAppContext().getString(R.string.preference_teletan), null
) )
/****************************************************
* DATABASE PASSWORD
****************************************************/
fun databasePassword(): CharArray? = getSharedPreferenceInstance().getString(
CoronaWarnApplication.getAppContext()
.getString(R.string.preference_database_password),
null
)?.toCharArray()
fun databasePassword(password: CharArray) {
with(getSharedPreferenceInstance().edit()) {
putString(
CoronaWarnApplication.getAppContext()
.getString(R.string.preference_database_password),
password.toString()
)
commit()
}
}
/**************************************************** /****************************************************
* ENCRYPTED SHARED PREFERENCES HANDLING * ENCRYPTED SHARED PREFERENCES HANDLING
****************************************************/ ****************************************************/
......
...@@ -19,12 +19,14 @@ object SubmissionRepository { ...@@ -19,12 +19,14 @@ object SubmissionRepository {
val testResultValue = val testResultValue =
WebRequestBuilder.asyncGetTestResult(TEST_RESULT_URL, registrationToken) WebRequestBuilder.asyncGetTestResult(TEST_RESULT_URL, registrationToken)
testResult.value = TestResult.fromInt(testResultValue) testResult.value = TestResult.fromInt(testResultValue)
if (testResult == TestResult.POSITIVE) {
LocalData.isAllowedToSubmitDiagnosisKeys(true)
}
val initialTestResultReceivedTimestamp = LocalData.inititalTestResultReceivedTimestamp() val initialTestResultReceivedTimestamp = LocalData.inititalTestResultReceivedTimestamp()
if (initialTestResultReceivedTimestamp == null) { if (initialTestResultReceivedTimestamp == null) {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
LocalData.initialTracingActivationTimestamp(currentTime) LocalData.inititalTestResultReceivedTimestamp(currentTime)
testResultReceivedDate.value = Date(currentTime) testResultReceivedDate.value = Date(currentTime)
} else { } else {
testResultReceivedDate.value = Date(initialTestResultReceivedTimestamp) testResultReceivedDate.value = Date(initialTestResultReceivedTimestamp)
......
...@@ -49,7 +49,7 @@ class TracingIntervalRepository(private val tracingIntervalDao: TracingIntervalD ...@@ -49,7 +49,7 @@ class TracingIntervalRepository(private val tracingIntervalDao: TracingIntervalD
suspend fun createInterval(from: Long, to: Long) { suspend fun createInterval(from: Long, to: Long) {
Log.v(TAG, "Insert Tracing Interval $from, $to") Log.v(TAG, "Insert Tracing Interval $from, $to")
if (to < from) throw IllegalArgumentException("to cannot be after from") if (to < from) throw IllegalArgumentException("to cannot be after or equal from")
tracingIntervalDao.insertInterval(TracingIntervalEntity().apply { tracingIntervalDao.insertInterval(TracingIntervalEntity().apply {
this.from = from this.from = from
this.to = to this.to = to
......
package de.rki.coronawarnapp.transaction package de.rki.coronawarnapp.transaction
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat
import com.google.android.gms.nearby.exposurenotification.ExposureSummary import com.google.android.gms.nearby.exposurenotification.ExposureSummary
import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.exception.RiskLevelCalculationException import de.rki.coronawarnapp.exception.RiskLevelCalculationException
import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
import de.rki.coronawarnapp.notification.NotificationHelper
import de.rki.coronawarnapp.risk.RiskLevel import de.rki.coronawarnapp.risk.RiskLevel
import de.rki.coronawarnapp.risk.RiskLevel.INCREASED_RISK import de.rki.coronawarnapp.risk.RiskLevel.INCREASED_RISK
import de.rki.coronawarnapp.risk.RiskLevel.LOW_LEVEL_RISK import de.rki.coronawarnapp.risk.RiskLevel.LOW_LEVEL_RISK
...@@ -444,6 +448,13 @@ object RiskLevelTransaction : Transaction() { ...@@ -444,6 +448,13 @@ object RiskLevelTransaction : Transaction() {
* @param riskLevel * @param riskLevel
*/ */
private fun updateRiskLevelScore(riskLevel: RiskLevel) { private fun updateRiskLevelScore(riskLevel: RiskLevel) {
val lastCalculatedScore = RiskLevelRepository.getLastCalculatedScore()
if (RiskLevel.riskLevelChangedBetweenLowAndHigh(lastCalculatedScore, riskLevel)) {
NotificationHelper.sendNotification(
CoronaWarnApplication.getAppContext().getString(R.string.notification_body),
NotificationCompat.PRIORITY_HIGH
)
}
RiskLevelRepository.setRiskLevelScore(riskLevel) RiskLevelRepository.setRiskLevelScore(riskLevel)
} }
......
...@@ -7,6 +7,7 @@ import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDia ...@@ -7,6 +7,7 @@ import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDia
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TAN import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TAN
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.SUBMIT_KEYS import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.SUBMIT_KEYS
import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.limitKeyCount
import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHistoryToExternalFormat import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHistoryToExternalFormat
/** /**
...@@ -58,6 +59,7 @@ object SubmitDiagnosisKeysTransaction : Transaction() { ...@@ -58,6 +59,7 @@ object SubmitDiagnosisKeysTransaction : Transaction() {
****************************************************/ ****************************************************/
val temporaryExposureKeyList = executeState(RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY) { val temporaryExposureKeyList = executeState(RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY) {
InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory() InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory()
.limitKeyCount()
.transformKeyHistoryToExternalFormat() .transformKeyHistoryToExternalFormat()
} }
/**************************************************** /****************************************************
......
...@@ -2,4 +2,7 @@ package de.rki.coronawarnapp.ui ...@@ -2,4 +2,7 @@ package de.rki.coronawarnapp.ui
object UiConstants { object UiConstants {
const val INFORMATION_URI = "https://www.bundesregierung.de/c19app-intern" const val INFORMATION_URI = "https://www.bundesregierung.de/c19app-intern"
// todo move to strings if translatable is needed? if yes include regex in CallHelper to filter non-numerical chars excluding '+'
const val TECHNICAL_HOTLINE = "tel:+49 800 7540001"
} }
...@@ -6,7 +6,9 @@ import android.view.View ...@@ -6,7 +6,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import de.rki.coronawarnapp.databinding.FragmentInformationContactBinding import de.rki.coronawarnapp.databinding.FragmentInformationContactBinding
import de.rki.coronawarnapp.ui.BaseFragment import de.rki.coronawarnapp.ui.BaseFragment
import de.rki.coronawarnapp.ui.UiConstants
import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.ui.main.MainActivity
import de.rki.coronawarnapp.util.CallHelper
/** /**
* Basic Fragment which only displays static content. * Basic Fragment which only displays static content.
...@@ -35,5 +37,8 @@ class InformationContactFragment : BaseFragment() { ...@@ -35,5 +37,8 @@ class InformationContactFragment : BaseFragment() {
binding.informationContactHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { binding.informationContactHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener {
(activity as MainActivity).goBack() (activity as MainActivity).goBack()
} }
binding.informationContactNavigationRowPhone.navigationRow.setOnClickListener {
CallHelper.call(this, UiConstants.TECHNICAL_HOTLINE)
}
} }
} }
...@@ -23,11 +23,12 @@ import de.rki.coronawarnapp.util.OpenUrlHelper ...@@ -23,11 +23,12 @@ import de.rki.coronawarnapp.util.OpenUrlHelper
/** /**
* After the user has finished the onboarding this fragment will be the heart of the application. * After the user has finished the onboarding this fragment will be the heart of the application.
* Two VieModels are needed that this fragment shows all relevant information to the user. * Three ViewModels are needed that this fragment shows all relevant information to the user.
* Also the Menu is set here. * Also the Menu is set here.
* *
* @see tracingViewModel * @see tracingViewModel
* @see settingsViewModel * @see settingsViewModel
* @see submissionViewModel
* @see PopupMenu * @see PopupMenu
*/ */
class MainFragment : BaseFragment() { class MainFragment : BaseFragment() {
...@@ -86,6 +87,11 @@ class MainFragment : BaseFragment() { ...@@ -86,6 +87,11 @@ class MainFragment : BaseFragment() {
MainFragmentDirections.actionMainFragmentToSubmissionResultFragment() MainFragmentDirections.actionMainFragmentToSubmissionResultFragment()
) )
} }
binding.mainTestPositive.submissionStatusCardPositiveResultShowButton.setOnClickListener {
doNavigate(
MainFragmentDirections.actionMainFragmentToSubmissionResultFragment()
)
}
binding.mainTest.submissionStatusCardUnregistered.submissionStatusCardUnregisteredButton.setOnClickListener { binding.mainTest.submissionStatusCardUnregistered.submissionStatusCardUnregisteredButton.setOnClickListener {
doNavigate( doNavigate(
MainFragmentDirections.actionMainFragmentToSubmissionIntroFragment() MainFragmentDirections.actionMainFragmentToSubmissionIntroFragment()
...@@ -150,9 +156,7 @@ class MainFragment : BaseFragment() { ...@@ -150,9 +156,7 @@ class MainFragment : BaseFragment() {
NotificationManagerCompat.from(requireContext()).areNotificationsEnabled() NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()
.toString() .toString()
) )
NotificationHelper.createNotificationChannel()
NotificationHelper.sendNotification( NotificationHelper.sendNotification(
getString(R.string.notification_headline),
getString(R.string.notification_body), getString(R.string.notification_body),
NotificationCompat.PRIORITY_HIGH NotificationCompat.PRIORITY_HIGH
) )
......
package de.rki.coronawarnapp.ui.main package de.rki.coronawarnapp.ui.main
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
...@@ -10,6 +9,7 @@ import de.rki.coronawarnapp.R ...@@ -10,6 +9,7 @@ import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentMainShareBinding import de.rki.coronawarnapp.databinding.FragmentMainShareBinding
import de.rki.coronawarnapp.ui.BaseFragment import de.rki.coronawarnapp.ui.BaseFragment
import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
import de.rki.coronawarnapp.util.ShareHelper
/** /**
* This fragment informs the user about what he is going to share and how he is going to help everybody with this :) * This fragment informs the user about what he is going to share and how he is going to help everybody with this :)
...@@ -43,20 +43,10 @@ class MainShareFragment : BaseFragment() { ...@@ -43,20 +43,10 @@ class MainShareFragment : BaseFragment() {
private fun setButtonOnClickListener() { private fun setButtonOnClickListener() {
binding.mainShareButton.setOnClickListener { binding.mainShareButton.setOnClickListener {
share() ShareHelper.shareText(this, getString(R.string.main_share_message), null)
} }
binding.mainShareHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { binding.mainShareHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener {
(activity as MainActivity).goBack() (activity as MainActivity).goBack()
} }
} }
// TODO move to helper
private fun share() {
val share = Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, getString(R.string.main_share_message))
}, null)
startActivity(share)
}
} }
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