Commit 608d724c authored by stopcovid@lunabee.com's avatar stopcovid@lunabee.com
Browse files

Update to 2.3.0

- Refacto Env constants
- Extract calibration from the config
- New way to calculate if user is at risk with level
parent d41433d0
......@@ -13,13 +13,13 @@ apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
buildToolsVersion '30.0.3'
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
buildConfigField 'String', 'BLE_VERSION', '"2.1.1"'
buildConfigField 'String', 'BLE_VERSION', '"2.1.2"'
}
compileOptions {
......
......@@ -217,21 +217,17 @@ class BleScannerImpl(
)
}
private fun ScanResult.isServiceScan() = scanRecord?.run {
isServiceScan() || isIOSInBackgroundServiceScan()
} ?: false
/**
* Android / iOS service scan in foreground
*/
private fun ScanRecord.isServiceScan() = serviceData?.get(serviceParcelUuid) != null
hasServiceUuid(serviceParcelUuid) || isIOSInBackgroundServiceScan()
} == true
/**
* iOS service scan in background
*/
private fun ScanRecord.isIOSInBackgroundServiceScan() =
(serviceData == null && manufacturerSpecificData?.get(APPLE_MANUFACTURER_ID)
.contentEquals(settings.backgroundServiceManufacturerDataIOS))
serviceData == null && matchesManufacturerDataMask(
APPLE_MANUFACTURER_ID,
settings.backgroundServiceManufacturerDataIOS
)
}
\ No newline at end of file
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Orange / Date - 2021/02/19 - for the TOUS-ANTI-COVID project
*/
package com.orange.proximitynotification.ble.scanner
import android.os.ParcelUuid
import no.nordicsemi.android.support.v18.scanner.ScanRecord
import kotlin.experimental.and
internal fun ScanRecord.hasServiceUuid(serviceUuid: ParcelUuid): Boolean =
serviceUuids?.contains(serviceUuid) == true || serviceData?.get(serviceUuid) != null
internal fun ScanRecord.matchesManufacturerDataMask(manufacturerId: Int, mask: ByteArray): Boolean {
val manufacturerData = getManufacturerSpecificData(manufacturerId)
if (manufacturerData == null || manufacturerData.size != mask.size) {
return false
}
mask.forEachIndexed { index, byte ->
if ((manufacturerData[index] and byte) != byte) {
return false
}
}
return true
}
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Orange / Date - 2021/02/19 - for the TOUS-ANTI-COVID project
*/
package com.orange.proximitynotification.ble.scanner
import android.os.ParcelUuid
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import no.nordicsemi.android.support.v18.scanner.ScanRecord
import org.junit.Test
import org.junit.runner.RunWith
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class ScanRecordExtKtTest {
companion object {
private const val MANUFACTURER_ID = 1
private val MANUFACTURER_DATA_MASK = byteArrayOf(1, 0, 8, 0)
}
@Test
fun `hasServiceUuid given scanRecord without service UUID should return false`() {
// Given
val serviceUUId = ParcelUuid(UUID.randomUUID())
val scanRecord = givenScanRecord()
// When
val result = scanRecord.hasServiceUuid(serviceUUId)
// Then
assertThat(result).isFalse()
}
@Test
fun `hasServiceUuid given scanRecord with expected service UUID should return true`() {
// Given
val serviceUUId = ParcelUuid(UUID.randomUUID())
val scanRecord = givenScanRecord(serviceUuids = listOf(serviceUUId))
// When
val result = scanRecord.hasServiceUuid(serviceUUId)
// Then
assertThat(result).isTrue()
}
@Test
fun `hasServiceUuid given scanRecord with another service UUID should return false`() {
// Given
val serviceUUId = ParcelUuid(UUID.randomUUID())
val otherServiceUUId = ParcelUuid(UUID.randomUUID())
val scanRecord = givenScanRecord(serviceUuids = listOf(otherServiceUUId))
// When
val result = scanRecord.hasServiceUuid(serviceUUId)
// Then
assertThat(result).isFalse()
}
@Test
fun `hasServiceUuid given scanRecord having multiple service UUID including expected one should return true`() {
// Given
val serviceUUId = ParcelUuid(UUID.randomUUID())
val otherServiceUUId = ParcelUuid(UUID.randomUUID())
val scanRecord = givenScanRecord(serviceUuids = listOf(serviceUUId, otherServiceUUId))
// When
val result = scanRecord.hasServiceUuid(serviceUUId)
// Then
assertThat(result).isTrue()
}
@Test
fun `hasServiceUuid given scanRecord with serviceData having data for expected service UUID should return true`() {
// Given
val serviceUUId = ParcelUuid(UUID.randomUUID())
val scanRecord = givenScanRecord(serviceData = mapOf(serviceUUId to byteArrayOf()))
// When
val result = scanRecord.hasServiceUuid(serviceUUId)
// Then
assertThat(result).isTrue()
}
@Test
fun `hasServiceUuid given scanRecord with serviceData having data for another service UUID should return false`() {
// Given
val serviceUUId = ParcelUuid(UUID.randomUUID())
val otherServiceUUId = ParcelUuid(UUID.randomUUID())
val scanRecord = givenScanRecord(serviceData = mapOf(otherServiceUUId to byteArrayOf()))
// When
val result = scanRecord.hasServiceUuid(serviceUUId)
// Then
assertThat(result).isFalse()
}
@Test
fun `matchesManufacturerDataMask given scanRecord without manufacturer data should return false`() {
// Given
val scanRecord = givenScanRecord()
// When
val result = scanRecord.matchesExpectedManufacturerData()
// Then
assertThat(result).isFalse()
}
@Test
fun `matchesManufacturerDataMask given scanRecord with unexpected manufacturerId should return false`() {
// Given
val scanRecord =
givenScanRecord(manufacturerData = mapOf(2 to MANUFACTURER_DATA_MASK.copyOf()))
// When
val result = scanRecord.matchesExpectedManufacturerData()
// Then
assertThat(result).isFalse()
}
@Test
fun `matchesManufacturerDataMask given scanRecord with incorrect manufacturer data return false`() {
// Given
val scanRecord =
givenScanRecord(manufacturerData = mapOf(MANUFACTURER_ID to byteArrayOf(1)))
// When
val result = scanRecord.matchesExpectedManufacturerData()
// Then
assertThat(result).isFalse()
}
@Test
fun `matchesManufacturerDataMask given scanRecord with manufacturer data equal to mask should return true`() {
// Given
val scanRecord =
givenScanRecord(manufacturerData = mapOf(MANUFACTURER_ID to MANUFACTURER_DATA_MASK.copyOf()))
// When
val result = scanRecord.matchesExpectedManufacturerData()
// Then
assertThat(result).isTrue()
}
@Test
fun `matchesManufacturerDataMask given scanRecord with manufacturer data matching mask should return true`() {
// Given
val scanRecord =
givenScanRecord(manufacturerData = mapOf(MANUFACTURER_ID to byteArrayOf(1, 1, 8, 8)))
// When
val result = scanRecord.matchesExpectedManufacturerData()
// Then
assertThat(result).isTrue()
}
@Test
fun `matchesManufacturerDataMask given scanRecord with another manufacturer data matching mask should return true`() {
// Given
val scanRecord =
givenScanRecord(manufacturerData = mapOf(MANUFACTURER_ID to byteArrayOf(1, 0, 10, 0)))
// When
val result = scanRecord.matchesExpectedManufacturerData()
// Then
assertThat(result).isTrue()
}
@Test
fun `matchesManufacturerDataMask given scanRecord without manufacturer data matching mask should return false`() {
// Given
val scanRecord =
givenScanRecord(manufacturerData = mapOf(MANUFACTURER_ID to byteArrayOf(1, 0, 1, 0)))
// When
val result = scanRecord.matchesExpectedManufacturerData()
// Then
assertThat(result).isFalse()
}
private fun ScanRecord.matchesExpectedManufacturerData() =
matchesManufacturerDataMask(MANUFACTURER_ID, MANUFACTURER_DATA_MASK)
private fun givenScanRecord(
serviceUuids: List<ParcelUuid>? = null,
serviceData: Map<ParcelUuid, ByteArray>? = null,
manufacturerData: Map<Int, ByteArray> = emptyMap()
): ScanRecord {
val scanRecord = mock<ScanRecord>()
whenever(scanRecord.serviceUuids).thenReturn(serviceUuids)
whenever(scanRecord.serviceData).thenReturn(serviceData)
manufacturerData.forEach { (manufacturerId, data) ->
whenever(scanRecord.getManufacturerSpecificData(manufacturerId)).thenReturn(data)
}
return scanRecord
}
}
\ No newline at end of file
......@@ -14,7 +14,7 @@ apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
buildToolsVersion '30.0.3'
buildTypes {
debug {
......@@ -29,11 +29,6 @@ android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
buildConfigField 'String', 'BASE_URL', '"https://app.stopcovid.gouv.fr"'
buildConfigField 'String', 'SERVER_URL', 'BASE_URL+"/json/version-26/"'
buildConfigField 'String', 'SERVER_CERTIFICATE_SHA256', '"sha256/sXQojvwsiyblrpMQIVRXGC5u7AgknzTJm+VIK1kQmD8="'
buildConfigField 'String', 'CONFIG_JSON', '"config.json"'
}
compileOptions {
......@@ -55,9 +50,9 @@ dependencies {
api 'androidx.appcompat:appcompat:_'
api 'androidx.constraintlayout:constraintlayout:_'
api 'androidx.core:core-ktx:_'
api 'androidx.lifecycle:lifecycle-extensions:_'
api 'androidx.lifecycle:lifecycle-viewmodel-ktx:_'
api 'androidx.lifecycle:lifecycle-livedata-ktx:_'
api 'androidx.lifecycle:lifecycle-process:_'
api "androidx.lifecycle:lifecycle-viewmodel-savedstate:_"
api "androidx.navigation:navigation-fragment-ktx:_"
api "androidx.navigation:navigation-ui-ktx:_"
......
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2020/15/07 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.stopcovid.coreui
object ConfigConstant {
private const val BASE_URL: String = "https://app.stopcovid.gouv.fr/"
private const val VERSION_PATH: String = "json/version-28/"
private const val VERSIONED_SERVER_URL: String = BASE_URL + VERSION_PATH
const val SERVER_CERTIFICATE_SHA256: String = "sha256/sXQojvwsiyblrpMQIVRXGC5u7AgknzTJm+VIK1kQmD8="
object Maintenance {
const val CERTIFICATE_SHA256: String = "sha256/sXQojvwsiyblrpMQIVRXGC5u7AgknzTJm+VIK1kQmD8="
private const val FOLDER: String = "maintenance/"
private const val FILENAME: String = "info-maintenance-v2.json"
const val URL: String = BASE_URL + FOLDER + FILENAME
}
object Vaccination {
const val ASSET_ZIP_GEOLOC_FILE_PATH: String = "VaccinationCenter/zip-geoloc.json"
const val CENTER_FILENAME: String = "centres-vaccination.json"
const val CENTER_LAST_UPDATE_FILENAME: String = "lastUpdate.json"
private const val FOLDER: String = "/infos/dep/"
const val URL: String = BASE_URL + FOLDER
const val CENTER_SUFFIX: String = "-centers.json"
const val LAST_UPDATE_SUFFIX: String = "-lastUpdate.json"
}
object KeyFigures {
const val MASTER_URL: String = BASE_URL + "infos/key-figures.json"
const val MASTER_LOCAL_FILENAME: String = "key-figures.json"
const val FOLDER: String = "MoreKeyFigures/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER
const val FILE_PREFIX: String = "morekeyfigures-"
}
object Attestations {
const val FILENAME: String = "form.json"
private const val FOLDER: String = "Attestations/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER + FILENAME
const val ASSET_FILE_PATH: String = FOLDER + FILENAME
}
object InfoCenter {
private const val FOLDER: String = "InfoCenter/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER
const val LOCAL_FALLBACK_FILENAME: String = "info-labels-en.json"
const val STRINGS_PREFIX: String = "info-labels-"
const val TAGS_PREFIX: String = "info-tags"
const val INFOS_PREFIX: String = "info-center"
const val LAST_UPDATE_PREFIX: String = "info-center-lastupdate"
}
object Links {
const val FOLDER: String = "Links/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER
const val FILE_PREFIX: String = "links-"
}
object Privacy {
const val URL: String = VERSIONED_SERVER_URL
const val FILE_PREFIX: String = "privacy-"
const val ASSET_FOLDER_NAME: String = "Privacy/"
}
object Risks {
const val FILENAME: String = "risks.json"
private const val FOLDER: String = "Risks/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER + FILENAME
const val ASSET_FILE_PATH: String = FOLDER + FILENAME
}
object Labels {
const val FOLDER: String = "Strings/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER
const val FILE_PREFIX: String = "strings-"
}
object Config {
const val FOLDER: String = "Config/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER
const val LOCAL_FILENAME: String = "config.json"
}
object Calibration {
const val FOLDER: String = "Calibration/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER
const val LOCAL_FILENAME: String = "calibrationBle.json"
}
object Store {
const val GOOGLE: String = "market://details?id=fr.gouv.android.stopcovid"
const val HUAWEI: String = "appmarket://details?id=fr.gouv.android.stopcovid"
const val WEBSITE: String = "https://bonjour.tousanticovid.gouv.fr"
}
}
\ No newline at end of file
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2020/15/07 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.stopcovid.coreui
enum class EnvConstant {
Prod {
override val captchaApiKey: String = "6LettPsUAAAAAHYaFdRBOilHUgmTMSIPKNZN4D7l"
override val baseUrl: String = "https://api.stopcovid.gouv.fr"
override val warningBaseUrl: String = "https://tacw.tousanticovid.gouv.fr"
override val certificateSha256: String = "sha256/Up+TDyVDu8vKvd22TeAnXYxQqfPd2oNOU9Y04JahHpQ="
override val warningCertificateSha256: String = "sha256/b7w+uqyD+XILNIlRc3XVmEROwFCVTv5yOchb2i5FJbo="
override val configFilename: String = "config.json"
override val calibrationFilename: String = "calibrationBle.json"
override val serverPublicKey: String = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAc9IDt6qJq453SwyWPB94JaLB2VfTAcL43YVtMr3HhDCd22gKaQXIbX1d+tNhfvaKM51sxeaXziPjntUzbTNiw=="
};
abstract val captchaApiKey: String
abstract val baseUrl: String
abstract val warningBaseUrl: String
abstract val certificateSha256: String
abstract val warningCertificateSha256: String
abstract val configFilename: String
abstract val calibrationFilename: String
abstract val serverPublicKey: String
}
\ No newline at end of file
......@@ -15,7 +15,7 @@ import android.content.Intent
import android.net.Uri
import androidx.core.util.AtomicFile
import androidx.emoji.text.EmojiCompat
import com.lunabeestudio.stopcovid.coreui.BuildConfig
import com.lunabeestudio.stopcovid.coreui.ConfigConstant
import com.lunabeestudio.stopcovid.coreui.network.OkHttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
......@@ -28,7 +28,7 @@ import java.io.FileOutputStream
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun String.saveTo(context: Context, file: File) {
withContext(Dispatchers.IO) {
val okHttpClient = OkHttpClient.getDefaultOKHttpClient(context, this@saveTo, BuildConfig.SERVER_CERTIFICATE_SHA256)
val okHttpClient = OkHttpClient.getDefaultOKHttpClient(context, this@saveTo, ConfigConstant.SERVER_CERTIFICATE_SHA256)
val request: Request = Request.Builder()
.url(this@saveTo)
.build()
......@@ -46,9 +46,9 @@ suspend fun String.saveTo(context: Context, file: File) {
}
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun String.saveTo(context: Context, atomicFile: AtomicFile): FileOutputStream? {
suspend fun String.saveTo(context: Context, atomicFile: AtomicFile): FileOutputStream {
return withContext(Dispatchers.IO) {
val okHttpClient = OkHttpClient.getDefaultOKHttpClient(context, this@saveTo, BuildConfig.SERVER_CERTIFICATE_SHA256)
val okHttpClient = OkHttpClient.getDefaultOKHttpClient(context, this@saveTo, ConfigConstant.SERVER_CERTIFICATE_SHA256)
val request: Request = Request.Builder()
.url(this@saveTo)
.build()
......
package com.lunabeestudio.stopcovid.coreui.fastitem
import android.content.res.ColorStateList
import android.graphics.drawable.GradientDrawable
import android.util.LayoutDirection
import android.view.LayoutInflater
import android.view.View
......@@ -34,6 +35,9 @@ class CardWithActionsItem(private val cardTheme: CardTheme) : AbstractBindingIte
@ColorInt
var cardTitleColorInt: Int? = null
// Gradient background, override theme
var gradientBackground: GradientDrawable? = null
var mainTitle: String? = null
var mainHeader: String? = null
var mainBody: String? = null
......@@ -52,7 +56,6 @@ class CardWithActionsItem(private val cardTheme: CardTheme) : AbstractBindingIte
val context = inflater.context
val themedInflater = LayoutInflater.from(ContextThemeWrapper(context, cardTheme.themeId))
val itemCardWithActionsBinding = ItemCardWithActionsBinding.inflate(themedInflater, parent, false)
itemCardWithActionsBinding.rootLayout.background = cardTheme.backgroundDrawableRes?.let { ContextCompat.getDrawable(context, it) }
return itemCardWithActionsBinding
}
......@@ -121,6 +124,14 @@ class CardWithActionsItem(private val cardTheme: CardTheme) : AbstractBindingIte
binding.actionsLinearLayout.visibility = View.VISIBLE
}
}
gradientBackground?.let {
binding.rootLayout.background = it
}
binding.rootLayout.background = gradientBackground ?: cardTheme.backgroundDrawableRes?.let {
ContextCompat.getDrawable(binding.rootLayout.context, it)
}
}
override fun unbindView(binding: ItemCardWithActionsBinding) {
......
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2020/28/05 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.stopcovid.coreui.manager
import android.content.Context
import com.google.gson.Gson
import com.lunabeestudio.domain.model.Calibration
import com.lunabeestudio.stopcovid.coreui.ConfigConstant
import com.lunabeestudio.stopcovid.coreui.EnvConstant
import com.lunabeestudio.stopcovid.coreui.extension.saveTo
import com.lunabeestudio.stopcovid.coreui.model.ApiCalibration
import com.lunabeestudio.stopcovid.coreui.model.toDomain
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
object CalibrationManager {