diff --git a/app/src/main/java/dgca/verifier/app/android/data/VerifierRepository.kt b/app/src/main/java/dgca/verifier/app/android/data/VerifierRepository.kt index 68c194a1..9b6c6e7b 100644 --- a/app/src/main/java/dgca/verifier/app/android/data/VerifierRepository.kt +++ b/app/src/main/java/dgca/verifier/app/android/data/VerifierRepository.kt @@ -31,5 +31,7 @@ interface VerifierRepository { suspend fun getCertificatesBy(kid: String): List - fun getLastSyncTimeMillis(): LiveData + fun getLastPubKeysSyncTimeMillis(): LiveData + + fun getLastRevocationSyncTimeMillis(): Long } \ No newline at end of file diff --git a/app/src/main/java/dgca/verifier/app/android/data/VerifierRepositoryImpl.kt b/app/src/main/java/dgca/verifier/app/android/data/VerifierRepositoryImpl.kt index bfde69d3..c95ada57 100644 --- a/app/src/main/java/dgca/verifier/app/android/data/VerifierRepositoryImpl.kt +++ b/app/src/main/java/dgca/verifier/app/android/data/VerifierRepositoryImpl.kt @@ -24,6 +24,7 @@ package dgca.verifier.app.android.data import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import dcc.app.revocation.data.RevocationPreferences import dgca.verifier.app.android.data.local.AppDatabase import dgca.verifier.app.android.data.local.Preferences import dgca.verifier.app.android.data.local.model.Key @@ -42,13 +43,14 @@ import javax.inject.Inject class VerifierRepositoryImpl @Inject constructor( private val apiService: ApiService, private val preferences: Preferences, + private val revocationPreferences: RevocationPreferences, private val db: AppDatabase, private val keyStoreCryptor: KeyStoreCryptor ) : BaseRepository(), VerifierRepository { private val validCertList = mutableListOf() private val mutex = Mutex() - private val lastSyncLiveData: MutableLiveData = MutableLiveData(preferences.lastKeysSyncTimeMillis) + private val lastSyncLiveData = MutableLiveData(preferences.lastKeysSyncTimeMillis) override suspend fun fetchCertificates(statusUrl: String, updateUrl: String): Boolean? { mutex.withLock { @@ -73,7 +75,9 @@ class VerifierRepositoryImpl @Inject constructor( keyStoreCryptor.decrypt(it.key)?.base64ToX509Certificate()!! } - override fun getLastSyncTimeMillis(): LiveData = lastSyncLiveData + override fun getLastPubKeysSyncTimeMillis(): LiveData = lastSyncLiveData + + override fun getLastRevocationSyncTimeMillis(): Long = revocationPreferences.lastRevocationSyncTimeMillis private suspend fun fetchCertificate(url: String, resumeToken: Long) { val tokenFormatted = if (resumeToken == -1L) "" else resumeToken.toString() diff --git a/app/src/main/java/dgca/verifier/app/android/settings/SettingsFragment.kt b/app/src/main/java/dgca/verifier/app/android/settings/SettingsFragment.kt index cdc171d6..b8a5d7b2 100644 --- a/app/src/main/java/dgca/verifier/app/android/settings/SettingsFragment.kt +++ b/app/src/main/java/dgca/verifier/app/android/settings/SettingsFragment.kt @@ -67,13 +67,13 @@ class SettingsFragment : BindingFragment() { binding.syncPublicKeys.setOnClickListener { viewModel.syncPublicKeys() } binding.version.text = getString(R.string.version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) - viewModel.inProgress.observe(viewLifecycleOwner, { + viewModel.inProgress.observe(viewLifecycleOwner) { binding.privacyInformation.isClickable = it != true binding.licenses.isClickable = it != true binding.syncPublicKeys.isClickable = it != true binding.progressBar.visibility = if (it == true) View.VISIBLE else View.GONE - }) - viewModel.lastSyncLiveData.observe(viewLifecycleOwner, { + } + viewModel.lastSyncLiveData.observe(viewLifecycleOwner) { if (it <= 0) { binding.lastUpdate.visibility = View.GONE } else { @@ -83,7 +83,7 @@ class SettingsFragment : BindingFragment() { it.toLocalDateTime().formatWith(LAST_UPDATE_DATE_TIME_FORMAT) ) } - }) + } viewModel.debugModeState.observe(viewLifecycleOwner) { setUpDebugModeButton(it) } @@ -92,6 +92,19 @@ class SettingsFragment : BindingFragment() { SettingsFragmentDirections.actionSettingsFragmentToVerificationResultFragment() findNavController().navigate(action) } + + binding.syncRevocation.setOnClickListener { viewModel.syncRevocation() } + viewModel.lastRevocationSyncTime.observe(viewLifecycleOwner) { + if (it <= 0) { + binding.lastRevocationUpdate.visibility = View.GONE + } else { + binding.lastRevocationUpdate.visibility = View.VISIBLE + binding.lastRevocationUpdate.text = getString( + R.string.last_updated, + it.toLocalDateTime().formatWith(LAST_UPDATE_DATE_TIME_FORMAT) + ) + } + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { diff --git a/app/src/main/java/dgca/verifier/app/android/settings/SettingsViewModel.kt b/app/src/main/java/dgca/verifier/app/android/settings/SettingsViewModel.kt index fe0e833b..b439948a 100644 --- a/app/src/main/java/dgca/verifier/app/android/settings/SettingsViewModel.kt +++ b/app/src/main/java/dgca/verifier/app/android/settings/SettingsViewModel.kt @@ -24,6 +24,7 @@ package dgca.verifier.app.android.settings import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel +import dcc.app.revocation.domain.usacase.GetRevocationDataUseCase import dgca.verifier.app.android.BuildConfig import dgca.verifier.app.android.data.ConfigRepository import dgca.verifier.app.android.data.VerifierRepository @@ -39,13 +40,15 @@ import javax.inject.Inject class SettingsViewModel @Inject constructor( private val configRepository: ConfigRepository, private val verifierRepository: VerifierRepository, - private val preferences: Preferences + private val preferences: Preferences, + private val getRevocationDataUseCase: GetRevocationDataUseCase ) : ViewModel(), LifecycleObserver { private val _inProgress = MutableLiveData() val inProgress: LiveData = _inProgress - val lastSyncLiveData: LiveData = verifierRepository.getLastSyncTimeMillis() + val lastSyncLiveData: LiveData = verifierRepository.getLastPubKeysSyncTimeMillis() + val lastRevocationSyncTime = MutableLiveData(verifierRepository.getLastRevocationSyncTimeMillis()) private val _debugModeState: MutableLiveData = MutableLiveData(DebugModeState.OFF) val debugModeState: LiveData = _debugModeState @@ -78,4 +81,14 @@ class SettingsViewModel @Inject constructor( _inProgress.value = false } } + + fun syncRevocation() { + _inProgress.value = true + getRevocationDataUseCase.execute( + viewModelScope, + onFailure = { Timber.d("error refreshing revocation: $it") }, + onSuccess = { lastRevocationSyncTime.value = verifierRepository.getLastRevocationSyncTimeMillis() }, + onComplete = { _inProgress.value = false } + ) + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index ea7946e5..8bcd53f7 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -112,7 +112,7 @@ android:letterSpacing="0" android:minHeight="48dp" android:paddingHorizontal="@dimen/default_padding" - android:text="@string/reload" + android:text="@string/reload_pub_keys" android:textAllCaps="false" app:layout_constraintTop_toBottomOf="@+id/trustedPublicKeys" /> @@ -129,6 +129,35 @@ tools:layout_editor_absoluteX="16dp" tools:text="Last Updated: 2021-05-27 09:44" /> + + + + Privacy Informatie Licenties Vertrouwde openbare sleutels - Herladen + Herladen Laatst bijgewerkt: %s Versie: %1$s (%2$d) Over diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b49c906..c8f859fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,7 +49,8 @@ Privacy information Licenses Trusted public keys - Reload + Reload pub keys + Reload revocation list Last Updated: %s Version: %1$s (%2$d) About diff --git a/revocation/src/main/java/dcc/app/revocation/data/RevocationPreferences.kt b/revocation/src/main/java/dcc/app/revocation/data/RevocationPreferences.kt index 8fc26945..9512b317 100644 --- a/revocation/src/main/java/dcc/app/revocation/data/RevocationPreferences.kt +++ b/revocation/src/main/java/dcc/app/revocation/data/RevocationPreferences.kt @@ -33,6 +33,8 @@ interface RevocationPreferences { var eTag: String? + var lastRevocationSyncTimeMillis: Long + fun clear() } @@ -50,6 +52,12 @@ class RevocationPreferencesImpl(context: Context) : RevocationPreferences { KEY_ETAG ) + override var lastRevocationSyncTimeMillis by LongPreference( + preferences, + KEY_REVOCATION_SYNC_TIME_MILLIS, + -1 + ) + override fun clear() { preferences.value.edit().clear().apply() } @@ -57,6 +65,7 @@ class RevocationPreferencesImpl(context: Context) : RevocationPreferences { companion object { private const val USER_PREF = "dcc.revocation.app.pref" private const val KEY_ETAG = "dcc.revocation.app.pref.eTag" + private const val KEY_REVOCATION_SYNC_TIME_MILLIS = "dcc.revocation.app.pref.last_revocation_sync_time_millis" } } @@ -74,4 +83,20 @@ class StringPreference( override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) { preferences.value.edit { putString(name, value) } } +} + +class LongPreference( + private val preferences: Lazy, + private val name: String, + private val defaultValue: Long +) : ReadWriteProperty { + + @WorkerThread + override fun getValue(thisRef: Any, property: KProperty<*>): Long { + return preferences.value.getLong(name, defaultValue) + } + + override fun setValue(thisRef: Any, property: KProperty<*>, value: Long) { + preferences.value.edit { putLong(name, value) } + } } \ No newline at end of file diff --git a/revocation/src/main/java/dcc/app/revocation/domain/usacase/GetRevocationDataUseCase.kt b/revocation/src/main/java/dcc/app/revocation/domain/usacase/GetRevocationDataUseCase.kt index 308683bd..cecfb8af 100644 --- a/revocation/src/main/java/dcc/app/revocation/domain/usacase/GetRevocationDataUseCase.kt +++ b/revocation/src/main/java/dcc/app/revocation/domain/usacase/GetRevocationDataUseCase.kt @@ -24,6 +24,7 @@ package dcc.app.revocation.domain.usacase import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import dcc.app.revocation.data.RevocationPreferences import dcc.app.revocation.data.network.model.RevocationPartitionResponse import dcc.app.revocation.data.network.model.Slice import dcc.app.revocation.data.network.model.SliceType @@ -41,13 +42,15 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream import java.io.InputStream import java.lang.reflect.Type +import java.time.Instant import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit import java.util.zip.GZIPInputStream import javax.inject.Inject - class GetRevocationDataUseCase @Inject constructor( private val repository: RevocationRepository, + private val revocationPreferences: RevocationPreferences, dispatcher: CoroutineDispatcher, errorHandler: ErrorHandler, ) : BaseUseCase(dispatcher, errorHandler) { @@ -66,7 +69,9 @@ class GetRevocationDataUseCase @Inject constructor( } // Delete expired data - repository.deleteExpiredData(System.currentTimeMillis()) + repository.deleteExpiredData(ChronoUnit.MICROS.between(Instant.EPOCH, ZonedDateTime.now().toInstant())) + + revocationPreferences.lastRevocationSyncTimeMillis = System.currentTimeMillis() } private suspend fun checkKidMetadata(revocationKidData: RevocationKidData) { diff --git a/revocation/src/main/java/dcc/app/revocation/repository/RevocationRepositoryImpl.kt b/revocation/src/main/java/dcc/app/revocation/repository/RevocationRepositoryImpl.kt index 85716885..c4b970bb 100644 --- a/revocation/src/main/java/dcc/app/revocation/repository/RevocationRepositoryImpl.kt +++ b/revocation/src/main/java/dcc/app/revocation/repository/RevocationRepositoryImpl.kt @@ -30,9 +30,13 @@ import dcc.app.revocation.data.network.mapper.toRevocationKidData import dcc.app.revocation.data.network.model.RevocationPartitionResponse import dcc.app.revocation.data.network.model.SliceType import dcc.app.revocation.domain.RevocationRepository -import dcc.app.revocation.domain.model.* +import dcc.app.revocation.domain.model.DccRevocationKidMetadata +import dcc.app.revocation.domain.model.DccRevocationPartition +import dcc.app.revocation.domain.model.DccRevocationSlice +import dcc.app.revocation.domain.model.RevocationKidData import okhttp3.ResponseBody import retrofit2.HttpException +import java.net.HttpURLConnection import javax.inject.Inject @Suppress("BlockingMethodInNonBlockingContext") @@ -50,9 +54,13 @@ class RevocationRepositoryImpl @Inject constructor( if (response.containsServerError()) { throw HttpException(response) } - revocationPreferences.eTag = response.headers()["eTag"]?.replace("\"", "") - return response.body()?.map { it.toRevocationKidData() } ?: emptyList() + return if (response.code() == HttpURLConnection.HTTP_OK) { + revocationPreferences.eTag = response.headers()["eTag"]?.replace("\"", "") + response.body()?.map { it.toRevocationKidData() } ?: emptyList() + } else { + emptyList() + } } @Throws(Exception::class)