diff --git a/.circleci/config.yml b/.circleci/config.yml index 61e9a9dbb61..2a7aa1d2d8a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,19 +8,16 @@ commands: jobs: quickBuildReleaseWithTestsAndChecks: executor: android/android - steps: - checkout - install-ndk: - ndk-sha: "50250fcba479de477b45801e2699cca47f7e1267" - ndk-version: "android-ndk-r21b" + ndk-sha: "c81a5bcb4672a18d3647bf6898cd4dbcb978d0e8" + ndk-version: "android-ndk-r21c" - restore-build-cache - restore_cache: key: jars-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }}-{{ checksum "Server-Protocol-Buffer/build.gradle" }} - run: - name: Sudo GradleWrapper - command: chmod +x ./gradlew - - run: + name: Quick Build command: ./gradlew quickBuild environment: JVM_OPTS: -Xmx2048m diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 534f5fe634d..ab1083da7e6 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -22,9 +22,10 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: "androidx.navigation.safeargs.kotlin" +apply plugin: 'jacoco' android { - ndkVersion "21.1.6352462" + ndkVersion "21.2.6472646" compileSdkVersion 29 buildToolsVersion "29.0.3" @@ -32,8 +33,8 @@ android { applicationId 'de.rki.coronawarnapp' minSdkVersion 23 targetSdkVersion 29 - versionCode 7 - versionName "0.8.0" + versionCode 8 + versionName "0.8.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "DOWNLOAD_CDN_URL", "\"$DOWNLOAD_CDN_URL\"" @@ -62,14 +63,14 @@ android { buildTypes { release { - minifyEnabled false - shrinkResources false + minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } releaseForTest { applicationIdSuffix '.dev' - minifyEnabled false - shrinkResources false + minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } @@ -86,18 +87,16 @@ android { } } - dataBinding { - enabled true - } + buildFeatures { + dataBinding { + enabled true + } - viewBinding { - enabled true + viewBinding { + enabled true + } } -// To inline the bytecode built with JVM target 1.8 into -// bytecode that is being built with JVM target 1.6. (e.g. navArgs) - - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -135,6 +134,13 @@ android { } +task jacocoTestReport(type: JacocoReport, dependsOn: ['testDeviceReleaseUnitTest']) { + reports { + xml.enabled = true + html.enabled = true + } +} + dependencies { api fileTree(dir: 'libs', include: ['play-services-nearby-18.0.2-eap.aar']) implementation project(":Server-Protocol-Buffer") @@ -153,6 +159,7 @@ dependencies { implementation 'androidx.work:work-runtime-ktx:2.3.4' implementation 'android.arch.lifecycle:extensions:1.1.1' implementation 'com.android.volley:volley:1.1.1' + implementation 'com.squareup.okhttp3:okhttp:4.7.2' implementation 'com.google.android.play:core:1.7.3' implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.guava:guava:29.0-android' diff --git a/Corona-Warn-App/proguard-rules.pro b/Corona-Warn-App/proguard-rules.pro index 76f1db035e1..d7f94bd58f3 100644 --- a/Corona-Warn-App/proguard-rules.pro +++ b/Corona-Warn-App/proguard-rules.pro @@ -16,11 +16,15 @@ private public protected *; } +-keep class * extends com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite { *; } +-keep class net.sqlcipher.** { *; } +-dontwarn net.sqlcipher.** + # Uncomment this to preserve the line number information for # debugging stack traces. -keepattributes SourceFile,LineNumberTable --dontobfuscate +#-dontobfuscate # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ExampleInstrumentedTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ExampleInstrumentedTest.kt deleted file mode 100644 index 722ed8fbcca..00000000000 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package de.rki.coronawarnapp - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("de.rki.coronawarnapp", appContext.packageName) - } -} 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 668dd8864f5..3e151d6899f 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 @@ -1,15 +1,21 @@ package de.rki.coronawarnapp +import android.annotation.SuppressLint +import android.app.Activity import android.app.Application import android.content.Context +import android.content.pm.ActivityInfo +import android.os.Bundle import android.util.Log +import android.view.WindowManager 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(), LifecycleObserver { +class CoronaWarnApplication : Application(), LifecycleObserver, + Application.ActivityLifecycleCallbacks { companion object { val TAG: String? = CoronaWarnApplication::class.simpleName @@ -31,6 +37,7 @@ class CoronaWarnApplication : Application(), LifecycleObserver { NotificationHelper.createNotificationChannel() super.onCreate() ProcessLifecycleOwner.get().lifecycle.addObserver(this) + registerActivityLifecycleCallbacks(this) } /** @@ -50,4 +57,39 @@ class CoronaWarnApplication : Application(), LifecycleObserver { isAppInForeground = true Log.v(TAG, "App foregrounded") } + + override fun onActivityPaused(activity: Activity) { + // does not override function. Empty on intention + } + + override fun onActivityStarted(activity: Activity) { + // does not override function. Empty on intention + } + + override fun onActivityDestroyed(activity: Activity) { + // does not override function. Empty on intention + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { + // does not override function. Empty on intention + } + + override fun onActivityStopped(activity: Activity) { + // does not override function. Empty on intention + } + + @SuppressLint("SourceLockedOrientationActivity") + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + // prevents screenshot of the app for all activities + activity.window.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) + // set screen orientation to portrait + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT + } + + override fun onActivityResumed(activity: Activity) { + // does not override function. Empty on intention + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/TestForAPIFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/TestForAPIFragment.kt index 935fd3a7723..907d9c8eea6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/TestForAPIFragment.kt @@ -35,6 +35,7 @@ import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange import de.rki.coronawarnapp.sharing.ExposureSharingService import de.rki.coronawarnapp.storage.AppDatabase import de.rki.coronawarnapp.storage.ExposureSummaryRepository +import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository import de.rki.coronawarnapp.transaction.RiskLevelTransaction import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction @@ -76,6 +77,8 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel companion object { val TAG: String? = TestForAPIFragment::class.simpleName + const val CONFIG_SCORE = 8 + fun keysToJson(keys: List): String { return Gson().toJson(keys).toString() } @@ -308,6 +311,7 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel showToast("No other keys provided. Please fill the EditText with the JSON containing keys") } else { token = UUID.randomUUID().toString() + LocalData.googleApiToken(token) val appleKeyList = mutableListOf() @@ -344,7 +348,7 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API InternalExposureNotificationClient.asyncProvideDiagnosisKeys( googleFileList, - ExposureConfiguration.ExposureConfigurationBuilder().build(), + getCustomConfig(), token!! ) showToast("Provided ${appleKeyList.size} keys to Google API with token $token") @@ -442,4 +446,48 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel keysToJson(keys) updateKeysDisplay() } + + private fun getCustomConfig(): ExposureConfiguration = ExposureConfiguration + .ExposureConfigurationBuilder() + .setAttenuationScores( + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE + ) + .setDaysSinceLastExposureScores( + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE + ) + .setDurationScores( + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE + ) + .setTransmissionRiskScores( + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE + ) + .build() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/InvalidQRCodeExcpetion.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/InvalidQRCodeExcpetion.kt deleted file mode 100644 index 28d4aeb35db..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/InvalidQRCodeExcpetion.kt +++ /dev/null @@ -1,3 +0,0 @@ -package de.rki.coronawarnapp.exception - -class InvalidQRCodeExcpetion : Exception("the supplied QR code does not match the patter") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/OkHttp3Stack.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/OkHttp3Stack.kt new file mode 100644 index 00000000000..0e60fa00b1f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/OkHttp3Stack.kt @@ -0,0 +1,190 @@ +package de.rki.coronawarnapp.http + +import android.content.Context +import com.android.volley.Header +import com.android.volley.Request +import com.android.volley.toolbox.BaseHttpStack +import com.android.volley.toolbox.HttpResponse +import de.rki.coronawarnapp.risk.TimeVariables +import okhttp3.Cache +import okhttp3.ConnectionPool +import okhttp3.ConnectionSpec +import okhttp3.Headers +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.TlsVersion +import java.io.File +import java.util.concurrent.TimeUnit + +/** + * Convenience Wrapper used for accessing the OkHTTP Transport layer with the Volley Interfaces. + * Designed for: + * - in-memory connection management + * - disk based caching (10MB) + * - extension based on volleys BaseHttpStack + * - volley request queue + * - synchronous calls executed by asynchronous thread-pool + * + * @param context + * @param interceptors + */ +class OkHttp3Stack(context: Context, interceptors: List) : BaseHttpStack() { + constructor(context: Context) : this(context, emptyList()) + + /** + * List of interceptors, e.g. logging + */ + private val mInterceptors: List = interceptors + + /** + * connection pool held in-memory, especially useful for key retrieval + */ + private val conPool = ConnectionPool() + + /** + * Basic disk cache backed by LRU + */ + private val cache = Cache( + directory = File(context.cacheDir, HTTP_CACHE_NAME), + maxSize = HTTP_CACHE_SIZE + ) + + /** + * lazily initialized client instance of OkHTTP + */ + private val client by lazy { buildClient() } + + /** + * Convenience method to map headers from volley to OkHTTP style. + */ + private fun mapHeaders(responseHeaders: Headers): List
= + responseHeaders.map { Header(it.first, it.second) } + + companion object { + /** + * 10 MiB + */ + private const val HTTP_CACHE_SIZE = 50L * 1024L * 1024L + + /** + * Cache file name + */ + private const val HTTP_CACHE_NAME = "http_cache" + + /** + * Convenience method used for building the correct request type. + */ + private fun setConnectionParametersForRequest( + builder: okhttp3.Request.Builder, + request: Request<*> + ) { + when (request.method) { + Request.Method.DEPRECATED_GET_OR_POST -> { throw IllegalArgumentException("deprecated.") } + Request.Method.GET -> builder.get() + Request.Method.DELETE -> builder.delete(createRequestBody(request)) + Request.Method.POST -> builder.post(createRequestBody(request)!!) + Request.Method.PUT -> builder.put(createRequestBody(request)!!) + Request.Method.HEAD -> builder.head() + Request.Method.OPTIONS -> builder.method("OPTIONS", null) + Request.Method.TRACE -> builder.method("TRACE", null) + Request.Method.PATCH -> builder.patch(createRequestBody(request)!!) + else -> throw IllegalStateException("Unknown method type.") + } + } + + /** + * Convenience method to create a request body based on MediaType + * + * @param volleyRequest + */ + private fun createRequestBody(volleyRequest: Request<*>): RequestBody? = + volleyRequest.body.toRequestBody( + volleyRequest.bodyContentType.toMediaType(), + 0, + volleyRequest.body.size + ) + } + + override fun executeRequest( + request: Request<*>, + additionalHeaders: MutableMap? + ): HttpResponse { + val okHttpRequest = buildRequest(request, additionalHeaders) + + val okHttpCall = client.newCall(okHttpRequest) + val okHttpResponse = okHttpCall.execute() + + val code = okHttpResponse.code + val body = okHttpResponse.body + val content = body?.byteStream() + val contentLength = body?.contentLength()?.toInt() ?: 0 + val responseHeaders = mapHeaders(okHttpResponse.headers) + return HttpResponse(code, responseHeaders, contentLength, content) + } + + /** + * Wrapper around the OkHTTP request builder used to set header fields, url and connection params. + * + * @param request + * @param additionalHeaders + */ + private fun buildRequest(request: Request<*>, additionalHeaders: MutableMap?): + okhttp3.Request { + val okHttpRequestBuilder = okhttp3.Request.Builder() + okHttpRequestBuilder.url(request.url) + + val headers = request.headers + headers.forEach { + okHttpRequestBuilder.addHeader(it.key, it.value) + } + additionalHeaders?.forEach { + okHttpRequestBuilder.addHeader(it.key, it.value) + } + + setConnectionParametersForRequest(okHttpRequestBuilder, request) + + return okHttpRequestBuilder.build() + } + + /** + * Helper method used to build the client with connection pool, timeout values, caching, + * connection specs, interceptors and other generic meta-info. + */ + private fun buildClient(): OkHttpClient { + val clientBuilder = OkHttpClient.Builder() + + val timeoutMs = TimeVariables.getTransactionTimeout() + clientBuilder.connectTimeout(timeoutMs, TimeUnit.MILLISECONDS) + clientBuilder.readTimeout(timeoutMs, TimeUnit.MILLISECONDS) + clientBuilder.writeTimeout(timeoutMs, TimeUnit.MILLISECONDS) + clientBuilder.callTimeout(timeoutMs, TimeUnit.MILLISECONDS) + + clientBuilder.connectionPool(conPool) + + cache.evictAll() + clientBuilder.cache(cache) + + val spec: ConnectionSpec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3) + .allEnabledCipherSuites() // TODO clarify more concrete Ciphers + .build() + + clientBuilder.connectionSpecs(listOf(spec)) + + // TODO add certificate pinning +// val certificatePinner = CertificatePinner.Builder() +// .add( +// "x.de", +// "sha256/base64" +// ) +// .build() +// clientBuilder.certificatePinner(certificatePinner) + + mInterceptors.forEach { clientBuilder.addInterceptor(it) } + + return clientBuilder.build() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/RequestQueueHolder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/RequestQueueHolder.kt index c4e8daf6359..2c16c7709b9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/RequestQueueHolder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/RequestQueueHolder.kt @@ -2,22 +2,22 @@ package de.rki.coronawarnapp.http import com.android.volley.Request import com.android.volley.RequestQueue -import com.android.volley.toolbox.Volley import de.rki.coronawarnapp.CoronaWarnApplication +import de.rki.coronawarnapp.util.security.SecurityHelper /** * Request queue holder used to reference a singleton of a Volley request queue for simple web requests. - * For more complex queries we use the os-owned DownloadManager. + * The Singleton is used here solely for Thread-Management, actual requests are executed by an OkHTTP + * Transport Layer */ object RequestQueueHolder { - /** * lazily initialized singleton reference to a request queue. */ private val requestQueue: RequestQueue by lazy { // applicationContext is key, it keeps you from leaking the // Activity or BroadcastReceiver if someone passes one in. - Volley.newRequestQueue(CoronaWarnApplication.getAppContext()) + SecurityHelper.getPinnedWebStack(CoronaWarnApplication.getAppContext()) } /** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/ApplicationConfigurationRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/ApplicationConfigurationRequest.kt index b0cb716ce22..a12c88fed40 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/ApplicationConfigurationRequest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/ApplicationConfigurationRequest.kt @@ -24,7 +24,7 @@ class ApplicationConfigurationRequest( companion object { private val TAG: String? = ApplicationConfigurationRequest::class.simpleName - private const val SOFT_TTL = 5 * 60 * 1000 // in 3 minutes cache will be hit, but also refreshed on background + private const val SOFT_TTL = 5 * 60 * 1000 // in 5 minutes cache will be hit, but also refreshed on background private const val TTL = 1 * 60 * 60 * 1000 // in 1 hours this cache entry expires completely } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionConstants.kt index 7798f280359..6edc4ce1bfc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionConstants.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionConstants.kt @@ -19,7 +19,4 @@ object SubmissionConstants { val REGISTRATION_TOKEN_URL = "$VERSIONED_VERIFICATION_CDN_URL/$REGISTRATION_TOKEN" val TEST_RESULT_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TEST_RESULT" val TAN_REQUEST_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TAN" - - val QR_CODE_VALIDATION_REGEX = - "[0-9A-Fa-f]{6}-[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}".toRegex() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt index 77f4d57c139..ad23d8a0d3b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt @@ -1,11 +1,9 @@ package de.rki.coronawarnapp.service.submission -import de.rki.coronawarnapp.exception.InvalidQRCodeExcpetion import de.rki.coronawarnapp.exception.NoGUIDOrTANSetException import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException import de.rki.coronawarnapp.http.WebRequestBuilder import de.rki.coronawarnapp.service.submission.SubmissionConstants.QR_CODE_KEY_TYPE -import de.rki.coronawarnapp.service.submission.SubmissionConstants.QR_CODE_VALIDATION_REGEX import de.rki.coronawarnapp.service.submission.SubmissionConstants.REGISTRATION_TOKEN_URL import de.rki.coronawarnapp.service.submission.SubmissionConstants.TAN_REQUEST_URL import de.rki.coronawarnapp.service.submission.SubmissionConstants.TELE_TAN_KEY_TYPE @@ -63,11 +61,19 @@ object SubmissionService { SubmitDiagnosisKeysTransaction.start(registrationToken) } - fun validateAndStoreTestGUID(testGUID: String) { - val regexMatch = QR_CODE_VALIDATION_REGEX.find(testGUID) ?: throw InvalidQRCodeExcpetion() - LocalData.testGUID(regexMatch.value) + /** + * extracts the GUID from [scanResult]. Returns null if it does not match the required pattern + */ + fun extractGUID(scanResult: String): String? { + val potentialGUID = scanResult.substringAfterLast("?", "") + return if (potentialGUID.isEmpty()) + null + else + potentialGUID } + fun storeTestGUID(guid: String) = LocalData.testGUID(guid) + fun deleteTestGUID() { LocalData.testGUID(null) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt index 7725698b6db..1988d0d4d7a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt @@ -79,7 +79,6 @@ object LocalData { .getString(R.string.preference_initial_tracing_activation_time), value ) - commit() } /** @@ -114,7 +113,6 @@ object LocalData { ), value ?: 0L ) - commit() } /** @@ -145,7 +143,6 @@ object LocalData { .getString(R.string.preference_total_non_active_tracing), value ?: 0L ) - commit() } } @@ -186,7 +183,6 @@ object LocalData { .getString(R.string.preference_m_timestamp_diagnosis_keys_fetch), value?.time ?: 0L ) - commit() } } @@ -211,7 +207,6 @@ object LocalData { .getString(R.string.preference_m_timestamp_manual_diagnosis_keys_retrieval), value ) - commit() } /**************************************************** @@ -240,7 +235,6 @@ object LocalData { .getString(R.string.preference_m_string_google_api_token), value ) - commit() } /**************************************************** @@ -268,7 +262,6 @@ object LocalData { .getString(R.string.preference_notifications_risk_enabled), !isNotificationsRiskEnabled() ) - commit() } fun isNotificationsTestEnabled(): Boolean = getSharedPreferenceInstance().getBoolean( @@ -283,7 +276,6 @@ object LocalData { .getString(R.string.preference_notifications_test_enabled), !isNotificationsTestEnabled() ) - commit() } /** @@ -306,7 +298,6 @@ object LocalData { .getString(R.string.preference_background_job_allowed), !isBackgroundJobEnabled() ) - commit() } /** @@ -329,7 +320,6 @@ object LocalData { .getString(R.string.preference_mobile_data_allowed), !isMobileDataEnabled() ) - commit() } /**************************************************** @@ -359,7 +349,6 @@ object LocalData { .getString(R.string.preference_m_registration_token), value ) - commit() } } @@ -370,7 +359,6 @@ object LocalData { .getString(R.string.preference_initial_result_received_time), value ) - commit() } fun inititalTestResultReceivedTimestamp(): Long? { @@ -393,7 +381,6 @@ object LocalData { .getString(R.string.preference_device_pairing_successful_time), value ) - commit() } fun devicePairingSuccessfulTimestamp(): Long? { @@ -411,7 +398,6 @@ object LocalData { .getString(R.string.preference_number_successful_submissions), value ) - commit() } fun numberOfSuccessfulSubmissions(): Int { @@ -435,7 +421,6 @@ object LocalData { .getString(R.string.preference_m_test_guid), value ) - commit() } } @@ -452,7 +437,6 @@ object LocalData { .getString(R.string.preference_m_auth_code), value ) - commit() } } @@ -463,7 +447,6 @@ object LocalData { .getString(R.string.preference_m_is_allowed_to_submit_diagnosis_keys), isAllowedToSubmitDiagnosisKeys ) - commit() } } @@ -480,7 +463,6 @@ object LocalData { CoronaWarnApplication.getAppContext().getString(R.string.preference_teletan), value ) - commit() } fun teletan(): String? = getSharedPreferenceInstance().getString( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt index db4794e2387..918f0ad3a4b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt @@ -92,8 +92,8 @@ object SettingsRepository { * * @see ConnectivityHelper */ - fun refreshBluetoothEnabled() { - isBluetoothEnabled.value = ConnectivityHelper.isBluetoothEnabled() + fun updateBluetoothEnabled(value: Boolean) { + isBluetoothEnabled.postValue(value) } /** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt index f71a30643c7..595f3580cbd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt @@ -12,7 +12,8 @@ import de.rki.coronawarnapp.util.ConnectivityHelper import de.rki.coronawarnapp.worker.BackgroundWorkScheduler /** - * This activity holds all the fragments (except onboarding) and also registers a listener for the connectivity to update ui regarding. + * This activity holds all the fragments (except onboarding) and also registers a listener for + * connectivity and bluetooth to update the ui. * * @see SettingsViewModel * @see ConnectivityHelper @@ -28,16 +29,31 @@ class MainActivity : AppCompatActivity() { private lateinit var settingsViewModel: SettingsViewModel - private val callback = object : ConnectivityHelper.NetworkCallback() { + /** + * Register connection callback. + */ + private val callbackNetwork = object : ConnectivityHelper.NetworkCallback() { override fun onNetworkAvailable() { settingsViewModel.updateConnectionEnabled(true) } - override fun onNetworkUnavailable() { settingsViewModel.updateConnectionEnabled(false) } } + /** + * Register bluetooth callback. + */ + private val callbackBluetooth = object : ConnectivityHelper.BluetoothCallback() { + override fun onBluetoothAvailable() { + settingsViewModel.updateBluetoothEnabled(true) + } + + override fun onBluetoothUnavailable() { + settingsViewModel.updateBluetoothEnabled(false) + } + } + init { scheduleWork() } @@ -48,14 +64,22 @@ class MainActivity : AppCompatActivity() { settingsViewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java) } + /** + * Register network and bluetooth callback. + */ override fun onResume() { super.onResume() - ConnectivityHelper.registerNetworkStatusCallback(this, callback) + ConnectivityHelper.registerNetworkStatusCallback(this, callbackNetwork) + ConnectivityHelper.registerBluetoothStatusCallback(this, callbackBluetooth) } + /** + * Unregister network and bluetooth callback. + */ override fun onPause() { - ConnectivityHelper.unregisterNetworkStatusCallback(this, callback) super.onPause() + ConnectivityHelper.unregisterNetworkStatusCallback(this, callbackNetwork) + ConnectivityHelper.unregisterBluetoothStatusCallback(this, callbackBluetooth) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -67,11 +91,15 @@ class MainActivity : AppCompatActivity() { ) } + /** + * Function is called from back buttons in fragments. + */ fun goBack() { onBackPressed() } - // Scheduling for a Download of Keys every Hour. - // private fun scheduleDiagnosisKeysDownload() = DiagnosisKeyWorkerScheduler.startWork() + /** + * Scheduling for a download of keys every hour. + */ private fun scheduleWork() = BackgroundWorkScheduler.startWorkScheduler() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt index 3c6049c93a1..5d2606f2096 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt @@ -6,7 +6,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.PopupMenu -import android.widget.Toast import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.fragment.app.activityViewModels @@ -69,7 +68,6 @@ class MainFragment : BaseFragment() { tracingViewModel.refreshIsTracingEnabled() tracingViewModel.refreshActiveTracingDaysInRetentionPeriod() settingsViewModel.refreshBackgroundJobEnabled() - settingsViewModel.refreshBluetoothEnabled() TimerHelper.checkManualKeyRetrievalTimer() if (submissionViewModel.deviceRegistered) { submissionViewModel.refreshTestResult() @@ -128,11 +126,7 @@ class MainFragment : BaseFragment() { popup.setOnMenuItemClickListener { return@setOnMenuItemClickListener when (it.itemId) { R.id.menu_help -> { - Toast.makeText( - requireContext(), - "Help Navigation isn't implemented", - Toast.LENGTH_LONG - ).show() + doNavigate(MainFragmentDirections.actionMainFragmentToMainOverviewFragment()) true } R.id.menu_information -> { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt new file mode 100644 index 00000000000..5a007838f7b --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt @@ -0,0 +1,44 @@ +package de.rki.coronawarnapp.ui.main + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import de.rki.coronawarnapp.databinding.FragmentMainOverviewBinding +import de.rki.coronawarnapp.ui.BaseFragment + +/** + * The fragment displays static informative content to the user + * and represents one way to gain more detailed understanding of the + * app and its content. + * + */ + +class MainOverviewFragment : BaseFragment() { + + companion object { + private val TAG: String? = MainOverviewFragment::class.simpleName + } + + private lateinit var binding: FragmentMainOverviewBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentMainOverviewBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setButtonOnClickListener() + } + + private fun setButtonOnClickListener() { + binding.mainOverviewHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { + (activity as MainActivity).goBack() + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt index 7984e7c41fc..11f6fb9ba9e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt @@ -37,7 +37,7 @@ class OnboardingNotificationsFragment : BaseFragment() { } private fun setButtonOnClickListener() { - binding.onboardingButtonFinish.setOnClickListener { + binding.onboardingButtonNext.setOnClickListener { navigateToMain() } binding.onboardingButtonBack.buttonIcon.setOnClickListener { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt index 341a61b48ea..15cc2dce96a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt @@ -82,6 +82,7 @@ class OnboardingTracingFragment : BaseFragment(), R.string.onboarding_tracing_dialog_body, R.string.onboarding_tracing_dialog_button_positive, R.string.onboarding_tracing_dialog_button_negative, + true, { navigate() }) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsFragment.kt index e31744b38b6..0e36914cd7a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsFragment.kt @@ -59,7 +59,7 @@ class SettingsFragment : BaseFragment() { val tracingRow = binding.settingsTracing.settingsRow val notificationRow = binding.settingsNotifications.settingsRow val resetRow = binding.settingsReset - val goBack = binding.settingsHeader.settingsDetailsHeaderButtonBack.buttonIcon + val goBack = binding.settingsHeader.informationHeader.headerButtonBack.buttonIcon resetRow.setOnClickListener { doNavigate( SettingsFragmentDirections.actionSettingsFragmentToSettingsResetFragment() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt index 570453ad966..7e335ef7c0c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt @@ -62,7 +62,7 @@ class SettingsNotificationFragment : Fragment() { // Settings val settingsRow = binding.settingsNavigationRowSystem.navigationRow val goBack = - binding.settingsDetailsHeaderNotifications.settingsDetailsHeaderButtonBack.buttonIcon + binding.settingsDetailsHeaderNotifications.informationHeader.headerButtonBack.buttonIcon // Update Risk updateRiskNotificationSwitch.setOnCheckedChangeListener { _, _ -> // android calls this listener also on start, so it has to be verified if the user pressed the switch diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetFragment.kt index c8c4ea02629..33ec04776d2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetFragment.kt @@ -55,7 +55,7 @@ class SettingsResetFragment : BaseFragment() { binding.settingsResetButtonCancel.setOnClickListener { (activity as MainActivity).goBack() } - binding.settingsDetailsHeaderReset.settingsDetailsHeaderButtonBack.buttonIcon.setOnClickListener { + binding.settingsDetailsHeaderReset.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { (activity as MainActivity).goBack() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt index 2b88b3c5002..89c4cdd24c7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt @@ -63,7 +63,6 @@ class SettingsTracingFragment : BaseFragment(), super.onResume() // refresh required data tracingViewModel.refreshIsTracingEnabled() - settingsViewModel.refreshBluetoothEnabled() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -103,7 +102,7 @@ class SettingsTracingFragment : BaseFragment(), } } } - binding.settingsTracingHeader.settingsDetailsHeaderButtonBack.buttonIcon.setOnClickListener { + binding.settingsTracingHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { (activity as MainActivity).goBack() } binding.settingsTracingStatusBluetooth.tracingStatusCardButton.setOnClickListener { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragment.kt index f362685bee6..c7e4bf5b4bf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragment.kt @@ -1,7 +1,5 @@ package de.rki.coronawarnapp.ui.submission -import android.Manifest -import android.content.pm.PackageManager import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -9,13 +7,11 @@ import android.view.ViewGroup import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionDispatcherBinding import de.rki.coronawarnapp.ui.BaseFragment -import de.rki.coronawarnapp.util.CameraPermissionHelper import de.rki.coronawarnapp.util.DialogHelper class SubmissionDispatcherFragment : BaseFragment() { companion object { - private const val REQUEST_CAMERA_PERMISSION_CODE = 1 private val TAG: String? = SubmissionDispatcherFragment::class.simpleName } @@ -38,7 +34,7 @@ class SubmissionDispatcherFragment : BaseFragment() { private fun setButtonOnClickListener() { binding.submissionDispatcherQr.dispatcherCard.setOnClickListener { - checkForCameraPermission() + checkForDataPrivacyPermission() } binding.submissionDispatcherTanCode.dispatcherCard.setOnClickListener { doNavigate( @@ -54,64 +50,23 @@ class SubmissionDispatcherFragment : BaseFragment() { } } - private fun checkForCameraPermission() { - if (!CameraPermissionHelper.hasCameraPermission(requireActivity())) { - if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { - showCameraPermissionRationaleDialog() - } else { - requestPermissions( - arrayOf(Manifest.permission.CAMERA), - REQUEST_CAMERA_PERMISSION_CODE - ) - } - } else { - cameraPermissionIsGranted() - } - } - - private fun showCameraPermissionRationaleDialog() { + private fun checkForDataPrivacyPermission() { val cameraPermissionRationaleDialogInstance = DialogHelper.DialogInstance( requireActivity(), - R.string.submission_qr_code_scan_permission_rationale_dialog_headline, - R.string.submission_qr_code_scan_permission_rationale_dialog_body, - R.string.submission_qr_code_scan_permission_rationale_dialog_button_positive, - R.string.submission_qr_code_scan_permission_rationale_dialog_button_negative, + R.string.submission_dispatcher_qr_privacy_dialog_headline, + R.string.submission_dispatcher_qr_privacy_dialog_body, + R.string.submission_dispatcher_qr_privacy_dialog_button_positive, + R.string.submission_dispatcher_qr_privacy_dialog_button_negative, + true, { - requestPermissions( - arrayOf(Manifest.permission.CAMERA), - REQUEST_CAMERA_PERMISSION_CODE - ) + privacyPermissionIsGranted() } ) DialogHelper.showDialog(cameraPermissionRationaleDialogInstance) } - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - cameraPermissionIsGranted() - } else { - showCameraPermissionDeniedDialog() - } - } - - private fun showCameraPermissionDeniedDialog() { - val cameraPermissionDeniedDialogInstance = DialogHelper.DialogInstance( - requireActivity(), - R.string.submission_qr_code_scan_permission_denied_dialog_headline, - R.string.submission_qr_code_scan_permission_denied_dialog_body, - R.string.submission_qr_code_scan_permission_denied_dialog_button_positive - ) - - DialogHelper.showDialog(cameraPermissionDeniedDialogInstance) - } - - private fun cameraPermissionIsGranted() { + private fun privacyPermissionIsGranted() { doNavigate( SubmissionDispatcherFragmentDirections .actionSubmissionDispatcherFragmentToSubmissionQRCodeScanFragment() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt index 7f22372c6ed..ad089960cb4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.ui.submission +import android.Manifest import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -12,7 +13,9 @@ import com.journeyapps.barcodescanner.DefaultDecoderFactory import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeScanBinding import de.rki.coronawarnapp.ui.BaseFragment +import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel +import de.rki.coronawarnapp.util.CameraPermissionHelper import de.rki.coronawarnapp.util.DialogHelper /** @@ -21,6 +24,7 @@ import de.rki.coronawarnapp.util.DialogHelper class SubmissionQRCodeScanFragment : BaseFragment() { companion object { + private const val REQUEST_CAMERA_PERMISSION_CODE = 1 private val TAG: String? = SubmissionQRCodeScanFragment::class.simpleName } @@ -86,6 +90,7 @@ class SubmissionQRCodeScanFragment : BaseFragment() { R.string.submission_qr_code_scan_successful_dialog_body, R.string.submission_qr_code_scan_successful_dialog_button_positive, R.string.submission_qr_code_scan_successful_dialog_button_negative, + true, { doNavigate( SubmissionQRCodeScanFragmentDirections @@ -108,6 +113,7 @@ class SubmissionQRCodeScanFragment : BaseFragment() { R.string.submission_qr_code_scan_invalid_dialog_body, R.string.submission_qr_code_scan_invalid_dialog_button_positive, R.string.submission_qr_code_scan_invalid_dialog_button_negative, + true, ::startDecode, ::navigateToDispatchScreen ) @@ -117,10 +123,45 @@ class SubmissionQRCodeScanFragment : BaseFragment() { override fun onResume() { super.onResume() - binding.submissionQrCodeScanPreview.resume() - startDecode() + + if (!CameraPermissionHelper.hasCameraPermission(requireActivity())) { + if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { + showCameraPermissionRationaleDialog() + } else { + requestCameraPermission() + } + } else { + binding.submissionQrCodeScanPreview.resume() + startDecode() + } + } + + private fun showCameraPermissionRationaleDialog() { + val cameraPermissionRationaleDialogInstance = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_qr_code_scan_permission_rationale_dialog_headline, + R.string.submission_qr_code_scan_permission_rationale_dialog_body, + R.string.submission_qr_code_scan_permission_rationale_dialog_button_positive, + R.string.submission_qr_code_scan_permission_rationale_dialog_button_negative, + false, + { + requestCameraPermission() + }, + { + goBack() + } + ) + + DialogHelper.showDialog(cameraPermissionRationaleDialogInstance) } + private fun goBack() = (activity as MainActivity).goBack() + + private fun requestCameraPermission() = requestPermissions( + arrayOf(Manifest.permission.CAMERA), + REQUEST_CAMERA_PERMISSION_CODE + ) + override fun onPause() { super.onPause() binding.submissionQrCodeScanPreview.pause() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSuccessDialogFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSuccessDialogFragment.kt index 2047a329d10..de42246623d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSuccessDialogFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSuccessDialogFragment.kt @@ -33,7 +33,7 @@ class SubmissionSuccessDialogFragment : DialogFragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - binding.smVerificationSuccessButton.setOnClickListener { + binding.submissionVerificationSuccessButton.setOnClickListener { Log.i(TAG, "button OK clicked") dismiss() findNavController().navigate( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt new file mode 100644 index 00000000000..dc4e6a9af5e --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt @@ -0,0 +1,147 @@ +package de.rki.coronawarnapp.ui.view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import androidx.core.content.ContextCompat +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.ViewCircleProgressBinding +import de.rki.coronawarnapp.risk.TimeVariables + +/** + * Used on the tracing details fragment without text and also on the risk card with the progress + * number in the circle. + * + * @param context + * @param attrs + * @param defStyleAttr + */ +class CircleProgress @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + companion object { + private const val START_ANGLE = 270f + private const val FULL_CIRCLE = 360f + private const val DEFAULT_WIDTH = 10f + private val DEFAULT_MAX_PROGRESS = TimeVariables.getDefaultRetentionPeriodInDays().toFloat() + } + + private val circlePaint: Paint + private val progressPaint: Paint + private val rect = RectF() + + private var binding: ViewCircleProgressBinding + private var centerX: Float = 0f + private var centerY: Float = 0f + private var radius: Float = 0f + private var progressWidth: Float = 0f + private var disableText: Boolean = false + + /** + * Setter for progress. Text and icon depend on the progress value. + * The visibility is also influenced by the disableText attribute. + */ + var progress: Float = 0F + set(value) { + field = value + val body = binding.circleProgressBody + val icon = binding.circleProgressIcon + // text visibility + if (disableText || value == DEFAULT_MAX_PROGRESS) { + body.visibility = View.GONE + } else { + body.visibility = View.VISIBLE + body.text = value.toInt().toString() + } + // icon visibility + if (value == DEFAULT_MAX_PROGRESS) { + icon.visibility = View.VISIBLE + } else { + icon.visibility = View.GONE + } + invalidate() + } + + /** + * Initialise the view with the following attributes or some default values: + * - circleColor + * - textColor + * - disableText + * - progressWidth + */ + init { + setWillNotDraw(false) + binding = ViewCircleProgressBinding.inflate(LayoutInflater.from(context), this) + val styleAttrs = context.obtainStyledAttributes(attrs, R.styleable.CircleProgress) + // attribute circleColor; default = colorGreyLight + val circleColor = styleAttrs.getColor( + R.styleable.CircleProgress_circleColor, + ContextCompat.getColor(context, R.color.colorGreyLight) + ) + // attribute progressColor; default = colorPrimary + val progressColor = styleAttrs.getColor(R.styleable.CircleProgress_progressColor, + ContextCompat.getColor(context, R.color.colorPrimary)) + // attribute textColor; default = colorGrey + val textColor = styleAttrs.getColor(R.styleable.CircleProgress_textColor, + ContextCompat.getColor(context, R.color.textColorGrey) + ) + // attribute disableText; default = true + disableText = styleAttrs.getBoolean(R.styleable.CircleProgress_disableText, false) + // attribute progressWidth; default = DEFAULT_WIDTH + progressWidth = styleAttrs.getFloat(R.styleable.CircleProgress_circleWidth, DEFAULT_WIDTH) + // attribute progress; default = 0 + progress = styleAttrs.getFloat(R.styleable.CircleProgress_progress, 0F) + // set textColor + val body = binding.circleProgressBody + body.setTextColor(textColor) + // set icon color + val icon = binding.circleProgressIcon + icon.setColorFilter(progressColor, android.graphics.PorterDuff.Mode.SRC_IN) + // circlePaint based on the attributes and default value + circlePaint = Paint().apply { + color = circleColor + style = Paint.Style.STROKE + strokeWidth = progressWidth + isAntiAlias = true + } + // progressPaint based on the attributes and default value + progressPaint = Paint().apply { + color = progressColor + style = Paint.Style.STROKE + strokeWidth = progressWidth + isAntiAlias = true + strokeCap = Paint.Cap.ROUND + } + styleAttrs.recycle() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + centerX = w.toFloat().div(2) + centerY = h.toFloat().div(2) + radius = w.toFloat().div(2).minus(progressWidth) + rect.set( + centerX.minus(radius), + centerY.minus(radius), + centerX.plus(radius), + centerY.plus(radius)) + super.onSizeChanged(w, h, oldw, oldh) + } + + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + canvas?.drawCircle(centerX, centerY, radius, circlePaint) + canvas?.drawArc( + rect, + START_ANGLE, + FULL_CIRCLE.times(progress).div(DEFAULT_MAX_PROGRESS), + false, + progressPaint) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt index ca35fe3a3fe..ada13e7039f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt @@ -93,10 +93,12 @@ class SettingsViewModel : ViewModel() { } /** - * Update connection enabled + * Update bluetooth enabled + * + * @param value */ - fun refreshBluetoothEnabled() { - SettingsRepository.refreshBluetoothEnabled() + fun updateBluetoothEnabled(value: Boolean) { + SettingsRepository.updateBluetoothEnabled(value) } /** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt index e1fda8bb4e7..d32a6100167 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import de.rki.coronawarnapp.exception.ExceptionCategory -import de.rki.coronawarnapp.exception.InvalidQRCodeExcpetion import de.rki.coronawarnapp.exception.report import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData @@ -44,11 +43,12 @@ class SubmissionViewModel : ViewModel() { fun refreshTestResult() = executeRequestWithState(SubmissionRepository::refreshTestResult, _testResultState) - fun validateAndStoreTestGUID(testGUID: String) { - try { - SubmissionService.validateAndStoreTestGUID(testGUID) + fun validateAndStoreTestGUID(scanResult: String) { + val guid = SubmissionService.extractGUID(scanResult) + if (guid != null) { + SubmissionService.storeTestGUID(guid) _scanStatus.value = ScanStatus.SUCCESS - } catch (ex: InvalidQRCodeExcpetion) { + } else { _scanStatus.value = ScanStatus.INVALID } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ConnectivityHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ConnectivityHelper.kt index 580b38223e0..6d2bbcf58a8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ConnectivityHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ConnectivityHelper.kt @@ -1,7 +1,10 @@ package de.rki.coronawarnapp.util import android.bluetooth.BluetoothAdapter +import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities @@ -10,9 +13,67 @@ import android.util.Log import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.report +/** + * Helper for connectivity statuses. + */ object ConnectivityHelper { private val TAG: String? = ConnectivityHelper::class.simpleName + /** + * Register bluetooth state change listener. + * + * @param context the context + * @param callback the bluetooth state callback + * + * @see [BluetoothAdapter.ACTION_STATE_CHANGED] + * @see [BluetoothCallback] + */ + fun registerBluetoothStatusCallback(context: Context, callback: BluetoothCallback) { + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent) { + val action = intent.action + if (BluetoothAdapter.ACTION_STATE_CHANGED == action) { + when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) { + BluetoothAdapter.STATE_OFF -> { + callback.onBluetoothUnavailable() + } + BluetoothAdapter.STATE_ON -> { + callback.onBluetoothAvailable() + } + } + } + } + } + callback.recevier = receiver + context.registerReceiver(callback.recevier, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) + // bluetooth state doesn't change when you register + if (isBluetoothEnabled()) + callback.onBluetoothAvailable() + else + callback.onBluetoothUnavailable() + } + + /** + * Register bluetooth state change listener. + * + * @param context the context + * @param callback the bluetooth state callback + * + * @see [BluetoothCallback] + */ + fun unregisterBluetoothStatusCallback(context: Context, callback: BluetoothCallback) { + context.unregisterReceiver(callback.recevier) + callback.recevier = null + } + + /** + * Unregister network state change callback. + * + * @param context the context + * @param callback the network state callback + * + * @see [ConnectivityManager] + */ fun unregisterNetworkStatusCallback(context: Context, callback: NetworkCallback) { try { val manager = @@ -27,6 +88,16 @@ object ConnectivityHelper { } } + /** + * Register network state change callback. + * + * @param context the context + * @param callback the network state callback + * + * @see [ConnectivityManager] + * @see [NetworkCapabilities] + * @see [NetworkRequest] + */ fun registerNetworkStatusCallback(context: Context, callback: NetworkCallback) { try { // If there are no Wi-Fi or mobile data presented when callback is registered @@ -49,6 +120,13 @@ object ConnectivityHelper { } } + /** + * Get bluetooth enabled status. + * + * @return current bluetooth status + * + * @see [BluetoothAdapter] + */ fun isBluetoothEnabled(): Boolean { val bAdapter = BluetoothAdapter.getDefaultAdapter() if (bAdapter == null) { @@ -58,9 +136,40 @@ object ConnectivityHelper { return bAdapter.isEnabled } + /** + * Abstract bluetooth state change callback. + * + * @see BroadcastReceiver + */ + abstract class BluetoothCallback { + var recevier: BroadcastReceiver? = null + + /** + * Called when bluetooth is turned on. + */ + abstract fun onBluetoothAvailable() + + /** + * Called when bluetooth is turned off. + */ + abstract fun onBluetoothUnavailable() + } + + /** + * Abstract network state change callback. + * + * @see [ConnectivityManager.NetworkCallback] + */ abstract class NetworkCallback : ConnectivityManager.NetworkCallback() { + + /** + * Called when network is available. + */ abstract fun onNetworkAvailable() + /** + * Called when network is unavailable or lost. + */ abstract fun onNetworkUnavailable() override fun onAvailable(network: Network?) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt index 6eacc39b5c2..5e8ba7e359e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt @@ -11,18 +11,20 @@ object DialogHelper { val message: Int, val positiveButton: Int, val negativeButton: Int? = null, + val cancelable: Boolean = true, val positiveButtonFunction: () -> Unit? = {}, val negativeButtonFunction: () -> Unit? = {} ) fun showDialog( dialogInstance: DialogInstance - ) { + ): AlertDialog { val alertDialog: AlertDialog = dialogInstance.activity.let { val builder = AlertDialog.Builder(it) builder.apply { setTitle(dialogInstance.title) setMessage(dialogInstance.message) + setCancelable(dialogInstance.cancelable) setPositiveButton( dialogInstance.positiveButton ) { _, _ -> @@ -39,5 +41,6 @@ object DialogHelper { builder.create() } alertDialog.show() + return alertDialog } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterHelper.kt index 0eaf7646926..0a895a569e7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterHelper.kt @@ -5,6 +5,7 @@ package de.rki.coronawarnapp.util.formatter import android.graphics.drawable.Drawable import android.view.View import de.rki.coronawarnapp.CoronaWarnApplication +import de.rki.coronawarnapp.R /*Style*/ /** @@ -103,3 +104,14 @@ fun formatText(value: Boolean?, stringTrue: Int, stringFalse: Int): String { appContext.getString(stringFalse) } } + +/** + * Formats color to be displayed depending on color id provided with default option + * + * @param color + * @return + */ +fun formatColorIcon(color: Int?): Int { + val appContext = CoronaWarnApplication.getAppContext() + return color ?: appContext.getColor(R.color.colorLight) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationHelper.kt new file mode 100644 index 00000000000..913424b0671 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationHelper.kt @@ -0,0 +1,13 @@ +@file:JvmName("FormatterInformationHelper") + +package de.rki.coronawarnapp.util.formatter + +import de.rki.coronawarnapp.BuildConfig +import de.rki.coronawarnapp.CoronaWarnApplication +import de.rki.coronawarnapp.R + +fun formatVersion(): String { + val appContext = CoronaWarnApplication.getAppContext() + val versionName: String = BuildConfig.VERSION_NAME + return appContext.getString(R.string.information_version).format(versionName) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt index 20392db037e..9ecbe2a7b59 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt @@ -7,7 +7,6 @@ import android.graphics.drawable.Drawable import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R import de.rki.coronawarnapp.risk.RiskLevelConstants -import de.rki.coronawarnapp.risk.TimeVariables import java.text.DateFormat import java.util.Date @@ -24,7 +23,11 @@ import java.util.Date * @return */ private fun isTracingOffRiskLevel(riskLevelScore: Int?): Boolean { - return (riskLevelScore == RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF) + return when (riskLevelScore) { + RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF, + RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS -> true + else -> false + } } /** @@ -84,10 +87,14 @@ fun formatRiskBody(riskLevelScore: Int?): String { */ fun formatRiskSavedRisk(riskLevelScore: Int?, savedRiskLevelScore: Int?): String { val appContext = CoronaWarnApplication.getAppContext() - return if (riskLevelScore == RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF) { + return if ( + riskLevelScore == RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF || + riskLevelScore == RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS + ) { when (savedRiskLevelScore) { RiskLevelConstants.LOW_LEVEL_RISK, - RiskLevelConstants.INCREASED_RISK -> + RiskLevelConstants.INCREASED_RISK, + RiskLevelConstants.UNKNOWN_RISK_INITIAL -> appContext.getString(R.string.risk_card_no_calculation_possible_body_saved_risk) .format(formatRiskLevelHeadline(savedRiskLevelScore, false)) else -> "" @@ -210,10 +217,12 @@ fun formatTimeFetched( appContext.getString(R.string.risk_card_body_not_yet_fetched) } } - RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF -> { + RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF, + RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS -> { when (savedRiskLevelScore) { RiskLevelConstants.LOW_LEVEL_RISK, - RiskLevelConstants.INCREASED_RISK -> { + RiskLevelConstants.INCREASED_RISK, + RiskLevelConstants.UNKNOWN_RISK_INITIAL -> { if (lastTimeDiagnosisKeysFetched != null) { appContext.getString( R.string.risk_card_body_time_fetched, @@ -287,7 +296,6 @@ fun formatRiskDetailsRiskLevelSubtitle(riskLevelScore: Int?): String { * @return */ fun formatRiskDetailsRiskLevelBody(riskLevelScore: Int?, daysSinceLastExposure: Int?): String { - // TODO replace lorem ipsum by text from rki val appContext = CoronaWarnApplication.getAppContext() val daysArg = daysSinceLastExposure.toString() @@ -404,36 +412,6 @@ fun formatRiskContactIcon(riskLevelScore: Int?): Drawable? = R.drawable.ic_risk_card_contact ) -/** - * Formats the risk card icon display of tracing active duration in days - * - * @param activeTracingDaysInRetentionPeriod - * @return - */ -// TODO needs to be replaced by a custom view -fun formatRiskActiveTracingDaysInRetentionPeriodIcon(activeTracingDaysInRetentionPeriod: Long): Drawable? { - val appContext = CoronaWarnApplication.getAppContext() - - return if ( - activeTracingDaysInRetentionPeriod in - 0..TimeVariables.getDefaultRetentionPeriodInDays() - ) { - val iconResString = "ic_risk_card_saved_days_" - val icon = iconResString + - activeTracingDaysInRetentionPeriod.toString() - - appContext.getDrawable( - appContext.resources.getIdentifier( - icon, - "drawable", - appContext.applicationContext.packageName - ) - ) - } else { - appContext.getDrawable(R.drawable.ic_risk_card_saved_days_0) - } -} - /** * Formats the risk card button display for enable tracing depending on risk level and current view * diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt index c4ab75ad0ab..9a8868b34f1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt @@ -23,7 +23,10 @@ import android.content.Context import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKeys +import com.android.volley.RequestQueue +import com.android.volley.toolbox.Volley import de.rki.coronawarnapp.CoronaWarnApplication +import de.rki.coronawarnapp.http.OkHttp3Stack import java.security.KeyStore /** @@ -35,7 +38,7 @@ object SecurityHelper { private val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec) private const val AndroidKeyStore = "AndroidKeyStore" - private val keyStore: KeyStore by lazy { + val keyStore: KeyStore by lazy { KeyStore.getInstance(AndroidKeyStore).also { it.load(null) } @@ -64,4 +67,8 @@ object SecurityHelper { .getKey(masterKeyAlias, null) .toString() .toCharArray() + + fun getPinnedWebStack(appContext: Context): RequestQueue { + return Volley.newRequestQueue(appContext, OkHttp3Stack(appContext)) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt index 15d886892b9..88f2db0be3e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt @@ -109,6 +109,8 @@ object BackgroundWorkScheduler { LocalData.getSharedPreferenceInstance().registerOnSharedPreferenceChangeListener( sharedPrefListener ) + // TODO: Reimplement after clarifications + WorkType.DIAGNOSIS_KEY_BACKGROUND_PERIODIC_WORK.start() } /** diff --git a/Corona-Warn-App/src/main/res/color/card_outdated.xml b/Corona-Warn-App/src/main/res/color/card_outdated.xml index c0151ab68b3..1e68d9dc371 100644 --- a/Corona-Warn-App/src/main/res/color/card_outdated.xml +++ b/Corona-Warn-App/src/main/res/color/card_outdated.xml @@ -2,6 +2,6 @@ - - + + \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable-anydpi/onboarding_placeholder.xml b/Corona-Warn-App/src/main/res/drawable-anydpi/onboarding_placeholder.xml deleted file mode 100644 index 88731561a2d..00000000000 --- a/Corona-Warn-App/src/main/res/drawable-anydpi/onboarding_placeholder.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_illustration_overview.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_illustration_overview.xml new file mode 100644 index 00000000000..44126e7a717 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_illustration_overview.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_overview_1.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_1.xml new file mode 100644 index 00000000000..3201d47f539 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_1.xml @@ -0,0 +1,18 @@ + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_overview_2.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_2.xml new file mode 100644 index 00000000000..d4be2c81cc2 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_2.xml @@ -0,0 +1,18 @@ + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_overview_3.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_3.xml new file mode 100644 index 00000000000..cf2dfa5efe1 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_3.xml @@ -0,0 +1,18 @@ + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_overview_circle.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_circle.xml new file mode 100644 index 00000000000..84d7b629759 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_circle.xml @@ -0,0 +1,12 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days.xml new file mode 100644 index 00000000000..4b9a19681a7 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days.xml @@ -0,0 +1,12 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_0.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_0.xml deleted file mode 100644 index c3b5b3e5222..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_1.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_1.xml deleted file mode 100644 index 2762768ebf5..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_1.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_10.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_10.xml deleted file mode 100644 index e567181a28e..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_10.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_11.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_11.xml deleted file mode 100644 index 8ab8981a8eb..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_11.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_12.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_12.xml deleted file mode 100644 index f671d554549..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_12.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_13.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_13.xml deleted file mode 100644 index 6a237d95e49..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_13.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_14.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_14.xml deleted file mode 100644 index 7c205a9e13a..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_14.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_2.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_2.xml deleted file mode 100644 index ae4e5c340f0..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_2.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_3.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_3.xml deleted file mode 100644 index fe12c457213..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_3.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_4.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_4.xml deleted file mode 100644 index de977128ec5..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_4.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_5.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_5.xml deleted file mode 100644 index e4a8239cf3f..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_5.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_6.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_6.xml deleted file mode 100644 index eb935294177..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_6.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_7.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_7.xml deleted file mode 100644 index 12b66c0ef30..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_7.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_8.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_8.xml deleted file mode 100644 index 504af6ad28b..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_8.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_9.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_9.xml deleted file mode 100644 index fde95e09a24..00000000000 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_9.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/Corona-Warn-App/src/main/res/drawable/ic_settings_reset_circle.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_reset_circle.xml new file mode 100644 index 00000000000..ded4098d212 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_settings_reset_circle.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/Corona-Warn-App/src/main/res/layout/fragment_information.xml b/Corona-Warn-App/src/main/res/layout/fragment_information.xml index 0556b3f77c5..42f5e3ab808 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_information.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_information.xml @@ -2,6 +2,12 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/fragment_onboarding_notifications.xml b/Corona-Warn-App/src/main/res/layout/fragment_onboarding_notifications.xml index 90bf72d714f..1a52491df69 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_onboarding_notifications.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_onboarding_notifications.xml @@ -30,11 +30,11 @@ app:subtitle="@{@string/onboarding_notifications_subtitle}" />