Skip to content

Commit

Permalink
Merge pull request #293 from DP-3T/develop
Browse files Browse the repository at this point in the history
Version 1.4
  • Loading branch information
simonroesch committed Mar 11, 2021
2 parents 2701812 + 0e17edc commit 992abdb
Show file tree
Hide file tree
Showing 78 changed files with 4,890 additions and 1,610 deletions.
18 changes: 12 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

plugins {
id 'com.android.application'
id "org.sonarqube" version "2.8"
id 'kotlin-android'
id 'org.sonarqube' version '2.8'
}

apply from: 'backend_certs.gradle'
Expand Down Expand Up @@ -40,8 +41,8 @@ android {
applicationId "ch.admin.bag.dp3t"
minSdkVersion 23
targetSdkVersion 30
versionCode 13010
versionName "1.3.1"
versionCode 14000
versionName "1.4"
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 Expand Up @@ -137,6 +138,10 @@ android {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

kotlinOptions {
jvmTarget = "1.8"
}
}


Expand All @@ -152,7 +157,7 @@ sonarqube {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])

def dp3t_sdk_version = '2.0.2'
def dp3t_sdk_version = '2.1.0'
devImplementation "org.dpppt:dp3t-sdk-android:$dp3t_sdk_version-calibration"
teschtImplementation "org.dpppt:dp3t-sdk-android:$dp3t_sdk_version"
abnahmeImplementation "org.dpppt:dp3t-sdk-android:$dp3t_sdk_version"
Expand All @@ -164,9 +169,10 @@ dependencies {
implementation 'androidx.fragment:fragment:1.2.5'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata:2.2.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'androidx.security:security-crypto:1.0.0-rc03'
implementation 'androidx.work:work-runtime:2.5.0-beta02'
implementation 'androidx.work:work-runtime-ktx:2.5.0'

implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
Expand All @@ -181,7 +187,7 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.work:work-testing:2.4.0'
androidTestImplementation 'androidx.work:work-testing:2.5.0'
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.14.7'

}
35 changes: 28 additions & 7 deletions app/src/androidTest/java/ch/admin/bag/dp3t/FakeWorkerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
import androidx.work.testing.TestDriver;
import androidx.work.testing.WorkManagerTestInitHelper;

import junit.framework.TestCase;

import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import junit.framework.TestCase;

