From d223bd81325111c1ec3a5670afb9db4e3a11e6b1 Mon Sep 17 00:00:00 2001 From: Michael Huebler Date: Tue, 1 Sep 2020 23:10:58 +0200 Subject: [PATCH] Refactored and corrected sorting. Replaced improper TreeMaps by correct lists of comparable objects. Closes #36 and #38 --- .../matchentries/DkAndMatchEntries.java | 18 +++++++ .../matchentries/MatchEntryContent.java | 8 +++ .../MatchesRecyclerViewAdapter.java | 51 ++++++++++++------- .../coronawarncompanion/rpis/RpiList.java | 44 +++++++++------- 4 files changed, 83 insertions(+), 38 deletions(-) create mode 100644 corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/DkAndMatchEntries.java diff --git a/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/DkAndMatchEntries.java b/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/DkAndMatchEntries.java new file mode 100644 index 0000000..5d39e79 --- /dev/null +++ b/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/DkAndMatchEntries.java @@ -0,0 +1,18 @@ +package org.tosl.coronawarncompanion.matchentries; + +import org.tosl.coronawarncompanion.diagnosiskeys.DiagnosisKey; + +public class DkAndMatchEntries implements Comparable { + public final DiagnosisKey dk; + public final MatchEntryContent.GroupedByDkMatchEntries matchEntries; + + public DkAndMatchEntries(DiagnosisKey dk, MatchEntryContent.GroupedByDkMatchEntries matchEntries) { + this.dk = dk; + this.matchEntries = matchEntries; + } + + @Override + public int compareTo(DkAndMatchEntries o) { + return this.matchEntries.getMinStartTimeStampUTC().compareTo(o.matchEntries.getMinStartTimeStampUTC()); + } +} diff --git a/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/MatchEntryContent.java b/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/MatchEntryContent.java index bb49cfa..22926cf 100644 --- a/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/MatchEntryContent.java +++ b/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/MatchEntryContent.java @@ -105,11 +105,16 @@ public void add(Matcher.MatchEntry entry, DiagnosisKey dk) { public static class GroupedByDkMatchEntries { private final ArrayList list = new ArrayList<>(); private int groupedByDkRpiCount = 0; + private Integer minStartTimeStampUTC = Integer.MAX_VALUE; public int getGroupedByDkRpiCount() { return groupedByDkRpiCount; } + public Integer getMinStartTimeStampUTC() { + return minStartTimeStampUTC; + } + public ArrayList getList() { return list; } @@ -117,6 +122,9 @@ public ArrayList getList() { public void add(Matcher.MatchEntry entry) { list.add(entry); groupedByDkRpiCount++; + if (minStartTimeStampUTC > entry.startTimestampUTC) { + minStartTimeStampUTC = entry.startTimestampUTC; + } } } } \ No newline at end of file diff --git a/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/MatchesRecyclerViewAdapter.java b/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/MatchesRecyclerViewAdapter.java index 89b641d..5752ca5 100644 --- a/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/MatchesRecyclerViewAdapter.java +++ b/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/matchentries/MatchesRecyclerViewAdapter.java @@ -27,7 +27,6 @@ import android.graphics.Color; import android.util.DisplayMetrics; import android.util.Log; -import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -53,12 +52,12 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; -import java.util.TreeMap; import static org.tosl.coronawarncompanion.tools.Utils.byteArrayToHexString; import static org.tosl.coronawarncompanion.tools.Utils.getMillisFromSeconds; @@ -78,7 +77,7 @@ public class MatchesRecyclerViewAdapter extends RecyclerView.Adapter> mValues; + private final ArrayList mValues; private final Context mContext; private boolean showAllScans = false; @@ -86,13 +85,11 @@ public class MatchesRecyclerViewAdapter extends RecyclerView.Adapter(); - TreeMap> treeMap = new TreeMap<>(); - // Sorted TreeMap > for (Map.Entry entry : dailyMatchEntries.getMap().entrySet()) { - treeMap.put(entry.getValue().getList().get(0).startTimestampUTC, new Pair<>(entry.getKey(), entry.getValue())); + mValues.add(new DkAndMatchEntries(entry.getKey(), entry.getValue())); } - mValues.addAll(treeMap.values()); + Collections.sort(mValues); DisplayMetrics metrics = this.mContext.getResources().getDisplayMetrics(); this.textScalingFactor = metrics.scaledDensity/metrics.density; this.lineColor = resolveColorAttr(android.R.attr.textColorSecondary, context); @@ -109,9 +106,9 @@ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @SuppressWarnings("deprecation") @Override public void onBindViewHolder(final ViewHolder holder, int position) { - holder.mMatchEntriesPair = mValues.get(position); - DiagnosisKey dk = holder.mMatchEntriesPair.first; - MatchEntryContent.GroupedByDkMatchEntries groupedByDkMatchEntries = holder.mMatchEntriesPair.second; + holder.mDkAndMatchEntries = mValues.get(position); + DiagnosisKey dk = holder.mDkAndMatchEntries.dk; + MatchEntryContent.GroupedByDkMatchEntries groupedByDkMatchEntries = holder.mDkAndMatchEntries.matchEntries; ArrayList list = groupedByDkMatchEntries.getList(); @@ -277,9 +274,24 @@ public static MatchEntryDetails getMatchEntryDetails(ArrayList dataPointsInterimMap = new TreeMap<>(); + class InterimDataPoint implements Comparable { + public final Integer timestampLocalTZ; + public final Integer attenuation; - // First step: Create a "flat" sorted list (TreeMap) from all scan records from all matchEntries + private InterimDataPoint(int timestampLocalTZ, int attenuation) { + this.timestampLocalTZ = timestampLocalTZ; + this.attenuation = attenuation; + } + + @Override + public int compareTo(InterimDataPoint o) { + return timestampLocalTZ.compareTo(o.timestampLocalTZ); + } + } + + ArrayList dataPointsInterimList = new ArrayList<>(); + + // First step: Create a "flat" sorted list from all scan records from all matchEntries for (Matcher.MatchEntry matchEntry : list) { // process each matchEntry separately for (ContactRecordsProtos.ScanRecord scanRecord : matchEntry.contactRecords.getRecordList()) { byte[] aem = xorTwoByteArrays(scanRecord.getAem().toByteArray(), matchEntry.aemXorBytes); @@ -296,7 +308,7 @@ public static MatchEntryDetails getMatchEntryDetails(ArrayList txPower) { @@ -319,6 +331,7 @@ public static MatchEntryDetails getMatchEntryDetails(ArrayList mapEntry : dataPointsInterimMap.entrySet()) { - // iterate over sorted TreeMap - int timestampLocalTZDay0 = mapEntry.getKey() - timestampMinOffset; - int attenuation = mapEntry.getValue(); + for (InterimDataPoint interimDataPoint : dataPointsInterimList) { + // iterate over sorted ArrayList + int timestampLocalTZDay0 = interimDataPoint.timestampLocalTZ - timestampMinOffset; + int attenuation = interimDataPoint.attenuation; // Second step: look for a break (>= pauseThresholdSeconds) // suppress break detection at the very first entry @@ -559,7 +572,7 @@ public static class ViewHolder extends RecyclerView.ViewHolder { public final TextView mTextViewRiskDetails; public final TextView mTextViewTxPower; public final LineChart mChartView; - public Pair mMatchEntriesPair; + public DkAndMatchEntries mDkAndMatchEntries; public ViewHolder(View view) { super(view); diff --git a/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/rpis/RpiList.java b/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/rpis/RpiList.java index b0dce60..ae120b3 100644 --- a/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/rpis/RpiList.java +++ b/corona-warn-companion/src/main/java/org/tosl/coronawarncompanion/rpis/RpiList.java @@ -36,11 +36,14 @@ public class RpiList { private static final String TAG = "RpiList"; + @SuppressWarnings("FieldCanBeLocal") + private final boolean addFakeMatches = false; // TODO: ONLY FOR TESTING, MUST BE SET TO FALSE FOR RELEASES! + private final Map mapOfDaysUTCAndListsOfRPIs; // daysSinceEpochUTC, ListsPerDayUTC private final Map mapOfDailyCountsLocalTZ; // daysSinceEpochLocalTZ, numberOfEntries private final int timeZoneOffsetSeconds; - //private final Random rand; + private final Random rand; public static class ListsPerDayUTC { public final HashMap rpiEntries = new HashMap<>(2048); // RpiEntries @@ -124,7 +127,7 @@ public RpiList() { mapOfDaysUTCAndListsOfRPIs = new HashMap<>(); mapOfDailyCountsLocalTZ = new TreeMap<>(); timeZoneOffsetSeconds = CWCApplication.getTimeZoneOffsetSeconds(); - //rand = new Random(); // not very random, but sufficient for the use case here + rand = new Random(); // not very random, but sufficient for the use case here } public void addEntry(Integer daysSinceEpochUTC, byte[] rpiBytes, ContactRecordsProtos.ContactRecords contactRecords) { @@ -188,6 +191,7 @@ public Integer getRpiCountForDaysSinceEpochLocalTZ(Integer daysSinceEpochLocalTZ public RpiEntry searchForRpiOnDaySinceEpochUTCWith2HoursTolerance(Crypto.RpiWithInterval searchRpiWithInterval) { int daysSinceEpochUTC = getDaysSinceEpochFromENIN(searchRpiWithInterval.intervalNumber); RpiEntry matchingRpiEntry = null; + //noinspection ConstantConditions if (searchRpiWithInterval != null) { RpiBytes rpiBytes = new RpiBytes(searchRpiWithInterval.rpiBytes); @@ -238,26 +242,28 @@ public RpiEntry searchForRpiOnDaySinceEpochUTCWith2HoursTolerance(Crypto.RpiWith } } - /* - // Add some random matches, for test purposes only! - // TODO: THIS MUST NOT BE ACTIVE FOR RELEASES! - if (rand.nextInt(100000) >= 99995) { - ListsPerDayUTC listsPerDayUTC = mapOfDaysUTCAndListsOfRPIs.get(daysSinceEpochUTC); - if (listsPerDayUTC != null) { - - int numEntries = listsPerDayUTC.rpiEntries.size(); - int randomPos = rand.nextInt(numEntries)+1; - // The following random selection process is not 100% correct, but sufficient. - Iterator it = listsPerDayUTC.rpiEntries.values().iterator(); - while (it.hasNext() && randomPos > 0) { - matchingRpiEntry = it.next(); - randomPos--; + if (addFakeMatches) { + // Add some random matches, for test purposes only! + // TODO: THIS MUST NOT BE ACTIVE FOR RELEASES! + if (rand.nextInt(100000) >= 99955) { + ListsPerDayUTC listsPerDayUTC = mapOfDaysUTCAndListsOfRPIs.get(daysSinceEpochUTC); + if (listsPerDayUTC != null) { + + int numEntries = listsPerDayUTC.rpiEntries.size(); + int randomPos = rand.nextInt(numEntries) + 1; + // The following random selection process is not 100% correct, but sufficient. + Iterator it = listsPerDayUTC.rpiEntries.values().iterator(); + while (it.hasNext() && randomPos > 0) { + matchingRpiEntry = it.next(); + randomPos--; + } + if (matchingRpiEntry != null) { + Log.d(TAG, "Reporting fake match: matchingRpiEntry.startTimeStampUTC: " + + matchingRpiEntry.startTimeStampUTC); + } } - Log.d(TAG, "Reporting fake match: matchingRpiEntry.startTimeStampUTC: " + - matchingRpiEntry.startTimeStampUTC); } } - */ } return matchingRpiEntry; }