Skip to content

Commit

Permalink
Merge pull request #420 from SwissCovid/develop
Browse files Browse the repository at this point in the history
Version 2.4.0
  • Loading branch information
simonroesch committed Mar 18, 2022
2 parents 482b981 + 1cb9e66 commit 005b485
Show file tree
Hide file tree
Showing 28 changed files with 918 additions and 29 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ android {
applicationId "ch.admin.bag.dp3t"
minSdkVersion 23
targetSdkVersion 30
versionCode 23010
versionName "2.3.1"
versionCode 24000
versionName "2.4.0"
resConfigs "en", "fr", "de", "it", "pt", "es", "sq", "bs", "hr", "sr", "rm", "tr", "ti"

buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L'
Expand Down
43 changes: 38 additions & 5 deletions app/src/main/java/ch/admin/bag/dp3t/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ package ch.admin.bag.dp3t
import android.content.*
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.result.ActivityResult
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import ch.admin.bag.dp3t.checkin.CheckinOverviewFragment
import ch.admin.bag.dp3t.checkin.CrowdNotifierViewModel
Expand All @@ -29,7 +31,9 @@ import ch.admin.bag.dp3t.checkin.networking.CrowdNotifierKeyLoadWorker
import ch.admin.bag.dp3t.checkin.utils.CrowdNotifierReminderHelper
import ch.admin.bag.dp3t.checkin.utils.ErrorDialog
import ch.admin.bag.dp3t.checkin.utils.NotificationHelper
import ch.admin.bag.dp3t.hibernate.HibernatingInfoFragment
import ch.admin.bag.dp3t.inform.InformActivity
import ch.admin.bag.dp3t.networking.ConfigWorker
import ch.admin.bag.dp3t.networking.ConfigWorker.Companion.scheduleConfigWorkerIfOutdated
import ch.admin.bag.dp3t.onboarding.OnboardingActivityArgs
import ch.admin.bag.dp3t.onboarding.OnboardingActivityResultContract
Expand All @@ -43,6 +47,7 @@ import ch.admin.bag.dp3t.util.UrlUtil
import ch.admin.bag.dp3t.viewmodel.TracingViewModel
import ch.admin.bag.dp3t.whattodo.WtdPositiveTestFragment
import com.google.android.gms.instantapps.InstantApps
import kotlinx.coroutines.launch
import org.crowdnotifier.android.sdk.CrowdNotifier
import org.crowdnotifier.android.sdk.utils.QrUtils.*
import org.dpppt.android.sdk.DP3T
Expand All @@ -62,7 +67,9 @@ class MainActivity : FragmentActivity() {
private val crowdNotifierViewModel: CrowdNotifierViewModel by viewModels()

private val onboardingLauncher = registerForActivityResult(OnboardingActivityResultContract()) {
if (it != null) {
if (secureStorage.isHibernating) {
showHibernateFragment()
} else if (it != null) {
onOnboardingFinished(it.onboardingType, it.activityResult, it.instantAppUrl)
} else {
finish()
Expand All @@ -78,7 +85,28 @@ class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
secureStorage.forceUpdateLiveData.observe(this, {
val isInHibernatingState = secureStorage.isHibernating
if (isInHibernatingState) {
if (savedInstanceState == null) {
showHibernateFragment()
}
return
}

// After the App Update, load the config immediately to check whether hibernation mode is turned on
if (secureStorage.lastConfigLoadSuccessAppVersion != BuildConfig.VERSION_CODE) {
lifecycleScope.launch {
try {
ConfigWorker.loadConfig(this@MainActivity)
if (secureStorage.isHibernating) showHibernateFragment()
} catch (e: Exception) {
Log.d("MainActivity", "Failed to load Config after App Update")
}

}
}

secureStorage.forceUpdateLiveData.observe(this) {

val forceUpdate = it && secureStorage.doForceUpdate

Expand All @@ -96,7 +124,7 @@ class MainActivity : FragmentActivity() {
}
forceUpdateDialog.show()
}
})
}
scheduleConfigWorkerIfOutdated(this)
CrowdNotifierKeyLoadWorker.startKeyLoadWorker(this)
CrowdNotifierKeyLoadWorker.cleanUpOldData(this)
Expand All @@ -111,7 +139,6 @@ class MainActivity : FragmentActivity() {
lastShownUpdateBoardingVersion < UPDATE_BOARDING_VERSION -> OnboardingType.UPDATE_BOARDING
else -> null
}

if (onboardingType == null) {
showHomeFragment()
} else {
Expand Down Expand Up @@ -174,7 +201,7 @@ class MainActivity : FragmentActivity() {

override fun onResume() {
super.onResume()
if (secureStorage.onboardingCompleted) checkIntentForActions()
if (secureStorage.onboardingCompleted && !secureStorage.isHibernating) checkIntentForActions()
LocalBroadcastManager.getInstance(this)
.registerReceiver(autoCheckoutBroadcastReceiver, IntentFilter(CrowdNotifierReminderHelper.ACTION_DID_AUTO_CHECKOUT))
}
Expand Down Expand Up @@ -285,6 +312,12 @@ class MainActivity : FragmentActivity() {
startActivity(intent)
}

private fun showHibernateFragment() {
supportFragmentManager.beginTransaction()
.add(R.id.main_fragment_container, HibernatingInfoFragment.newInstance())
.commit()
}

private fun showHomeFragment() {
supportFragmentManager.beginTransaction()
.add(R.id.main_fragment_container, TabbarHostFragment.newInstance())
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/ch/admin/bag/dp3t/MainApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,15 @@ public void onCreate() {
Logger.init(getApplicationContext(), LogLevel.DEBUG);
CertificatePinning.initDebug(this);
}
SecureStorage secureStorage = SecureStorage.getInstance(this);

// DP3T SDK is not initialized if app is in Hibernating Mode to prevent it from showing any notifications to the user
if (secureStorage.isHibernating()) return;

registerReceiver(contactUpdateReceiver, DP3T.getUpdateIntentFilter());

initDP3T(this);

SecureStorage secureStorage = SecureStorage.getInstance(this);
int appVersionCode = BuildConfig.VERSION_CODE;
if (secureStorage.getLastKnownAppVersionCode() != appVersionCode) {
secureStorage.setLastKnownAppVersionCode(appVersionCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public static void startKeyLoadWorker(Context context) {
workManager.enqueueUniquePeriodicWork(WORK_TAG, ExistingPeriodicWorkPolicy.KEEP, periodicWorkRequest);
}

public static void stop(Context context) {
WorkManager.getInstance(context).cancelAllWorkByTag(WORK_TAG);
}


public CrowdNotifierKeyLoadWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package ch.admin.bag.dp3t.hibernate

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import ch.admin.bag.dp3t.R
import ch.admin.bag.dp3t.TabbarHostFragment
import ch.admin.bag.dp3t.databinding.FragmentHibernatingInfoBinding
import ch.admin.bag.dp3t.html.HtmlFragment
import ch.admin.bag.dp3t.networking.models.InfoBoxModel
import ch.admin.bag.dp3t.util.AssetUtil
import ch.admin.bag.dp3t.util.UrlUtil

class HibernatingInfoFragment : Fragment() {

companion object {
@JvmStatic
fun newInstance() = HibernatingInfoFragment()
}

private val viewModel: HibernatingViewModel by viewModels()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return FragmentHibernatingInfoBinding.inflate(inflater).apply {

toolbar.setOnMenuItemClickListener {
showImpressum()
true
}

viewModel.isHibernatingModeEnabled.observe(viewLifecycleOwner) { isHibernatingModeEnabled ->
if (!isHibernatingModeEnabled) {
showHomeFragment()
}
}

viewModel.hibernatingInfoBox.observe(viewLifecycleOwner) { infoBoxModelCollection ->
val infobox = infoBoxModelCollection?.getInfoBox(getString(R.string.language_key))
infobox?.let {
title.text = it.title
text.text = it.msg
linkGroup.isVisible = it.urlTitle != null
linkText.text = it.urlTitle
linkGroup.setOnClickListener { v -> UrlUtil.openUrl(requireContext(), it.url) }
}
}

}.root
}

private fun showHomeFragment() {
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.main_fragment_container, TabbarHostFragment.newInstance())
.commit()
}

private fun showImpressum() {
val htmlFragment = HtmlFragment.newInstance(
R.string.menu_impressum, AssetUtil.getImpressumBaseUrl(context),
AssetUtil.getImpressumHtml(context)
)
requireActivity().supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.slide_enter,
R.anim.slide_exit,
R.anim.slide_pop_enter,
R.anim.slide_pop_exit
)
.replace(R.id.main_fragment_container, htmlFragment)
.addToBackStack(HtmlFragment::class.java.canonicalName)
.commit()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ch.admin.bag.dp3t.hibernate

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import ch.admin.bag.dp3t.MainApplication
import ch.admin.bag.dp3t.R
import ch.admin.bag.dp3t.checkin.networking.CrowdNotifierKeyLoadWorker
import ch.admin.bag.dp3t.networking.ConfigWorker
import ch.admin.bag.dp3t.networking.FakeWorker
import ch.admin.bag.dp3t.networking.models.InfoBoxModel
import ch.admin.bag.dp3t.networking.models.InfoBoxModelCollection
import ch.admin.bag.dp3t.storage.SecureStorage
import ch.admin.bag.dp3t.util.NotificationRepeatWorker
import kotlinx.coroutines.launch
import org.dpppt.android.sdk.DP3T
import org.dpppt.android.sdk.internal.logger.Logger

class HibernatingViewModel(application: Application) : AndroidViewModel(application) {

companion object {
private val TAG = "HibernatingViewModel"

}

private val isHibernatingModeEnabledMutable = MutableLiveData<Boolean>()
val isHibernatingModeEnabled: LiveData<Boolean> = isHibernatingModeEnabledMutable

private val secureStorage: SecureStorage by lazy { SecureStorage.getInstance(application) }

private val hibernatingInfoboxMutable = MutableLiveData<InfoBoxModelCollection?>(secureStorage.hibernatingInfoboxCollection)
val hibernatingInfoBox: LiveData<InfoBoxModelCollection?> = hibernatingInfoboxMutable

init {
loadConfig()
}


private fun loadConfig() {
viewModelScope.launch {
try {
ConfigWorker.loadConfig(getApplication())
if (secureStorage.isHibernating) {
isHibernatingModeEnabledMutable.value = true
hibernatingInfoboxMutable.value = secureStorage.hibernatingInfoboxCollection
} else {
MainApplication.initDP3T(getApplication())
FakeWorker.safeStartFakeWorker(getApplication())
CrowdNotifierKeyLoadWorker.startKeyLoadWorker(getApplication())
NotificationRepeatWorker.startWorker(getApplication())
ConfigWorker.scheduleConfigWorkerIfOutdated(getApplication())
isHibernatingModeEnabledMutable.value = false
}
} catch (e: Exception) {
Logger.e(TAG, "config request failed", e)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@

public class CertificatePinning {

public static final String QUOVADIS_ROOT_CA_2_G3_PIN = "sha256/SkntvS+PgjC9VZKzE1c/4cFypF+pgBHMHt27Nq3j/OU=";

private static final CertificatePinner CERTIFICATE_PINNER_LIVE = new CertificatePinner.Builder()
.add("www.pt-d.bfs.admin.ch", "sha256/xWdkLqfT40GnyHyZXt9IStltvrshlowMuGZHgp631Tw=") // leaf
.add("www.pt1-d.bfs.admin.ch", "sha256/Pr8nx8M3Oa8EYefVXYB3D4KJViREDy4ipA1oVyjGoss=") // leaf
.add("codegen-service-d.bag.admin.ch", "sha256/GLV5pALJpJb02GavguT3NTOmOL57H7K3KhJ59iH6A/Q=") //leaf
.add("www.pt-t.bfs.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("www.pt1-t.bfs.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("codegen-service-t.bag.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("www.pt-a.bfs.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("www.pt1-a.bfs.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("codegen-service-a.bag.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("www.pt.bfs.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("www.pt1.bfs.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("codegen-service.bag.admin.ch", "sha256/KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=") // intermediate
.add("www.pt-d.bfs.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("www.pt1-d.bfs.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("codegen-service-d.bag.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("www.pt-t.bfs.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("www.pt1-t.bfs.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("codegen-service-t.bag.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("www.pt-a.bfs.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("www.pt1-a.bfs.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("codegen-service-a.bag.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("www.pt.bfs.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("www.pt1.bfs.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.add("codegen-service.bag.admin.ch", QUOVADIS_ROOT_CA_2_G3_PIN)
.build();

private static final CertificatePinner CERTIFICATE_PINNER_DISABLED = new CertificatePinner.Builder().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.dpppt.android.sdk.util.SignatureUtil
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.IOException
import java.lang.Exception

class ConfigRepository(context: Context) {

Expand Down Expand Up @@ -64,7 +65,13 @@ class ConfigRepository(context: Context) {
val appVersion = APP_VERSION_PREFIX_ANDROID + BuildConfig.VERSION_NAME
val osVersion = OS_VERSION_PREFIX_ANDROID + Build.VERSION.SDK_INT
val buildNumber = BuildConfig.BUILD_TIME.toString()
val enModuleVersion = DP3T.getENModuleVersion(context).toString()

//In Hibernating Mode, the DP3T Module is not initialized and therefore the ENModuleVersion cannot be accessed
val enModuleVersion = try {
DP3T.getENModuleVersion(context).toString()
} catch (e: Exception) {
""
}
val configResponse = configService.getConfig(appVersion, osVersion, buildNumber, enModuleVersion)
if (configResponse.isSuccessful) {
secureStorage.lastConfigLoadSuccess = System.currentTimeMillis()
Expand Down
Loading

0 comments on commit 005b485

Please sign in to comment.