import org.dpppt.android.sdk.DP3T;
import org.dpppt.android.sdk.internal.AppConfigManager;
import org.dpppt.android.sdk.internal.logger.LogLevel;
Expand Down Expand Up @@ -52,6 +52,9 @@ public void setup() throws IOException {
context = InstrumentationRegistry.getInstrumentation().getTargetContext();
Logger.init(context, LogLevel.DEBUG);

//cancel all work that was scheduled on normal WorkManager on Application creation
WorkManager.getInstance(context).cancelAllWork();

// Initialize WorkManager for instrumentation tests.
Configuration config = new Configuration.Builder()
// Set log level to Log.DEBUG to make it easier to debug
Expand All @@ -75,6 +78,9 @@ public void setup() throws IOException {
appConfigManager.setTracingEnabled(true);

SecureStorage.getInstance(context).setTDummy(-1);

//cancel all work that was scheduled during initialization
WorkManager.getInstance(context).cancelAllWork();
}

@Test
Expand Down Expand Up @@ -169,12 +175,22 @@ public MockResponse dispatch(RecordedRequest request) {

FakeWorker.safeStartFakeWorker(context);
WorkInfo workInfo = executeWorker();
long new_t_dummy = SecureStorage.getInstance(context).getTDummy();

// The request stays enqueued. T_dummy stays the same and exactly one network request is executed (and fails with Error
// code 503)
assertEquals(WorkInfo.State.ENQUEUED, workInfo.getState());
// The worker should be done (success) and there is a new worker enqueued.
// T_dummy stays the same and exactly one network request is executed (and fails with Error code 503)
assertEquals(WorkInfo.State.SUCCEEDED, workInfo.getState());
WorkInfo workInfoNext = null;
int workerCount = 0;
for (WorkInfo wi : WorkManager.getInstance(context).getWorkInfosByTag(FakeWorker.WORK_TAG).get()) {
if (wi.getState() == WorkInfo.State.ENQUEUED) {
workInfoNext = wi;
}
workerCount++;
}
assertEquals(2, workerCount);
assertEquals(WorkInfo.State.ENQUEUED, workInfoNext.getState());
assertEquals(1, requestCounter.get());
long new_t_dummy = SecureStorage.getInstance(context).getTDummy();
assertEquals(new_t_dummy, t_dummy);
}

Expand Down Expand Up @@ -285,7 +301,12 @@ private WorkInfo executeWorker() throws Exception {
UUID requestID = workInfoList.get(0).getId();
testDriver.setInitialDelayMet(requestID);
testDriver.setAllConstraintsMet(requestID);
return WorkManager.getInstance(context).getWorkInfoById(requestID).get();
WorkInfo workInfo = null;
do {
Thread.sleep(500);
workInfo = WorkManager.getInstance(context).getWorkInfoById(requestID).get();
} while (workInfo.getState() == WorkInfo.State.RUNNING);
return workInfo;
}


Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/ch/admin/bag/dp3t/MainApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public void onCreate() {

initDP3T(this);

SecureStorage secureStorage = SecureStorage.getInstance(this);
int appVersionCode = BuildConfig.VERSION_CODE;
if (secureStorage.getLastKnownAppVersionCode() != appVersionCode) {
secureStorage.setLastKnownAppVersionCode(appVersionCode);
// cancel any active fake workers (will be restarted below if needed)
FakeWorker.stop(this);
}

FakeWorker.safeStartFakeWorker(this);
ConfigWorker.scheduleConfigWorkerIfOutdated(this);

Expand Down Expand Up @@ -100,6 +108,8 @@ public static void initDP3T(Context context) {
DP3T.setUserAgent(
() -> context.getPackageName() + ";" + BuildConfig.VERSION_NAME + ";" + BuildConfig.BUILD_TIME + ";Android;" +
Build.VERSION.SDK_INT + ";" + DP3T.getENModuleVersion(context));

DP3T.setWithFederationGateway(context, true);
}

private BroadcastReceiver contactUpdateReceiver = new BroadcastReceiver() {
Expand Down
79 changes: 79 additions & 0 deletions app/src/main/java/ch/admin/bag/dp3t/home/HomeFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.helper.widget.Flow;
import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import java.util.List;
import java.util.concurrent.TimeUnit;

import org.dpppt.android.sdk.TracingStatus;
import org.dpppt.android.sdk.internal.logger.Logger;

Expand All @@ -41,11 +47,15 @@
import ch.admin.bag.dp3t.home.model.NotificationState;
import ch.admin.bag.dp3t.home.model.NotificationStateError;
import ch.admin.bag.dp3t.home.model.TracingState;
import ch.admin.bag.dp3t.home.model.TracingStatusInterface;
import ch.admin.bag.dp3t.home.views.HeaderView;
import ch.admin.bag.dp3t.reports.ReportsFragment;
import ch.admin.bag.dp3t.storage.SecureStorage;
import ch.admin.bag.dp3t.travel.TravelFragment;
import ch.admin.bag.dp3t.travel.TravelUtils;
import ch.admin.bag.dp3t.util.*;
import ch.admin.bag.dp3t.viewmodel.TracingViewModel;
import ch.admin.bag.dp3t.whattodo.WtdInfolineAccessabilityDialogFragment;
import ch.admin.bag.dp3t.whattodo.WtdPositiveTestFragment;
import ch.admin.bag.dp3t.whattodo.WtdSymptomsFragment;

Expand All @@ -64,6 +74,7 @@ public class HomeFragment extends Fragment {
private View reportStatusBubble;
private View reportStatusView;
private View reportErrorView;
private View travelCard;
private View cardSymptomsFrame;
private View cardTestFrame;
private View cardSymptoms;
Expand Down Expand Up @@ -102,6 +113,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
reportStatusBubble = view.findViewById(R.id.report_status_bubble);
reportStatusView = reportStatusBubble.findViewById(R.id.report_status);
reportErrorView = reportStatusBubble.findViewById(R.id.report_errors);
travelCard = view.findViewById(R.id.card_travel);
headerView = view.findViewById(R.id.home_header_view);
scrollView = view.findViewById(R.id.home_scroll_view);
cardSymptoms = view.findViewById(R.id.card_what_to_do_symptoms);
Expand All @@ -114,9 +126,12 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
setupInfobox();
setupTracingView();
setupNotification();
setupTravelCard();
setupWhatToDo();
setupNonProductionHint();
setupScrollBehavior();

showEndIsolationDialogIfNecessary();
}

@Override
Expand Down Expand Up @@ -175,6 +190,23 @@ private void setupInfobox() {
linkGroup.setVisibility(View.GONE);
}

String hearingImpairedInfo = secureStorage.getInfoboxHearingImpairedInfo();
View hearingImpairedView = infobox.findViewById(R.id.infobox_link_hearing_impaired);
ImageView linkIcon = infobox.findViewById(R.id.infobox_link_icon);
if (hearingImpairedInfo != null) {
hearingImpairedView.setOnClickListener(v ->
requireActivity().getSupportFragmentManager().beginTransaction()
.add(WtdInfolineAccessabilityDialogFragment.newInstance(hearingImpairedInfo),
WtdInfolineAccessabilityDialogFragment.class.getCanonicalName())
.commit()
);
linkIcon.setImageResource(R.drawable.ic_phone);
hearingImpairedView.setVisibility(VISIBLE);
} else {
linkIcon.setImageResource(R.drawable.ic_launch);
hearingImpairedView.setVisibility(View.GONE);
}

boolean isDismissible = secureStorage.getInfoboxDismissible();
View dismissButton = infobox.findViewById(R.id.dismiss_button);
if (isDismissible) {
Expand Down Expand Up @@ -315,6 +347,25 @@ private boolean isNotificationChannelEnabled(Context context, @Nullable String c
}
}

private void setupTravelCard() {
travelCard.setOnClickListener(
v -> getActivity().getSupportFragmentManager().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, TravelFragment.newInstance())
.addToBackStack(TravelFragment.class.getCanonicalName())
.commit()
);

List<String> countries = secureStorage.getInteropCountries();
if (!countries.isEmpty()) {
travelCard.setVisibility(VISIBLE);
Flow flowConstraint = travelCard.findViewById(R.id.travel_flags_flow);
TravelUtils.inflateFlagFlow(flowConstraint, countries);
} else {
travelCard.setVisibility(View.GONE);
}
}

private void setupWhatToDo() {
cardSymptoms.setOnClickListener(
v -> getActivity().getSupportFragmentManager().beginTransaction()
Expand Down Expand Up @@ -355,6 +406,34 @@ private void setupScrollBehavior() {
});
}

private void showEndIsolationDialogIfNecessary() {
Observer<TracingStatusInterface> observer = new Observer<TracingStatusInterface>() {
@Override
public void onChanged(TracingStatusInterface tracingStatusInterface) {
long isolationEndDialogTimestamp = secureStorage.getIsolationEndDialogTimestamp();
if (isolationEndDialogTimestamp != -1L && System.currentTimeMillis() > isolationEndDialogTimestamp &&
tracingStatusInterface.isReportedAsInfected()) {
new AlertDialog.Builder(requireContext(), R.style.NextStep_AlertDialogStyle)
.setTitle(R.string.homescreen_isolation_ended_popup_title)
.setMessage(R.string.homescreen_isolation_ended_popup_text)
.setPositiveButton(R.string.answer_yes, (dialog, which) -> {
tracingStatusInterface.resetInfectionStatus(getContext());
secureStorage.setIsolationEndDialogTimestamp(-1L);
})
.setNegativeButton(R.string.answer_no, (dialog, which) -> {
long newTimestamp = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
secureStorage.setIsolationEndDialogTimestamp(newTimestamp);
})
.setCancelable(false)
.show();
}
tracingViewModel.getAppStatusLiveData().removeObserver(this);
}
};

tracingViewModel.getAppStatusLiveData().observe(getViewLifecycleOwner(), observer);
}

private void enableTracing() {
Activity activity = getActivity();
if (activity == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.util.Date;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;

import com.google.android.gms.common.api.ApiException;

Expand Down Expand Up @@ -168,7 +169,8 @@ public void onError(Throwable throwable) {
}
buttonSend.setEnabled(true);
}
});
},
getViewLifecycleOwner());
}

private void informExposed(Date onsetDate, String authorizationHeader) {
Expand All @@ -180,6 +182,11 @@ public void onSuccess(Void response) {
progressDialog.dismiss();
}
secureStorage.clearInformTimeAndCodeAndToken();

// Ask if user wants to end isolation after 14 days
long isolationEndDialogTimestamp = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14);
secureStorage.setIsolationEndDialogTimestamp(isolationEndDialogTimestamp);

getParentFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_enter, R.anim.slide_exit, R.anim.slide_pop_enter,
R.anim.slide_pop_exit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@

import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.helper.widget.Flow;
import androidx.fragment.app.Fragment;

import java.util.List;

import ch.admin.bag.dp3t.R;
import ch.admin.bag.dp3t.storage.SecureStorage;
import ch.admin.bag.dp3t.travel.TravelUtils;

public class InformIntroFragment extends Fragment {

Expand All @@ -36,6 +42,17 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
});
((InformActivity) requireActivity()).allowBackButton(true);

SecureStorage secureStorage = SecureStorage.getInstance(getContext());
List<String> countries = secureStorage.getInteropCountries();
ViewGroup countriesContainer = view.findViewById(R.id.inform_intro_travel);
if (!countries.isEmpty()) {
countriesContainer.setVisibility(View.VISIBLE);
Flow flowConstraint = view.findViewById(R.id.inform_intro_travel_flags_flow);
TravelUtils.inflateFlagFlow(flowConstraint, countries);
} else {
countriesContainer.setVisibility(View.GONE);
}

Button continueButton = view.findViewById(R.id.inform_intro_button_continue);
continueButton.setOnClickListener(v -> {
getParentFragmentManager()
Expand Down
Loading

0 comments on commit 992abdb

Please sign in to comment.