COVIDSafe code from version 1.4 (#3)

This commit is contained in:
COVIDSafe Support 2020-05-26 17:13:26 +10:00 committed by GitHub
parent 56c93f2079
commit b2e0c5b34c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 6865 additions and 481 deletions

View file

@ -36,8 +36,9 @@
30BE1CB523F15D47005DCE4F /* OTPViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BE1CB423F15D47005DCE4F /* OTPViewController.swift */; };
30E91BE923EFE514002D592A /* UploadDataVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E91BE823EFE514002D592A /* UploadDataVC.swift */; };
30E91BEB23EFEA0B002D592A /* CodeInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E91BEA23EFEA0B002D592A /* CodeInputView.swift */; };
30FADDAE23F6896C006C125F /* Encounter+Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FADDAD23F6896C006C125F /* Encounter+Event.swift */; };
469DD4CFF262D29768A8CBEF /* Pods_CovidSafe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BE2A0527997C5042B9E2A41 /* Pods_CovidSafe.framework */; };
3A8951FDED310D7ABA54259E /* Pods_CovidSafe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34E9EB96EA6E42A571E73C8E /* Pods_CovidSafe.framework */; };
5904A5C22462471A008C8012 /* EncounterV1toV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */; };
5904A5C32462471A008C8012 /* EncounterV1toV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */; };
590888AF2431B9E3008C9B9F /* UITextView + Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B55E1912430760500C9E798 /* UITextView + Extensions.swift */; };
590888B02431B9E7008C9B9F /* UIColor + Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC141AD2430685800399FA8 /* UIColor + Extensions.swift */; };
590888B12431B9EB008C9B9F /* Question3ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B86119424303F5E00EA4B6B /* Question3ViewController.swift */; };
@ -52,13 +53,27 @@
590C99332432C1C400A5EC71 /* UploadDataHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B69E7E82430C22E00561DD9 /* UploadDataHomeViewController.swift */; };
592CBB802441A583001FFCE9 /* PersonalDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */; };
592CBB812441A583001FFCE9 /* PersonalDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */; };
59490B64245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59490B63245FE3DA00C9802B /* EncounterV2Mapping.swift */; };
59490B65245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59490B63245FE3DA00C9802B /* EncounterV2Mapping.swift */; };
594E77BF24736B77009B8B34 /* EncounterDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594E77BE24736B77009B8B34 /* EncounterDB.swift */; };
594E77C024736B77009B8B34 /* EncounterDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594E77BE24736B77009B8B34 /* EncounterDB.swift */; };
594E77C2247387B1009B8B34 /* EncounterDB+migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594E77C1247387B1009B8B34 /* EncounterDB+migration.swift */; };
594E77C3247387B1009B8B34 /* EncounterDB+migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594E77C1247387B1009B8B34 /* EncounterDB+migration.swift */; };
5956CD8F246D44EA00EA4D4A /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5956CD8E246D44EA00EA4D4A /* CoreBluetooth.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
5956CD90246D44F100EA4D4A /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5956CD8E246D44EA00EA4D4A /* CoreBluetooth.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
5957EB67244E936E002F5388 /* UnderSixteenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B7ABF27244D6BE100BB249B /* UnderSixteenViewController.swift */; };
5961ABEA2474E358004040DF /* MigrationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5961ABE92474E358004040DF /* MigrationViewController.swift */; };
5961ABEB2474E358004040DF /* MigrationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5961ABE92474E358004040DF /* MigrationViewController.swift */; };
5961ABED2474E464004040DF /* spinner_migrating_db.json in Resources */ = {isa = PBXBuildFile; fileRef = 5961ABEC2474E464004040DF /* spinner_migrating_db.json */; };
5961ABEE2474E464004040DF /* spinner_migrating_db.json in Resources */ = {isa = PBXBuildFile; fileRef = 5961ABEC2474E464004040DF /* spinner_migrating_db.json */; };
596B189924496D32003E190F /* Encounter+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B189824496D32003E190F /* Encounter+Util.swift */; };
596B189A24496D32003E190F /* Encounter+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B189824496D32003E190F /* Encounter+Util.swift */; };
596B189C24499591003E190F /* UploadHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B189B24499591003E190F /* UploadHelper.swift */; };
596B189D24499591003E190F /* UploadHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B189B24499591003E190F /* UploadHelper.swift */; };
597BB7CC245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597BB7CB245FAEC00067A2E2 /* SecKey+CovidSafe.swift */; };
597BB7CD245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597BB7CB245FAEC00067A2E2 /* SecKey+CovidSafe.swift */; };
597BB7CF245FB1250067A2E2 /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597BB7CE245FB1250067A2E2 /* Crypto.swift */; };
597BB7D0245FB1250067A2E2 /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597BB7CE245FB1250067A2E2 /* Crypto.swift */; };
59898603245173C200966E61 /* URLHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59898602245173C200966E61 /* URLHelper.swift */; };
59898604245173C200966E61 /* URLHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59898602245173C200966E61 /* URLHelper.swift */; };
59ACB574242F195A00E63E3C /* InitiateUploadAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59ACB573242F195A00E63E3C /* InitiateUploadAPI.swift */; };
@ -73,8 +88,6 @@
59B7417124514126006E1EEA /* RegistrationConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B7416F24514126006E1EEA /* RegistrationConsentViewController.swift */; };
59F25D69245B917A002A7ED8 /* Spinner_home.json in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D68245B917A002A7ED8 /* Spinner_home.json */; };
59F25D6A245B917A002A7ED8 /* Spinner_home.json in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D68245B917A002A7ED8 /* Spinner_home.json */; };
59F25D6C245BBCA2002A7ED8 /* Spinner_home_upload_complete.json in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D6B245BBCA2002A7ED8 /* Spinner_home_upload_complete.json */; };
59F25D6D245BBCA2002A7ED8 /* Spinner_home_upload_complete.json in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D6B245BBCA2002A7ED8 /* Spinner_home_upload_complete.json */; };
59F25D6F245BED80002A7ED8 /* Debug.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D6E245BED80002A7ED8 /* Debug.storyboard */; };
5B337AAB245A9BBC00537620 /* UploadDataErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B337AAA245A9BBC00537620 /* UploadDataErrorViewController.swift */; };
5B337AAC245A9EEC00537620 /* UploadDataPrefaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B577814245A584C0088F111 /* UploadDataPrefaceViewController.swift */; };
@ -96,11 +109,9 @@
5B92D674243018040049877B /* BluetraceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DC7B8D0242B8536008E1715 /* BluetraceConfig.swift */; };
5B92D675243018040049877B /* IssueInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D300242DF1B000DC9E2A /* IssueInfo.swift */; };
5B92D676243018040049877B /* ADGColorPallete.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D30A242DF1B000DC9E2A /* ADGColorPallete.swift */; };
5B92D677243018040049877B /* Encounter+Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FADDAD23F6896C006C125F /* Encounter+Event.swift */; };
5B92D678243018040049877B /* UIKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2FC242DF1B000DC9E2A /* UIKitExtensions.swift */; };
5B92D679243018040049877B /* InitialScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FACD53923F25A9A0042A33A /* InitialScreenViewController.swift */; };
5B92D67A243018040049877B /* ContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DD05923E2F08400E097EF /* ContactViewController.swift */; };
5B92D67B243018040049877B /* OnboardingStep2cViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817823F16C3900345771 /* OnboardingStep2cViewController.swift */; };
5B92D67C243018040049877B /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DD05B23E2F0A700E097EF /* LogViewController.swift */; };
5B92D67D243018040049877B /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5F83AF23F045A800770DEF /* HomeViewController.swift */; };
5B92D67E243018040049877B /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DD05D23E2F0BA00E097EF /* InfoViewController.swift */; };
@ -188,7 +199,6 @@
5DD41D4723DCB03D00FD4AB0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5DD41D4523DCB03D00FD4AB0 /* LaunchScreen.storyboard */; };
5DD41D4F23DCB05600FD4AB0 /* CentralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D4E23DCB05600FD4AB0 /* CentralController.swift */; };
5DD41D5323DD4CA400FD4AB0 /* PeripheralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D5223DD4CA400FD4AB0 /* PeripheralController.swift */; };
7AB66BCCCA27E969EE126F9F /* Pods_CovidSafe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BE2A0527997C5042B9E2A41 /* Pods_CovidSafe.framework */; };
7F19B4DD23F565850071A11E /* PogoInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F19B4DC23F565850071A11E /* PogoInstructionsViewController.swift */; };
7F2F0BA223EFFF75006D7404 /* OnboardingStep2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2F0BA123EFFF75006D7404 /* OnboardingStep2ViewController.swift */; };
7F36305F23F7F81400CC6E1D /* PushNotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F36305E23F7F81400CC6E1D /* PushNotificationConstants.swift */; };
@ -223,7 +233,6 @@
A767D334242DF1B100DC9E2A /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D309242DF1B000DC9E2A /* Action.swift */; };
A767D335242DF1B100DC9E2A /* ADGColorPallete.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D30A242DF1B000DC9E2A /* ADGColorPallete.swift */; };
A767D336242DF1B100DC9E2A /* GetJMCTargetAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D30B242DF1B000DC9E2A /* GetJMCTargetAction.swift */; };
AC1B51D6C926C60CCC1FB565 /* Pods_CovidSafe_staging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0A3F236B583DE5A31A50F1F /* Pods_CovidSafe_staging.framework */; };
B605A7B12427429D008BA819 /* PlistHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B605A7B02427429D008BA819 /* PlistHelper.swift */; };
B60F8BE4242659810007A641 /* UILabelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60F8BE3242659810007A641 /* UILabelExtension.swift */; };
B615C5A723F8EB1700345969 /* UIProgressView + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615C5A623F8EB1700345969 /* UIProgressView + Extension.swift */; };
@ -233,11 +242,11 @@
C56CF43F23F18A15006B05B0 /* OnboardingStep4ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */; };
C585C83B23EEB99B0061B7C6 /* GradientButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C585C83A23EEB99B0061B7C6 /* GradientButton.swift */; };
C58D817723F169DB00345771 /* OnboardingStep2bViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */; };
C58D817923F16C3900345771 /* OnboardingStep2cViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817823F16C3900345771 /* OnboardingStep2cViewController.swift */; };
C5D209FC23F4476F007233BE /* UITextViewFixed.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D209FB23F4476F007233BE /* UITextViewFixed.swift */; };
D8DEB6822423AE2E00D99925 /* OnboardingStep1bViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DEB6812423AE2E00D99925 /* OnboardingStep1bViewController.swift */; };
D8EB201B23FA722D001C60EC /* HelpNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8EB201A23FA722D001C60EC /* HelpNavController.swift */; };
D8EB201D23FBE216001C60EC /* help_center_article_style.css in Resources */ = {isa = PBXBuildFile; fileRef = D8EB201C23FBE216001C60EC /* help_center_article_style.css */; };
F4992D5F7DAE4B13FF4BB47D /* Pods_CovidSafe_staging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFB86126400FA51506E1B416 /* Pods_CovidSafe_staging.framework */; };
FB12C4C1242F0480007E893B /* RespondToAuthChallengeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C0242F047F007E893B /* RespondToAuthChallengeAPI.swift */; };
FB12C4C3242F0FE9007E893B /* GetTempIdAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C2242F0FE9007E893B /* GetTempIdAPI.swift */; };
FB12C4C524304AF0007E893B /* OnboardingStep1aViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */; };
@ -245,6 +254,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
03E2D9045555F5F013130375 /* Pods-CovidSafe.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.release.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.release.xcconfig"; sourceTree = "<group>"; };
0B1810112431EE610005D11F /* PhoneNumberParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberParser.swift; sourceTree = "<group>"; };
0B22A56A242F286900D1FE60 /* UINavigationBar+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Style.swift"; sourceTree = "<group>"; };
0B55E1912430760500C9E798 /* UITextView + Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView + Extensions.swift"; sourceTree = "<group>"; };
@ -255,6 +265,7 @@
0BC141AB24305D9C00399FA8 /* NSMutableString + Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableString + Extensions.swift"; sourceTree = "<group>"; };
0BC141AD2430685800399FA8 /* UIColor + Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor + Extensions.swift"; sourceTree = "<group>"; };
122AF4E79D17C983066C1CEB /* Pods-CovidSafe-staging.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.release.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.release.xcconfig"; sourceTree = "<group>"; };
1925EA5F4413AD52AC198894 /* Pods-CovidSafe.covid-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.covid-production.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.covid-production.xcconfig"; sourceTree = "<group>"; };
1B86118D24303E7D00EA4B6B /* Questions.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Questions.storyboard; sourceTree = "<group>"; };
1B86119024303EF200EA4B6B /* Question1ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Question1ViewController.swift; sourceTree = "<group>"; };
1B86119224303F4A00EA4B6B /* Question2ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Question2ViewController.swift; sourceTree = "<group>"; };
@ -264,6 +275,7 @@
1B86119A24303FA200EA4B6B /* Question3ErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Question3ErrorViewController.swift; sourceTree = "<group>"; };
1B86119C24303FC000EA4B6B /* QuestionUploadDataViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionUploadDataViewController.swift; sourceTree = "<group>"; };
20A0AADA27329C83CFAB5A7C /* Pods-CovidSafe.covid-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.covid-staging.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.covid-staging.xcconfig"; sourceTree = "<group>"; };
2B916D8946F8A94E32E569C7 /* Pods-CovidSafe.covid-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.covid-staging.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.covid-staging.xcconfig"; sourceTree = "<group>"; };
30BE1CA923F108BA005DCE4F /* UploadFileData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadFileData.swift; sourceTree = "<group>"; };
30BE1CAC23F119FD005DCE4F /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
30BE1CAE23F1349F005DCE4F /* EncounterRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncounterRecord.swift; sourceTree = "<group>"; };
@ -271,15 +283,27 @@
30BE1CB423F15D47005DCE4F /* OTPViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPViewController.swift; sourceTree = "<group>"; };
30E91BE823EFE514002D592A /* UploadDataVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadDataVC.swift; sourceTree = "<group>"; };
30E91BEA23EFEA0B002D592A /* CodeInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeInputView.swift; sourceTree = "<group>"; };
30FADDAD23F6896C006C125F /* Encounter+Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encounter+Event.swift"; sourceTree = "<group>"; };
342FF717762F714E56F4412D /* Pods-CovidSafe.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.release.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.release.xcconfig"; sourceTree = "<group>"; };
34E9EB96EA6E42A571E73C8E /* Pods_CovidSafe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe.framework; sourceTree = BUILT_PRODUCTS_DIR; };
39E21BD3842669F051A5D6D8 /* Pods-CovidSafe.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.debug.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.debug.xcconfig"; sourceTree = "<group>"; };
3A403F07F3B7C5A94CBFC43D /* Pods-CovidSafe-staging.covid-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.covid-staging.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.covid-staging.xcconfig"; sourceTree = "<group>"; };
46A5730925DA6B664DFE9546 /* Pods-CovidSafe-staging.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.debug.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.debug.xcconfig"; sourceTree = "<group>"; };
46D0C3F015CA753BB4D4787D /* Pods-CovidSafe-staging.covid-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.covid-production.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.covid-production.xcconfig"; sourceTree = "<group>"; };
49A22E09D113DF058C94C6E6 /* Pods-CovidSafe-staging.covid-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.covid-staging.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.covid-staging.xcconfig"; sourceTree = "<group>"; };
5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = EncounterV1toV2.xcmappingmodel; sourceTree = "<group>"; };
5909E4AA245043C400D41C26 /* CovidPersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CovidPersistentContainer.swift; sourceTree = "<group>"; };
592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalDetailsViewController.swift; sourceTree = "<group>"; };
59490B61245FE22C00C9802B /* ModelV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ModelV2.xcdatamodel; sourceTree = "<group>"; };
59490B63245FE3DA00C9802B /* EncounterV2Mapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncounterV2Mapping.swift; sourceTree = "<group>"; };
594E77BE24736B77009B8B34 /* EncounterDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncounterDB.swift; sourceTree = "<group>"; };
594E77C1247387B1009B8B34 /* EncounterDB+migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EncounterDB+migration.swift"; sourceTree = "<group>"; };
5956CD8E246D44EA00EA4D4A /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; };
5961ABE92474E358004040DF /* MigrationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationViewController.swift; sourceTree = "<group>"; };
5961ABEC2474E464004040DF /* spinner_migrating_db.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = spinner_migrating_db.json; sourceTree = "<group>"; };
596B189824496D32003E190F /* Encounter+Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encounter+Util.swift"; sourceTree = "<group>"; };
596B189B24499591003E190F /* UploadHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadHelper.swift; sourceTree = "<group>"; };
597BB7CB245FAEC00067A2E2 /* SecKey+CovidSafe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecKey+CovidSafe.swift"; sourceTree = "<group>"; };
597BB7CE245FB1250067A2E2 /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = "<group>"; };
59898602245173C200966E61 /* URLHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHelper.swift; sourceTree = "<group>"; };
59ACB573242F195A00E63E3C /* InitiateUploadAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitiateUploadAPI.swift; sourceTree = "<group>"; };
59ACB575242F404500E63E3C /* DataUploadS3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUploadS3.swift; sourceTree = "<group>"; };
@ -288,7 +312,6 @@
59AF2EB12435A38100ACCAF2 /* CovidRequestRetrier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CovidRequestRetrier.swift; sourceTree = "<group>"; };
59B7416F24514126006E1EEA /* RegistrationConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationConsentViewController.swift; sourceTree = "<group>"; };
59F25D68245B917A002A7ED8 /* Spinner_home.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Spinner_home.json; sourceTree = "<group>"; };
59F25D6B245BBCA2002A7ED8 /* Spinner_home_upload_complete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Spinner_home_upload_complete.json; sourceTree = "<group>"; };
59F25D6E245BED80002A7ED8 /* Debug.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Debug.storyboard; sourceTree = "<group>"; };
5B337AAA245A9BBC00537620 /* UploadDataErrorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadDataErrorViewController.swift; sourceTree = "<group>"; };
5B337AAE245AA26300537620 /* Spinner_upload.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Spinner_upload.json; sourceTree = "<group>"; };
@ -299,7 +322,6 @@
5B92D666243012330049877B /* CovidSafe-config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "CovidSafe-config.plist"; path = "Resources/STG/CovidSafe-config.plist"; sourceTree = "<group>"; };
5B92D6D9243018040049877B /* COVIDSafe-staging.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "COVIDSafe-staging.app"; sourceTree = BUILT_PRODUCTS_DIR; };
5BD3EE8224330E1A0004A007 /* UploadDataStep2VC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadDataStep2VC.swift; sourceTree = "<group>"; };
5BE2A0527997C5042B9E2A41 /* Pods_CovidSafe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5BFFD94A242EC120003AEF4F /* PhoneValidationAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneValidationAPI.swift; sourceTree = "<group>"; };
5D269C0A23E22CC400ADF2DE /* DeviceIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentifier.swift; sourceTree = "<group>"; };
5D269C0C23E2958F00ADF2DE /* BluetraceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetraceManager.swift; sourceTree = "<group>"; };
@ -331,6 +353,7 @@
7FACD53923F25A9A0042A33A /* InitialScreenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialScreenViewController.swift; sourceTree = "<group>"; };
7FEC361423F16A1E00127AFB /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = "<group>"; };
7FF75C212429FEE800C11FEA /* CountriesData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountriesData.swift; sourceTree = "<group>"; };
81B28CDCE1F29BA0F1D30CCE /* Pods-CovidSafe-staging.covid-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.covid-production.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.covid-production.xcconfig"; sourceTree = "<group>"; };
A767D2B6242DF1B000DC9E2A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Feedback.strings; sourceTree = "<group>"; };
A767D2B7242DF1B000DC9E2A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/NewFeedbackFlow.strings; sourceTree = "<group>"; };
A767D2D1242DF1B000DC9E2A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/NewFeedbackFlow.storyboard; sourceTree = "<group>"; };
@ -369,9 +392,7 @@
C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep4ViewController.swift; sourceTree = "<group>"; };
C585C83A23EEB99B0061B7C6 /* GradientButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButton.swift; sourceTree = "<group>"; };
C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep2bViewController.swift; sourceTree = "<group>"; };
C58D817823F16C3900345771 /* OnboardingStep2cViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep2cViewController.swift; sourceTree = "<group>"; };
C5D209FB23F4476F007233BE /* UITextViewFixed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextViewFixed.swift; sourceTree = "<group>"; };
D0A3F236B583DE5A31A50F1F /* Pods_CovidSafe_staging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe_staging.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D8DEB6812423AE2E00D99925 /* OnboardingStep1bViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep1bViewController.swift; sourceTree = "<group>"; };
D8EB201A23FA722D001C60EC /* HelpNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpNavController.swift; sourceTree = "<group>"; };
D8EB201C23FBE216001C60EC /* help_center_article_style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = help_center_article_style.css; sourceTree = "<group>"; };
@ -380,6 +401,8 @@
FB12C4C0242F047F007E893B /* RespondToAuthChallengeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RespondToAuthChallengeAPI.swift; sourceTree = "<group>"; };
FB12C4C2242F0FE9007E893B /* GetTempIdAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetTempIdAPI.swift; sourceTree = "<group>"; };
FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingStep1aViewController.swift; sourceTree = "<group>"; };
FB81BAD81D73C6FD42202A04 /* Pods-CovidSafe-staging.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.release.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.release.xcconfig"; sourceTree = "<group>"; };
FFB86126400FA51506E1B416 /* Pods_CovidSafe_staging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe_staging.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -388,7 +411,7 @@
buildActionMask = 2147483647;
files = (
5956CD8F246D44EA00EA4D4A /* CoreBluetooth.framework in Frameworks */,
AC1B51D6C926C60CCC1FB565 /* Pods_CovidCare_staging.framework in Frameworks */,
F4992D5F7DAE4B13FF4BB47D /* Pods_CovidSafe_staging.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -397,8 +420,7 @@
buildActionMask = 2147483647;
files = (
5956CD90246D44F100EA4D4A /* CoreBluetooth.framework in Frameworks */,
469DD4CFF262D29768A8CBEF /* Pods_CovidCare.framework in Frameworks */,
7AB66BCCCA27E969EE126F9F /* Pods_CovidCare.framework in Frameworks */,
3A8951FDED310D7ABA54259E /* Pods_CovidSafe.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -432,9 +454,9 @@
30BE1CB223F13B26005DCE4F /* Models */ = {
isa = PBXGroup;
children = (
59490B62245FE3BD00C9802B /* V2 */,
5D8DD05F23E319B300E097EF /* Encounter+CoreDataClass.swift */,
5D8DD06023E319B300E097EF /* Encounter+CoreDataProperties.swift */,
30FADDAD23F6896C006C125F /* Encounter+Event.swift */,
596B189824496D32003E190F /* Encounter+Util.swift */,
);
name = Models;
@ -449,7 +471,6 @@
FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */,
7F2F0BA123EFFF75006D7404 /* OnboardingStep2ViewController.swift */,
C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */,
C58D817823F16C3900345771 /* OnboardingStep2cViewController.swift */,
C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */,
5D8DD05923E2F08400E097EF /* ContactViewController.swift */,
5D8DD05B23E2F0A700E097EF /* LogViewController.swift */,
@ -465,6 +486,7 @@
5B7ABF24244D3BC600BB249B /* IsolationSuccessViewController.swift */,
5B7ABF27244D6BE100BB249B /* UnderSixteenViewController.swift */,
59B7416F24514126006E1EEA /* RegistrationConsentViewController.swift */,
5961ABE92474E358004040DF /* MigrationViewController.swift */,
);
name = "View Controllers";
sourceTree = "<group>";
@ -510,20 +532,40 @@
isa = PBXGroup;
children = (
5909E4AA245043C400D41C26 /* CovidPersistentContainer.swift */,
594E77BE24736B77009B8B34 /* EncounterDB.swift */,
594E77C1247387B1009B8B34 /* EncounterDB+migration.swift */,
);
name = CoreData;
sourceTree = "<group>";
};
59490B62245FE3BD00C9802B /* V2 */ = {
isa = PBXGroup;
children = (
59490B63245FE3DA00C9802B /* EncounterV2Mapping.swift */,
5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */,
);
name = V2;
sourceTree = "<group>";
};
5949DC522434859600AE76BC /* lottie */ = {
isa = PBXGroup;
children = (
5961ABEC2474E464004040DF /* spinner_migrating_db.json */,
5B337AAE245AA26300537620 /* Spinner_upload.json */,
59F25D68245B917A002A7ED8 /* Spinner_home.json */,
59F25D6B245BBCA2002A7ED8 /* Spinner_home_upload_complete.json */,
);
name = lottie;
sourceTree = "<group>";
};
597BB7CA245FACE20067A2E2 /* Crypto */ = {
isa = PBXGroup;
children = (
597BB7CB245FAEC00067A2E2 /* SecKey+CovidSafe.swift */,
597BB7CE245FB1250067A2E2 /* Crypto.swift */,
);
name = Crypto;
sourceTree = "<group>";
};
59AF2E97243552FB00ACCAF2 /* Certificates */ = {
isa = PBXGroup;
children = (
@ -603,6 +645,7 @@
5DD41D3923DCB03B00FD4AB0 /* CovidSafe */ = {
isa = PBXGroup;
children = (
597BB7CA245FACE20067A2E2 /* Crypto */,
5949DC522434859600AE76BC /* lottie */,
5B92D661243005B10049877B /* Resources */,
A767D2AE242DF1B000DC9E2A /* Feedback */,
@ -700,8 +743,8 @@
isa = PBXGroup;
children = (
5956CD8E246D44EA00EA4D4A /* CoreBluetooth.framework */,
5BE2A0527997C5042B9E2A41 /* Pods_CovidCare.framework */,
D0A3F236B583DE5A31A50F1F /* Pods_CovidCare_staging.framework */,
34E9EB96EA6E42A571E73C8E /* Pods_CovidSafe.framework */,
FFB86126400FA51506E1B416 /* Pods_CovidSafe_staging.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -732,6 +775,14 @@
342FF717762F714E56F4412D /* Pods-CovidSafe.release.xcconfig */,
E3B9CCF78D93EFF0EF2A0ADB /* Pods-CovidSafe-staging.debug.xcconfig */,
122AF4E79D17C983066C1CEB /* Pods-CovidSafe-staging.release.xcconfig */,
39E21BD3842669F051A5D6D8 /* Pods-CovidSafe.debug.xcconfig */,
2B916D8946F8A94E32E569C7 /* Pods-CovidSafe.covid-staging.xcconfig */,
03E2D9045555F5F013130375 /* Pods-CovidSafe.release.xcconfig */,
1925EA5F4413AD52AC198894 /* Pods-CovidSafe.covid-production.xcconfig */,
46A5730925DA6B664DFE9546 /* Pods-CovidSafe-staging.debug.xcconfig */,
49A22E09D113DF058C94C6E6 /* Pods-CovidSafe-staging.covid-staging.xcconfig */,
FB81BAD81D73C6FD42202A04 /* Pods-CovidSafe-staging.release.xcconfig */,
81B28CDCE1F29BA0F1D30CCE /* Pods-CovidSafe-staging.covid-production.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@ -868,10 +919,11 @@
5B92D6C2243018040049877B /* Localizable.strings in Resources */,
5B92D6C7243018040049877B /* CovidSafe-config.plist in Resources */,
5B92D6C8243018040049877B /* NewFeedbackFlow.strings in Resources */,
5961ABEE2474E464004040DF /* spinner_migrating_db.json in Resources */,
59AF2EAD2435801400ACCAF2 /* AmazonRootCA4.cer in Resources */,
59F25D6F245BED80002A7ED8 /* Debug.storyboard in Resources */,
5B92D6C9243018040049877B /* LaunchScreen.storyboard in Resources */,
5B92D6CB243018040049877B /* Assets.xcassets in Resources */,
59F25D6D245BBCA2002A7ED8 /* Spinner_home_upload_complete.json in Resources */,
5B92D6CC243018040049877B /* Feedback.strings in Resources */,
5B337AB0245AA26300537620 /* Spinner_upload.json in Resources */,
5B92D6CF243018040049877B /* Main.storyboard in Resources */,
@ -893,9 +945,10 @@
A767D30F242DF1B000DC9E2A /* NewFeedbackFlow.strings in Resources */,
5DD41D4723DCB03D00FD4AB0 /* LaunchScreen.storyboard in Resources */,
5DD41D4423DCB03D00FD4AB0 /* Assets.xcassets in Resources */,
59F25D6C245BBCA2002A7ED8 /* Spinner_home_upload_complete.json in Resources */,
A767D30E242DF1B000DC9E2A /* Feedback.strings in Resources */,
5B337AAF245AA26300537620 /* Spinner_upload.json in Resources */,
59AF2EAA2435801400ACCAF2 /* AmazonRootCA3.cer in Resources */,
5961ABED2474E464004040DF /* spinner_migrating_db.json in Resources */,
5DD41D4223DCB03B00FD4AB0 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -998,16 +1051,16 @@
5B92D674243018040049877B /* BluetraceConfig.swift in Sources */,
5B92D675243018040049877B /* IssueInfo.swift in Sources */,
5B92D676243018040049877B /* ADGColorPallete.swift in Sources */,
5B92D677243018040049877B /* Encounter+Event.swift in Sources */,
5B92D678243018040049877B /* UIKitExtensions.swift in Sources */,
5B92D679243018040049877B /* InitialScreenViewController.swift in Sources */,
59490B65245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */,
5B92D67A243018040049877B /* ContactViewController.swift in Sources */,
5B92D67B243018040049877B /* OnboardingStep2cViewController.swift in Sources */,
5B92D67C243018040049877B /* LogViewController.swift in Sources */,
5B92D67D243018040049877B /* HomeViewController.swift in Sources */,
5B92D67E243018040049877B /* InfoViewController.swift in Sources */,
5B92D67F243018040049877B /* PogoInstructionsViewController.swift in Sources */,
FBBBFCE82430A933002B174D /* OnboardingStep1aViewController.swift in Sources */,
594E77C3247387B1009B8B34 /* EncounterDB+migration.swift in Sources */,
590C99332432C1C400A5EC71 /* UploadDataHomeViewController.swift in Sources */,
59B7417124514126006E1EEA /* RegistrationConsentViewController.swift in Sources */,
5B92D680243018040049877B /* CodeInputView.swift in Sources */,
@ -1020,11 +1073,13 @@
5B92D685243018040049877B /* UILabelExtension.swift in Sources */,
5B92D686243018040049877B /* AppDelegate.swift in Sources */,
5B92D687243018040049877B /* PhoneNumberViewController.swift in Sources */,
5961ABEB2474E358004040DF /* MigrationViewController.swift in Sources */,
5B92D688243018040049877B /* BluetraceUtils.swift in Sources */,
5B92D689243018040049877B /* NewFeedbackFlowController.swift in Sources */,
5B92D68A243018040049877B /* Outcome.swift in Sources */,
5B92D68B243018040049877B /* Encounter+EncounterRecord.swift in Sources */,
0B42D0DE2432B39E00E4F44C /* Question2ErrorViewController.swift in Sources */,
594E77C024736B77009B8B34 /* EncounterDB.swift in Sources */,
590888B42431B9F5008C9B9F /* UploadDataNavigationController.swift in Sources */,
5B92D68C243018040049877B /* GetJMCTargetAction.swift in Sources */,
5B92D68D243018040049877B /* Logging.swift in Sources */,
@ -1032,9 +1087,12 @@
5B92D68E243018040049877B /* HelpNavController.swift in Sources */,
5B92D68F243018040049877B /* OTPViewController.swift in Sources */,
596B189A24496D32003E190F /* Encounter+Util.swift in Sources */,
5904A5C32462471A008C8012 /* EncounterV1toV2.xcmappingmodel in Sources */,
5B92D690243018040049877B /* Encounter+CoreDataClass.swift in Sources */,
5B92D691243018040049877B /* RespondToAuthChallengeAPI.swift in Sources */,
5B92D692243018040049877B /* BluetraceManager.swift in Sources */,
597BB7CD245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */,
597BB7D0245FB1250067A2E2 /* Crypto.swift in Sources */,
5B92D693243018040049877B /* UIColor+Hex.swift in Sources */,
5B92D694243018040049877B /* OnboardingStep1ViewController.swift in Sources */,
5B92D695243018040049877B /* DeviceInfoExtension.swift in Sources */,
@ -1113,17 +1171,17 @@
0B69E7EB2430CF4100561DD9 /* UploadDataThankYouHomeViewController.swift in Sources */,
1B86119924303F9600EA4B6B /* Question2ErrorViewController.swift in Sources */,
A767D335242DF1B100DC9E2A /* ADGColorPallete.swift in Sources */,
30FADDAE23F6896C006C125F /* Encounter+Event.swift in Sources */,
A767D327242DF1B100DC9E2A /* UIKitExtensions.swift in Sources */,
7FACD53A23F25A9A0042A33A /* InitialScreenViewController.swift in Sources */,
C58D817923F16C3900345771 /* OnboardingStep2cViewController.swift in Sources */,
59B7417024514126006E1EEA /* RegistrationConsentViewController.swift in Sources */,
5D5F83B023F045A800770DEF /* HomeViewController.swift in Sources */,
5B337AAB245A9BBC00537620 /* UploadDataErrorViewController.swift in Sources */,
FB12C4C524304AF0007E893B /* OnboardingStep1aViewController.swift in Sources */,
7F19B4DD23F565850071A11E /* PogoInstructionsViewController.swift in Sources */,
30E91BEB23EFEA0B002D592A /* CodeInputView.swift in Sources */,
597BB7CF245FB1250067A2E2 /* Crypto.swift in Sources */,
30BE1CAF23F1349F005DCE4F /* EncounterRecord.swift in Sources */,
59490B64245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */,
C58D817723F169DB00345771 /* OnboardingStep2bViewController.swift in Sources */,
59ACB574242F195A00E63E3C /* InitiateUploadAPI.swift in Sources */,
A767D324242DF1B100DC9E2A /* AsyncAction.swift in Sources */,
@ -1138,8 +1196,10 @@
A767D316242DF1B000DC9E2A /* Logging.swift in Sources */,
D8EB201B23FA722D001C60EC /* HelpNavController.swift in Sources */,
596B189924496D32003E190F /* Encounter+Util.swift in Sources */,
5961ABEA2474E358004040DF /* MigrationViewController.swift in Sources */,
30BE1CB523F15D47005DCE4F /* OTPViewController.swift in Sources */,
5D8DD06123E319B300E097EF /* Encounter+CoreDataClass.swift in Sources */,
594E77BF24736B77009B8B34 /* EncounterDB.swift in Sources */,
FB12C4C1242F0480007E893B /* RespondToAuthChallengeAPI.swift in Sources */,
5D269C0D23E2958F00ADF2DE /* BluetraceManager.swift in Sources */,
1B86119124303EF200EA4B6B /* Question1ViewController.swift in Sources */,
@ -1153,6 +1213,7 @@
A767D325242DF1B100DC9E2A /* SendFeedbackAction.swift in Sources */,
C5D209FC23F4476F007233BE /* UITextViewFixed.swift in Sources */,
C585C83B23EEB99B0061B7C6 /* GradientButton.swift in Sources */,
597BB7CC245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */,
5B7ABF25244D3BC600BB249B /* IsolationSuccessViewController.swift in Sources */,
A767D331242DF1B100DC9E2A /* ViewControllerFactory.swift in Sources */,
0BA617CE242E09B200E6C631 /* FeedbackViewController.swift in Sources */,
@ -1186,6 +1247,7 @@
A767D334242DF1B100DC9E2A /* Action.swift in Sources */,
59AF2E992435533A00ACCAF2 /* CovidCertificates.swift in Sources */,
7F2F0BA223EFFF75006D7404 /* OnboardingStep2ViewController.swift in Sources */,
594E77C2247387B1009B8B34 /* EncounterDB+migration.swift in Sources */,
59898603245173C200966E61 /* URLHelper.swift in Sources */,
A767D31F242DF1B000DC9E2A /* HTTPPostFeedbackAction.swift in Sources */,
7F36305F23F7F81400CC6E1D /* PushNotificationConstants.swift in Sources */,
@ -1196,6 +1258,7 @@
1B86119324303F4A00EA4B6B /* Question2ViewController.swift in Sources */,
5D8DD06223E319B300E097EF /* Encounter+CoreDataProperties.swift in Sources */,
A767D32A242DF1B100DC9E2A /* Issue.swift in Sources */,
5904A5C22462471A008C8012 /* EncounterV1toV2.xcmappingmodel in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1308,21 +1371,21 @@
};
59E766DD242CBC590015EA07 /* covid-staging */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 20A0AADA27329C83CFAB5A7C /* Pods-CovidSafe.covid-staging.xcconfig */;
baseConfigurationReference = 2B916D8946F8A94E32E569C7 /* Pods-CovidSafe.covid-staging.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 20;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = CovidSafe/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_NAME = COVIDSafe;
@ -1391,21 +1454,21 @@
};
59E766DF242CBC8C0015EA07 /* covid-production */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 679DE00E3E364DA756795844 /* Pods-CovidSafe.covid-production.xcconfig */;
baseConfigurationReference = 1925EA5F4413AD52AC198894 /* Pods-CovidSafe.covid-production.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 20;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = CovidSafe/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_NAME = COVIDSafe;
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1419,24 +1482,25 @@
};
5B92D6D5243018040049877B /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E3B9CCF78D93EFF0EF2A0ADB /* Pods-CovidSafe-staging.debug.xcconfig */;
baseConfigurationReference = 46A5730925DA6B664DFE9546 /* Pods-CovidSafe-staging.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = CovidSafe/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_MODULE_NAME = COVIDSafe;
PRODUCT_NAME = "COVIDSafe-staging";
PROVISIONING_PROFILE_SPECIFIER = "";
SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065";
@ -1448,24 +1512,25 @@
};
5B92D6D6243018040049877B /* covid-staging */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3A403F07F3B7C5A94CBFC43D /* Pods-CovidSafe-staging.covid-staging.xcconfig */;
baseConfigurationReference = 49A22E09D113DF058C94C6E6 /* Pods-CovidSafe-staging.covid-staging.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = CovidSafe/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_MODULE_NAME = COVIDSafe;
PRODUCT_NAME = "COVIDSafe-staging";
PROVISIONING_PROFILE_SPECIFIER = "";
SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065";
@ -1477,23 +1542,24 @@
};
5B92D6D7243018040049877B /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 122AF4E79D17C983066C1CEB /* Pods-CovidSafe-staging.release.xcconfig */;
baseConfigurationReference = FB81BAD81D73C6FD42202A04 /* Pods-CovidSafe-staging.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = CovidSafe/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_MODULE_NAME = COVIDSafe;
PRODUCT_NAME = "COVIDSafe-staging";
PROVISIONING_PROFILE_SPECIFIER = "";
SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065";
@ -1506,23 +1572,24 @@
};
5B92D6D8243018040049877B /* covid-production */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 46D0C3F015CA753BB4D4787D /* Pods-CovidSafe-staging.covid-production.xcconfig */;
baseConfigurationReference = 81B28CDCE1F29BA0F1D30CCE /* Pods-CovidSafe-staging.covid-production.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = CovidSafe/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_MODULE_NAME = COVIDSafe;
PRODUCT_NAME = "COVIDSafe-staging";
PROVISIONING_PROFILE_SPECIFIER = "";
SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065";
@ -1651,21 +1718,21 @@
};
5DD41D4C23DCB03D00FD4AB0 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6EBCA1AD6D10F8DF7BBDC660 /* Pods-CovidSafe.debug.xcconfig */;
baseConfigurationReference = 39E21BD3842669F051A5D6D8 /* Pods-CovidSafe.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 20;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = CovidSafe/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_NAME = COVIDSafe;
@ -1679,21 +1746,21 @@
};
5DD41D4D23DCB03D00FD4AB0 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 342FF717762F714E56F4412D /* Pods-CovidSafe.release.xcconfig */;
baseConfigurationReference = 03E2D9045555F5F013130375 /* Pods-CovidSafe.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 20;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = CovidSafe/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3;
MARKETING_VERSION = 1.4;
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_NAME = COVIDSafe;
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1747,9 +1814,10 @@
5DD41D7723DE141700FD4AB0 /* tracer.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
59490B61245FE22C00C9802B /* ModelV2.xcdatamodel */,
5DD41D7823DE141700FD4AB0 /* Model.xcdatamodel */,
);
currentVersion = 5DD41D7823DE141700FD4AB0 /* Model.xcdatamodel */;
currentVersion = 59490B61245FE22C00C9802B /* ModelV2.xcdatamodel */;
path = tracer.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View file

@ -50,6 +50,12 @@
ReferencedContainer = "container:CovidSafe.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.CoreData.MigrationDebug 1"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View file

@ -19,7 +19,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setupCoredataDir()
Encounter.timestamp(for: .appStarted)
let firstRun = UserDefaults.standard.bool(forKey: "HasBeenLaunched")
if( !firstRun ) {
let keychain = KeychainSwift()
@ -164,27 +163,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
scheduleReminderNotifications()
}
// MARK: - Core Data stack
lazy var persistentContainer: CovidPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = CovidPersistentContainer(name: "tracer")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func applicationDidBecomeActive(_ application: UIApplication) {
DLog("applicationDidBecomeActive")
Encounter.timestamp(for: .appEnteredForeground)
startAccelerometerUpdates()
clearOldDataInContext()
@ -199,7 +179,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidEnterBackground(_ application: UIApplication) {
DLog("applicationDidEnterBackground")
Encounter.timestamp(for: .appEnteredBackground)
self.dismissBlackscreen()
stopAccelerometerUpdates()
@ -214,25 +193,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationWillTerminate(_ application: UIApplication) {
DLog("applicationWillTerminate")
Encounter.timestamp(for: .appTerminating)
stopAccelerometerUpdates()
}
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
func clearOldDataInContext() {
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
@ -250,7 +214,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
registerBackgroundTask()
let dispatchQueue = DispatchQueue(label: "DeleteOldData", qos: .background)
dispatchQueue.async{
let managedContext = self.persistentContainer.viewContext
guard let persistentContainer = EncounterDB.shared.persistentContainer else {
self.endBackgroundTask()
return
}
let managedContext = persistentContainer.viewContext
if let oldFetchRequest = Encounter.fetchOldEncounters() {
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: oldFetchRequest)
do {

View file

@ -20,7 +20,7 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="586"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ow2-rP-ZcP" userLabel="ContentView">
<rect key="frame" x="0.0" y="0.0" width="375" height="429"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="472"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="AppPermissions2" translatesAutoresizingMaskIntoConstraints="NO" id="zzU-xp-IaB">
<rect key="frame" x="32" y="8" width="311" height="188"/>
@ -38,9 +38,9 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="x1u-Qk-m74">
<rect key="frame" x="32" y="311" width="311" height="43"/>
<rect key="frame" x="32" y="311" width="311" height="64.5"/>
<attributedString key="attributedText">
<fragment content="1. Keep your phone with you when you leave home. ">
<fragment content="1. When you leave home, keep your phone with you and make sure COVIDSafe is active.">
<attributes>
<font key="NSFont" metaFont="system" size="18"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="natural" headIndent="18" tighteningFactorForTruncation="0.0"/>
@ -49,16 +49,30 @@
</attributedString>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2. Keep the app running. " textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sHp-C7-xCZ">
<rect key="frame" x="32" y="370" width="311" height="21.5"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2. Bluetooth® should be kept ON." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sHp-C7-xCZ">
<rect key="frame" x="32" y="391.5" width="311" height="21.5"/>
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="18"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="04D-Hy-djY">
<rect key="frame" x="32" y="407.5" width="311" height="21.5"/>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="04D-Hy-djY">
<rect key="frame" x="32" y="429" width="311" height="43"/>
<attributedString key="attributedText">
<fragment content="3. Keep Bluetooth® on.">
<fragment content="3. COVIDSafe does not send pairing requests. ">
<attributes>
<font key="NSFont" metaFont="system" size="18"/>
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" headIndent="18" tighteningFactorForTruncation="0.0"/>
</attributes>
</fragment>
<fragment content="Learn more">
<attributes>
<color key="NSColor" red="0.0" green="0.54117647059999996" blue="0.13725490200000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<font key="NSFont" metaFont="system" size="18"/>
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" headIndent="18" tighteningFactorForTruncation="0.0"/>
<integer key="NSUnderline" value="1"/>
</attributes>
</fragment>
<fragment content=".">
<attributes>
<font key="NSFont" metaFont="system" size="18"/>
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" headIndent="18" tighteningFactorForTruncation="0.0"/>
@ -66,6 +80,9 @@
</fragment>
</attributedString>
<nil key="highlightedColor"/>
<connections>
<outletCollection property="gestureRecognizers" destination="0yI-MG-Vp0" appends="YES" id="Gwy-3W-KzG"/>
</connections>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
@ -107,7 +124,7 @@
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<segue destination="yl1-rG-jXE" kind="presentation" modalPresentationStyle="fullScreen" id="JfD-5U-gMM"/>
<action selector="continueBtnTapped:" destination="GaQ-f5-ei6" eventType="touchUpInside" id="ksa-Q1-bFw"/>
</connections>
</button>
</subviews>
@ -125,10 +142,18 @@
<viewLayoutGuide key="safeArea" id="PHO-4j-w6y"/>
</view>
<navigationItem key="navigationItem" id="Qmi-E0-d5C"/>
<connections>
<segue destination="yl1-rG-jXE" kind="presentation" identifier="showHomeSegue" modalPresentationStyle="fullScreen" id="Yi1-Hr-f9s"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="SqF-IF-P8L" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
<tapGestureRecognizer id="0yI-MG-Vp0" userLabel="LearnMoreTapped">
<connections>
<action selector="learnMoreTapped:" destination="GaQ-f5-ei6" id="UJg-09-Jdt"/>
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="-3423" y="4081"/>
<point key="canvasLocation" x="-2980" y="3394"/>
</scene>
<!--Onboarding Step 1 View Controller-->
<scene sceneID="26D-Zd-57d">
@ -239,7 +264,7 @@ Together we can help stop the spread and stay healthy.</string>
<rect key="frame" x="0.0" y="0.0" width="375" height="543"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SpU-i1-aUA" userLabel="ContentView">
<rect key="frame" x="0.0" y="0.0" width="375" height="522.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="587"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="BEd-YX-6P6" userLabel="BackButton">
<rect key="frame" x="16" y="16" width="44" height="44"/>
@ -281,8 +306,11 @@ Together we can help stop the spread and stay healthy.</string>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2. Notifications" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DAg-5r-Sgd">
<rect key="frame" x="32" y="485" width="311" height="21.5"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DAg-5r-Sgd">
<rect key="frame" x="32" y="485" width="311" height="86"/>
<string key="text">2. Notifications
COVIDSafe does not send pairing requests.</string>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -329,8 +357,7 @@ Together we can help stop the spread and stay healthy.</string>
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="allowPermissionsBtn:" destination="eME-NJ-Fcz" eventType="touchUpInside" id="siX-i4-aS7"/>
<segue destination="jtV-53-sil" kind="show" id="9GC-gr-FFS"/>
<segue destination="GaQ-f5-ei6" kind="show" id="DrV-xU-Plc"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="CdG-cy-ocX" userLabel="ProgressView">
@ -952,7 +979,6 @@ They will need to register using their own device and phone number so that COVID
<outlet property="verifyButton" destination="lQY-RW-yBL" id="0Zf-Wz-kba"/>
<outlet property="wrongNumberButton" destination="K7X-Ux-SAc" id="yCf-mF-ngh"/>
<segue destination="eME-NJ-Fcz" kind="show" identifier="showAllowPermissionsFromOTPSegue" id="4eL-1Z-MtW"/>
<segue destination="jtV-53-sil" kind="show" identifier="OTPToTurnOnBtSegue" id="Qc8-cY-neO"/>
<segue destination="yl1-rG-jXE" kind="presentation" identifier="OTPToHomeSegue" modalPresentationStyle="fullScreen" id="8rs-ZD-VFl"/>
</connections>
</viewController>
@ -960,73 +986,6 @@ They will need to register using their own device and phone number so that COVID
</objects>
<point key="canvasLocation" x="-2870" y="1012"/>
</scene>
<!--Onboarding Step 2c View Controller-->
<scene sceneID="s5a-9i-aLg">
<objects>
<viewController modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" id="jtV-53-sil" userLabel="Onboarding Step 2c View Controller" customClass="OnboardingStep2cViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="nnw-PT-xcy">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QGC-4P-Xbk">
<rect key="frame" x="32" y="586" width="311" height="49"/>
<color key="backgroundColor" red="0.0" green="0.40000000000000002" blue="0.1058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="49" id="MDf-og-8ou"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
<state key="normal" title="Continue">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="enabledBluetoothBtn:" destination="jtV-53-sil" eventType="touchUpInside" id="OhZ-8d-ygC"/>
</connections>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="AppNoPermissions" translatesAutoresizingMaskIntoConstraints="NO" id="jcN-AE-FsG">
<rect key="frame" x="32" y="8" width="311" height="188"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Turn on your Bluetooth®" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ahv-yZ-Jd3">
<rect key="frame" x="32" y="199" width="311" height="29"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" staticText="YES" header="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Please go to your Settings or Control Center to turn on Bluetooth®. Then come back to the COVIDSafe app to confirm." lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tjL-QF-W6s">
<rect key="frame" x="32" y="236" width="311" height="107.5"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="uhC-er-6bV" firstAttribute="trailing" secondItem="QGC-4P-Xbk" secondAttribute="trailing" constant="32" id="9gn-Mz-yYE"/>
<constraint firstItem="jcN-AE-FsG" firstAttribute="centerX" secondItem="uhC-er-6bV" secondAttribute="centerX" id="Afx-qn-ugT"/>
<constraint firstItem="uhC-er-6bV" firstAttribute="trailing" secondItem="Ahv-yZ-Jd3" secondAttribute="trailing" constant="32" id="ESD-TG-nCp"/>
<constraint firstItem="uhC-er-6bV" firstAttribute="trailing" secondItem="tjL-QF-W6s" secondAttribute="trailing" constant="32" id="ba8-1O-KOC"/>
<constraint firstItem="uhC-er-6bV" firstAttribute="bottom" secondItem="QGC-4P-Xbk" secondAttribute="bottom" constant="32" id="cZR-Lx-IIe"/>
<constraint firstItem="Ahv-yZ-Jd3" firstAttribute="leading" secondItem="uhC-er-6bV" secondAttribute="leading" constant="32" id="gE6-B8-Ukk"/>
<constraint firstItem="QGC-4P-Xbk" firstAttribute="leading" secondItem="uhC-er-6bV" secondAttribute="leading" constant="32" id="ghH-cO-PBt"/>
<constraint firstItem="Ahv-yZ-Jd3" firstAttribute="top" secondItem="jcN-AE-FsG" secondAttribute="bottom" constant="3" id="lAO-nD-Vwl"/>
<constraint firstItem="jcN-AE-FsG" firstAttribute="top" secondItem="uhC-er-6bV" secondAttribute="top" constant="8" id="mQu-H9-RzN"/>
<constraint firstItem="tjL-QF-W6s" firstAttribute="leading" secondItem="uhC-er-6bV" secondAttribute="leading" constant="32" id="sTj-BQ-Nhj"/>
<constraint firstItem="tjL-QF-W6s" firstAttribute="top" secondItem="Ahv-yZ-Jd3" secondAttribute="bottom" constant="8" id="xuJ-ia-nAM"/>
</constraints>
<viewLayoutGuide key="safeArea" id="uhC-er-6bV"/>
</view>
<navigationItem key="navigationItem" id="wa8-bd-DB6"/>
<connections>
<segue destination="GaQ-f5-ei6" kind="show" identifier="showFullySetUpFromTurnOnBtSegue" id="sbt-WA-Zmh"/>
<segue destination="yl1-rG-jXE" kind="presentation" identifier="showHomeFromTurnOnBtSegue" modalPresentationStyle="fullScreen" id="9Us-JB-oI0"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="yOi-Ce-xh5" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3422" y="3134"/>
</scene>
<!--Home-->
<scene sceneID="ExA-0f-AvY">
<objects>
@ -1039,7 +998,7 @@ They will need to register using their own device and phone number so that COVID
<rect key="frame" x="0.0" y="0.0" width="375" height="2301"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="a1C-2s-72y" userLabel="Home Header View">
<rect key="frame" x="0.0" y="0.0" width="375" height="775"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="725"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="u6A-MV-WzM">
<rect key="frame" x="317" y="22" width="50" height="50"/>
@ -1074,54 +1033,56 @@ They will need to register using their own device and phone number so that COVID
<constraint firstAttribute="width" constant="240" id="yaP-9d-JNY"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="7vU-Zc-lZj">
<rect key="frame" x="32" y="423" width="311" height="256"/>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="7vU-Zc-lZj">
<rect key="frame" x="32" y="468" width="311" height="161"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="AxN-oX-9aS" userLabel="ThanksForHelp">
<rect key="frame" x="0.0" y="0.0" width="311" height="128"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Thank you for helping stop the spread of COVID-19. Your information has been uploaded." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="J3y-tK-eCX">
<rect key="frame" x="0.0" y="0.0" width="311" height="78"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vk7-vj-SYv" userLabel="seperator">
<rect key="frame" x="75.5" y="102" width="160" height="2"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstAttribute="height" constant="2" id="2Yz-2U-XRU"/>
<constraint firstAttribute="width" constant="160" id="Pmr-1g-1qw"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="J3y-tK-eCX" secondAttribute="trailing" id="0oL-pQ-gRj"/>
<constraint firstItem="J3y-tK-eCX" firstAttribute="top" secondItem="AxN-oX-9aS" secondAttribute="top" id="2vb-QH-79r"/>
<constraint firstItem="J3y-tK-eCX" firstAttribute="leading" secondItem="AxN-oX-9aS" secondAttribute="leading" id="BAH-sg-Cwn"/>
<constraint firstItem="vk7-vj-SYv" firstAttribute="centerX" secondItem="AxN-oX-9aS" secondAttribute="centerX" id="BSo-tr-gI3"/>
<constraint firstAttribute="bottom" secondItem="vk7-vj-SYv" secondAttribute="bottom" constant="24" id="eNR-8l-aqT"/>
<constraint firstItem="vk7-vj-SYv" firstAttribute="top" secondItem="J3y-tK-eCX" secondAttribute="bottom" constant="24" id="syN-9Z-JWd"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GHB-bi-tze" userLabel="Status">
<rect key="frame" x="0.0" y="128" width="311" height="128"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="COVIDSafe is active. No further action is required." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="49x-lt-ifR">
<rect key="frame" x="0.0" y="0.0" width="311" height="128"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="49x-lt-ifR">
<rect key="frame" x="0.0" y="0.0" width="311" height="43"/>
<string key="text">COVIDSafe is active.
No further action is required.</string>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Your information was uploaded on 19 May 2020." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SdZ-6A-VIB">
<rect key="frame" x="0.0" y="59" width="311" height="43"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vKm-pz-Cun">
<rect key="frame" x="0.0" y="118" width="311" height="43"/>
<attributedString key="attributedText">
<fragment content="COVIDSafe does not send ">
<attributes>
<font key="NSFont" metaFont="system" size="18"/>
<paragraphStyle key="NSParagraphStyle" alignment="center" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
</attributes>
</fragment>
<fragment content="pairing requests">
<attributes>
<font key="NSFont" metaFont="system" size="18"/>
<paragraphStyle key="NSParagraphStyle" alignment="center" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
<integer key="NSUnderline" value="1"/>
</attributes>
</fragment>
<fragment content=".">
<attributes>
<font key="NSFont" metaFont="system" size="18"/>
<paragraphStyle key="NSParagraphStyle" alignment="center" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
</attributes>
</fragment>
</attributedString>
<nil key="highlightedColor"/>
<connections>
<outletCollection property="gestureRecognizers" destination="f47-5X-eXP" appends="YES" id="5Yi-Zr-9XG"/>
</connections>
</label>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="49x-lt-ifR" secondAttribute="trailing" id="9a6-CD-3Oz"/>
<constraint firstItem="49x-lt-ifR" firstAttribute="leading" secondItem="GHB-bi-tze" secondAttribute="leading" id="Jyn-4S-Q8t"/>
<constraint firstItem="49x-lt-ifR" firstAttribute="top" secondItem="GHB-bi-tze" secondAttribute="top" id="tdZ-OR-TpQ"/>
<constraint firstAttribute="bottom" secondItem="49x-lt-ifR" secondAttribute="bottom" id="xnQ-zn-FwO"/>
<constraint firstItem="49x-lt-ifR" firstAttribute="top" secondItem="7vU-Zc-lZj" secondAttribute="top" id="fpf-Dy-hQb"/>
<constraint firstAttribute="bottom" secondItem="vKm-pz-Cun" secondAttribute="bottom" id="ozd-TP-CpW"/>
</constraints>
</view>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" red="0.78431372549019607" green="1" blue="0.72549019607843135" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
@ -1133,12 +1094,12 @@ They will need to register using their own device and phone number so that COVID
<constraint firstItem="g8W-pe-Zjl" firstAttribute="top" secondItem="a1C-2s-72y" secondAttribute="top" constant="168" id="SXW-DN-uaa"/>
<constraint firstAttribute="bottom" secondItem="7vU-Zc-lZj" secondAttribute="bottom" constant="96" id="Sn6-eI-OWc"/>
<constraint firstAttribute="trailing" secondItem="u6A-MV-WzM" secondAttribute="trailing" constant="8" id="TPp-ft-aBl"/>
<constraint firstItem="7vU-Zc-lZj" firstAttribute="top" secondItem="g8W-pe-Zjl" secondAttribute="bottom" constant="15" id="l3p-ND-WnZ"/>
<constraint firstItem="7vU-Zc-lZj" firstAttribute="top" secondItem="g8W-pe-Zjl" secondAttribute="bottom" constant="60" id="l3p-ND-WnZ"/>
<constraint firstItem="7vU-Zc-lZj" firstAttribute="leading" secondItem="a1C-2s-72y" secondAttribute="leading" constant="32" id="vAy-Ic-q12"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Eim-Z7-BkO">
<rect key="frame" x="0.0" y="719" width="375" height="1658"/>
<rect key="frame" x="0.0" y="669" width="375" height="1658"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ewx-Jj-gLM" userLabel="PermissionsView">
<rect key="frame" x="0.0" y="0.0" width="375" height="832"/>
@ -2054,11 +2015,10 @@ They will need to register using their own device and phone number so that COVID
<outlet property="pushNotificationStatusLabel" destination="tdm-Nt-WrT" id="1fz-Dj-kEt"/>
<outlet property="pushNotificationStatusTitle" destination="gx3-nF-2OD" id="A6K-y6-BFD"/>
<outlet property="screenStack" destination="Eim-Z7-BkO" id="vQc-mn-6mJ"/>
<outlet property="thanksForTheHelp" destination="AxN-oX-9aS" id="QhO-zB-K4o"/>
<outlet property="uploadDateLabel" destination="SdZ-6A-VIB" id="2Xb-Nr-mtf"/>
<outlet property="uploadView" destination="2xp-v3-22Q" id="diq-zr-YJL"/>
<outlet property="versionNumberLabel" destination="CD7-Ft-bQU" id="Lqj-N3-DqH"/>
<outlet property="versionView" destination="KEs-yq-szw" id="zPn-dc-ce9"/>
<segue destination="8dk-ge-2YL" kind="presentation" identifier="IsolationSuccessSegue" modalPresentationStyle="fullScreen" id="mjO-TD-7Iq"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="nJa-Mu-MXq" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
@ -2067,6 +2027,11 @@ They will need to register using their own device and phone number so that COVID
<action selector="onSettingsTapped:" destination="yl1-rG-jXE" id="A5D-v5-Mvt"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="f47-5X-eXP" userLabel="BluetoothPairingLabelTapped">
<connections>
<action selector="bluetoothPairingTapped:" destination="yl1-rG-jXE" id="SAI-Sq-Jsb"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="3K8-or-mMv" userLabel="HelpTopicsTapped">
<connections>
<action selector="onHelpButtonTapped:" destination="yl1-rG-jXE" id="hpf-bA-ZiK"/>
@ -2105,75 +2070,6 @@ They will need to register using their own device and phone number so that COVID
</objects>
<point key="canvasLocation" x="2189.5999999999999" y="-1882.7586206896553"/>
</scene>
<!--Isolation Success View Controller-->
<scene sceneID="0nZ-xq-WN5">
<objects>
<viewController id="8dk-ge-2YL" customClass="IsolationSuccessViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Lla-H0-kdm">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dz7-Q3-qhF">
<rect key="frame" x="32" y="586" width="311" height="49"/>
<color key="backgroundColor" red="0.0" green="0.40000000000000002" blue="0.1058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="49" id="VZc-Co-WlR"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
<color key="tintColor" red="0.0" green="0.54117647059999996" blue="0.13725490200000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Continue">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="doneOntap:" destination="8dk-ge-2YL" eventType="touchUpInside" id="1cX-HR-u8X"/>
<action selector="enabledBluetoothBtn:" destination="jtV-53-sil" eventType="touchUpInside" id="EQT-3K-YYd"/>
</connections>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Upload_Success" translatesAutoresizingMaskIntoConstraints="NO" id="NS0-JW-dF4">
<rect key="frame" x="32" y="8" width="311" height="188"/>
<constraints>
<constraint firstAttribute="width" secondItem="NS0-JW-dF4" secondAttribute="height" multiplier="311:188" id="zWE-QF-0JR"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Thank you for helping stop the spread of COVID-19!" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="asx-BK-7er">
<rect key="frame" x="32" y="230" width="311" height="57.5"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" staticText="YES" header="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Youve kept others safe while helping to stop the spread of COVID-19 during self-isolation." lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="izB-Kk-H7a">
<rect key="frame" x="32" y="303.5" width="311" height="57.5"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="NS0-JW-dF4" firstAttribute="top" secondItem="G4r-4V-vDo" secondAttribute="top" constant="8" id="0G3-Aw-ixP"/>
<constraint firstItem="G4r-4V-vDo" firstAttribute="bottom" secondItem="dz7-Q3-qhF" secondAttribute="bottom" constant="32" id="0eC-1X-bDL"/>
<constraint firstItem="NS0-JW-dF4" firstAttribute="centerX" secondItem="Lla-H0-kdm" secondAttribute="centerX" id="DNO-R2-eaP"/>
<constraint firstItem="izB-Kk-H7a" firstAttribute="leading" secondItem="G4r-4V-vDo" secondAttribute="leading" constant="32" id="Kiq-ZY-i0o"/>
<constraint firstItem="asx-BK-7er" firstAttribute="leading" secondItem="G4r-4V-vDo" secondAttribute="leading" constant="32" id="SLj-yu-lFS"/>
<constraint firstItem="dz7-Q3-qhF" firstAttribute="leading" secondItem="G4r-4V-vDo" secondAttribute="leading" constant="32" id="SXg-8D-MkV"/>
<constraint firstItem="NS0-JW-dF4" firstAttribute="leading" secondItem="G4r-4V-vDo" secondAttribute="leading" constant="32" id="Xbo-8Q-ynp"/>
<constraint firstItem="G4r-4V-vDo" firstAttribute="trailing" secondItem="NS0-JW-dF4" secondAttribute="trailing" constant="32" id="bt0-k6-uvx"/>
<constraint firstItem="G4r-4V-vDo" firstAttribute="trailing" secondItem="izB-Kk-H7a" secondAttribute="trailing" constant="32" id="dWa-qW-8kE"/>
<constraint firstItem="G4r-4V-vDo" firstAttribute="trailing" secondItem="dz7-Q3-qhF" secondAttribute="trailing" constant="32" id="oUZ-cg-Zif"/>
<constraint firstItem="izB-Kk-H7a" firstAttribute="top" secondItem="asx-BK-7er" secondAttribute="bottom" constant="16" id="qfJ-8d-Do5"/>
<constraint firstItem="G4r-4V-vDo" firstAttribute="trailing" secondItem="asx-BK-7er" secondAttribute="trailing" constant="32" id="r5t-C4-RAX"/>
<constraint firstItem="asx-BK-7er" firstAttribute="top" secondItem="NS0-JW-dF4" secondAttribute="bottom" constant="34" id="zvw-yT-oOA"/>
</constraints>
<viewLayoutGuide key="safeArea" id="G4r-4V-vDo"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="q84-vG-DNL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3380" y="-2840"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="39N-pk-1gr">
<objects>
@ -2247,16 +2143,77 @@ and save lives.</string>
<segue destination="8nR-hO-fWt" kind="show" identifier="initialScreenToIWantToHelpSegue" id="eMC-CV-hVS"/>
<segue destination="mND-9i-sIw" kind="show" identifier="initialScreenToGetOTPSegue" id="rHc-rZ-59x"/>
<segue destination="eME-NJ-Fcz" kind="show" identifier="initialScreenToAllowPermissionsSegue" id="XiO-Zp-pOg"/>
<segue destination="jtV-53-sil" kind="show" identifier="initialScreenToTurnOnBtSegue" id="ap4-2a-3fH"/>
<segue destination="yl1-rG-jXE" kind="presentation" identifier="initialScreenToHomeSegue" modalPresentationStyle="fullScreen" id="Eya-O4-CPO"/>
<segue destination="2XR-xi-raR" kind="show" identifier="initialScreenToConsentSegue" id="ntT-Wx-kqK"/>
<segue destination="tmd-7A-Wz4" kind="show" identifier="initialPersonalDetailsSegue" id="cLu-Ng-v4y"/>
<segue destination="wD9-lR-1wv" kind="presentation" identifier="presentMigrationSegue" id="w3o-Dg-9eW"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="29g-dC-yWi" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-8290" y="339"/>
</scene>
<!--Migration View Controller-->
<scene sceneID="1KH-mM-D5F">
<objects>
<viewController storyboardIdentifier="migrationInProgress" modalPresentationStyle="fullScreen" id="wD9-lR-1wv" customClass="MigrationViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="cPl-cO-Fbz">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="govLogoWhite" translatesAutoresizingMaskIntoConstraints="NO" id="Qov-O8-A7L">
<rect key="frame" x="113.5" y="30" width="148" height="77"/>
<constraints>
<constraint firstAttribute="width" secondItem="Qov-O8-A7L" secondAttribute="height" multiplier="148:77" id="aqp-uc-4R5"/>
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SLW-8v-OdL" userLabel="Blue background view">
<rect key="frame" x="0.0" y="137" width="375" height="530"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="COVIDSafe update is in progress.Please make sure your phone is not switched off until the update is complete." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1Wo-Qe-Wye">
<rect key="frame" x="32" y="40" width="311" height="107.5"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SOF-ig-kwj" userLabel="AnimatingView">
<rect key="frame" x="67.5" y="189.5" width="240" height="240"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="kxC-N2-PH3"/>
<constraint firstAttribute="height" constant="240" id="ryn-nk-rKK"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.7843137255" green="1" blue="0.72549019609999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="1Wo-Qe-Wye" secondAttribute="trailing" constant="32" id="8LA-9O-xXW"/>
<constraint firstItem="1Wo-Qe-Wye" firstAttribute="top" secondItem="SLW-8v-OdL" secondAttribute="top" constant="40" id="D5D-Ij-J3b"/>
<constraint firstItem="SOF-ig-kwj" firstAttribute="top" secondItem="1Wo-Qe-Wye" secondAttribute="bottom" constant="42" id="QeO-v6-n9q"/>
<constraint firstItem="SOF-ig-kwj" firstAttribute="centerX" secondItem="SLW-8v-OdL" secondAttribute="centerX" id="TVj-vF-v4H"/>
<constraint firstItem="1Wo-Qe-Wye" firstAttribute="leading" secondItem="SLW-8v-OdL" secondAttribute="leading" constant="32" id="wrG-HY-APO"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Qov-O8-A7L" firstAttribute="width" secondItem="Qov-O8-A7L" secondAttribute="height" multiplier="148:77" id="1iI-wS-6LY"/>
<constraint firstItem="SLW-8v-OdL" firstAttribute="leading" secondItem="ZLv-lh-E2f" secondAttribute="leading" id="5do-pU-Abn"/>
<constraint firstItem="SLW-8v-OdL" firstAttribute="trailing" secondItem="ZLv-lh-E2f" secondAttribute="trailing" id="PDr-fw-RgW"/>
<constraint firstItem="ZLv-lh-E2f" firstAttribute="bottom" secondItem="SLW-8v-OdL" secondAttribute="bottom" id="Tlz-3G-C3g"/>
<constraint firstItem="Qov-O8-A7L" firstAttribute="centerX" secondItem="cPl-cO-Fbz" secondAttribute="centerX" id="faB-hs-F95"/>
<constraint firstItem="SLW-8v-OdL" firstAttribute="top" secondItem="Qov-O8-A7L" secondAttribute="bottom" constant="30" id="nKo-BV-iiu"/>
<constraint firstItem="Qov-O8-A7L" firstAttribute="top" secondItem="ZLv-lh-E2f" secondAttribute="top" constant="30" id="u5Z-1m-FSD"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ZLv-lh-E2f"/>
</view>
<connections>
<outlet property="animationContainer" destination="SOF-ig-kwj" id="24K-l5-R3z"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="3IU-Mp-wPy" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-8561" y="1143"/>
</scene>
<!--Onboarding Step 1b View Controller-->
<scene sceneID="f3t-QQ-sex">
<objects>
@ -2722,7 +2679,7 @@ See the COVIDSafe *privacy policy* for further details about your rights about y
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Full name (first, last)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EIF-xa-lvX">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Full name (First Last)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EIF-xa-lvX">
<rect key="frame" x="32" y="109.5" width="311" height="21.5"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<nil key="textColor"/>
@ -2755,7 +2712,7 @@ See the COVIDSafe *privacy policy* for further details about your rights about y
<constraint firstAttribute="width" constant="12" id="QNs-P4-1Be"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Postcode (eg 3000)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7RM-TO-7v0">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Postcode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7RM-TO-7v0">
<rect key="frame" x="32" y="293.5" width="311" height="21.5"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<nil key="textColor"/>
@ -3094,22 +3051,19 @@ See the COVIDSafe *privacy policy* for further details about your rights about y
</scene>
</scenes>
<inferredMetricsTieBreakers>
<segue reference="h8R-i7-zdK"/>
<segue reference="ALe-An-dsT"/>
<segue reference="8rs-ZD-VFl"/>
<segue reference="Qc8-cY-neO"/>
<segue reference="bMl-IY-pjw"/>
<segue reference="WC1-tG-bo5"/>
<segue reference="cLu-Ng-v4y"/>
<segue reference="ntT-Wx-kqK"/>
<segue reference="Eya-O4-CPO"/>
<segue reference="iUa-3s-4XB"/>
<segue reference="XiO-Zp-pOg"/>
</inferredMetricsTieBreakers>
<resources>
<image name="AppNoPermissions" width="311" height="188"/>
<image name="AppPermissions1" width="311" height="188"/>
<image name="AppPermissions2" width="311" height="188"/>
<image name="ChevronRight" width="24" height="24"/>
<image name="CovidPermissionsOff" width="240" height="240"/>
<image name="ShareApp" width="24" height="24"/>
<image name="Splash_logo" width="240" height="240"/>
<image name="Upload_Success" width="311" height="188"/>
<image name="WhiteSmallGovCrest" width="44" height="31"/>
<image name="arrow-left" width="24" height="24"/>
<image name="bell 1" width="40" height="40"/>

View file

@ -13,8 +13,12 @@ struct BluetraceConfig {
static let BluetoothServiceID = CBUUID(string: "\(PlistHelper.getvalueFromInfoPlist(withKey: "TRACER_SVC_ID") ?? "B82AB3FC-1595-4F6A-80F0-FE094CC218F9")")
static let OrgID = "AU_DTA"
static let ProtocolVersion = 1
static let ProtocolVersion = 2
static let CentralScanInterval = 60.0 // in seconds
static let CentralScanDuration = 10 // in seconds
static let DummyModel = ""
static let DummyRSSI = 999
static let DummyTxPower = 999
}

View file

@ -65,22 +65,30 @@ class CentralController: NSObject {
func shouldRecordEncounter(_ encounter: EncounterRecord) -> Bool {
guard let scannedDate = encounter.timestamp else {
DLog("Not recorded encounter before \(encounter)")
return true
}
if abs(scannedDate.timeIntervalSinceNow) > BluetraceConfig.CentralScanInterval {
DLog("Encounter last recorded \(abs(scannedDate.timeIntervalSinceNow)) seconds ago")
return true
}
return false
}
func shouldReconnectToPeripheral(peripheral: CBPeripheral) -> Bool {
guard peripheral.state == .disconnected else {
return false
}
guard let encounteredPeripheral = scannedPeripherals[peripheral.identifier] else {
DLog("Not previously encountered CBPeripheral \(String(describing: peripheral.name))")
return true
}
guard let scannedDate = encounteredPeripheral.encounter.timestamp else {
DLog("Not previously recorded an encounter with \(encounteredPeripheral)")
return true
}
if abs(scannedDate.timeIntervalSinceNow) > BluetraceConfig.CentralScanInterval {
DLog("Peripheral last recorded \(abs(scannedDate.timeIntervalSinceNow)) seconds ago")
return true
}
return false
@ -110,7 +118,6 @@ extension CentralController: CBCentralManagerDelegate {
switch central.state {
case .poweredOn:
DLog("CC Starting a scan")
Encounter.timestamp(for: .scanningStarted)
// for all peripherals that are not disconnected, disconnect them
self.scannedPeripherals.forEach { (scannedPeri) in
@ -128,7 +135,7 @@ extension CentralController: CBCentralManagerDelegate {
central.connect(recoveredPeripheral)
}
central.scanForPeripherals(withServices: [BluetraceConfig.BluetoothServiceID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(true)])
central.scanForPeripherals(withServices: [BluetraceConfig.BluetoothServiceID], options:nil)
default:
DLog("State chnged to \(central.state)")
}
@ -196,10 +203,8 @@ extension CentralController: CBCentralManagerDelegate {
if let encounteredPeripheral = scannedPeripherals[peripheral.identifier] {
if shouldReconnectToPeripheral(peripheral: encounteredPeripheral.peripheral) {
peripheral.delegate = self
if peripheral.state != .connected {
central.connect(peripheral)
DLog("found previous peripheral from more than 60 seconds ago")
}
} else {
DLog("iOS Peripheral \(peripheral) has been discovered already in this window, will not attempt to connect to it again")
if let scannedDate = encounteredPeripheral.encounter.timestamp {
@ -217,7 +222,8 @@ extension CentralController: CBCentralManagerDelegate {
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
let peripheralStateString = BluetraceUtils.peripheralStateToString(peripheral.state)
DLog("CC didConnect peripheral peripheralCentral state: \(BluetraceUtils.centralStateToString(central.state)), Peripheral state: \(peripheralStateString)")
guard shouldReconnectToPeripheral(peripheral: peripheral) else {
guard let seenPeripheral = scannedPeripherals[peripheral.identifier], shouldRecordEncounter(seenPeripheral.encounter) else {
central.cancelPeripheralConnection(peripheral)
return
}
@ -283,15 +289,22 @@ extension CentralController: CBPeripheralDelegate {
DLog("rssi should be present in \(currEncounter.encounter)")
return
}
let dataToWrite = CentralWriteData(modelC: DeviceIdentifier.getModel(),
let encounterToBroadcast = EncounterBlob(modelC: DeviceIdentifier.getModel(),
rssi: rssi,
txPower: currEncounter.encounter.txPower,
msg: tempId,
org: BluetraceConfig.OrgID,
v: BluetraceConfig.ProtocolVersion)
modelP: nil,
msg: tempId)
do {
let jsonMsg = try JSONEncoder().encode(encounterToBroadcast)
let encryptedMsg = try Crypto.encrypt(dataToEncrypt: jsonMsg)
let dataToWrite = CentralWriteData(modelC: BluetraceConfig.DummyModel,
rssi: Double(BluetraceConfig.DummyRSSI),
txPower: Double(BluetraceConfig.DummyTxPower),
msg: encryptedMsg,
org: BluetraceConfig.OrgID,
v: BluetraceConfig.ProtocolVersion)
let encodedData = try JSONEncoder().encode(dataToWrite)
peripheral.writeValue(encodedData, for: characteristic, type: .withResponse)
} catch {
@ -314,8 +327,7 @@ extension CentralController: CBPeripheralDelegate {
if let scannedPeri = scannedPeripherals[peripheral.identifier],
let characteristicValue = characteristic.value,
shouldRecordEncounter(scannedPeri.encounter)
{
shouldRecordEncounter(scannedPeri.encounter) {
do {
let peripheralCharData = try JSONDecoder().decode(PeripheralCharacteristicsData.self, from: characteristicValue)
var encounterStruct = scannedPeri.encounter
@ -325,10 +337,12 @@ extension CentralController: CBPeripheralDelegate {
encounterStruct.v = peripheralCharData.v
encounterStruct.timestamp = Date()
scannedPeripherals.updateValue((scannedPeri.peripheral, encounterStruct), forKey: peripheral.identifier)
encounterStruct.saveToCoreData()
// here the remote blob will be msg and modelp if v1, msg if v2
// local blob will be rssi, txpower, modelc
try encounterStruct.saveRemotePeripheralToCoreData()
DLog("Central recorded encounter with \(String(describing: scannedPeri.peripheral.name))")
} catch {
DLog("Error: \(error). CharacteristicValue is \(characteristicValue)")
DLog("Error: \(error). CharacteristicValue is \(String(data: characteristicValue, encoding: .utf8) ?? "<nil>")")
}
} else {
DLog("Error: scannedPeripherals[peripheral.identifier] is \(String(describing: scannedPeripherals[peripheral.identifier])), characteristic.value is \(String(describing: characteristic.value))")

View file

@ -19,12 +19,13 @@ final class ContactViewController: UIViewController {
}
func fetchContacts() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
guard let persistentContainer =
EncounterDB.shared.persistentContainer else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let managedContext = persistentContainer.viewContext
let fetchRequest = Encounter.fetchRequestForRecords()
let sortByContactId = NSSortDescriptor(key: "msg", ascending: false)
let sortByContactId = NSSortDescriptor(key: "timestamp", ascending: false)
fetchRequest.sortDescriptors = [sortByContactId]
fetchedResultsController = NSFetchedResultsController<Encounter>(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController?.delegate = self
@ -44,11 +45,11 @@ final class ContactViewController: UIViewController {
}
var contactCounts: [String: Int] = [:]
for encounter in encounters {
if encounter.msg != nil {
if contactCounts[encounter.msg!] == nil {
contactCounts[encounter.msg!] = 0
if encounter.remoteBlob != nil {
if contactCounts[encounter.remoteBlob!] == nil {
contactCounts[encounter.remoteBlob!] = 0
}
contactCounts[encounter.msg!]! += 1
contactCounts[encounter.remoteBlob!]! += 1
}
}
var contactTuples = contactCounts.map { ($0.key, $0.value) }

196
CovidSafe/Crypto.swift Normal file
View file

@ -0,0 +1,196 @@
//
// Crypto.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import CommonCrypto
import Foundation
import Security
enum SecurityError: Error {
case PublicKeyCopyError
case EncryptionFailedError(_ status: CCCryptorStatus)
case DigestFailedError
case EncryptionLengthError
case EncryptionKeyLengthError
case UnexpectedNilKeys
}
class Crypto {
private static var cachedExportPublicKey: Data?
private static var cachedAesKey: Data?
private static var cachedMacKey: Data?
private static var keyGenTime: Int64 = Int64.min
private static var counter: UInt16 = 0
private static let NONCE_PADDING: Data = Data([UInt8](repeating: UInt8(0x0E), count: 14))
private static let keyCacheQueue = DispatchQueue(label: "au.gov.health.covidsafe.crypto")
private static let KEY_GEN_TIME_DELTA: Int64 = 450 // 7.5 minutes
#if DEBUG
private static let publicKey = Data(base64Encoded: "BNrAcR+C6nkCpIYS9KWYt0Z5Sbleh7UybHmIT2T9YzuR9RzTh3YZcMBjr1K6smeDJW7sPCvMFJNWVPkk3exqjkQ=")
#else
private static let publicKey = Data(base64Encoded: "BDQbOM4lxeK6ed9br26qvcwsYgaUK9w3CozIHP1gOhR7+qwb7vrh0kSSUUtsayekard9EHElA9RNn/3dJW9hr7I=")
#endif
/**
Get a series of secrets that can be decrypted by the server key. The returned data is:
1. the ephemeral public key used for decrypting
2. the AES encryption key
3. the HMAC signature key
4. the IV for AES encryption
- Parameter serverKey: X9.63 formatted P-256 public key for the server
- Throws: Errors from Security framework, or `SecurityError.PublicKeyCopyError`
if function failed to derive public key from the ephemeral private key
- Returns:
- publicKey: exported public P-256 key (compressed form)
- aesKey: ephemeral 16-byte AES-128 key
- macKey: ephemeral 16-byte key for HMAC
- iv: ephemeral 16-byte AES-128 IV
*/
private static func getEphemeralSecrets(_ serverKey: Data) throws -> (publicKey: Data, aesKey: Data, macKey: Data) {
// Server public key
var err: Unmanaged<CFError>?
let serverKeyOptions: [CFString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits: 256,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
]
guard let serverPublicKey = SecKeyCreateWithData(serverKey as CFData, serverKeyOptions as CFDictionary, &err) else {
throw err!.takeRetainedValue() as Error
}
// CREATE A LOCAL EPHEMERAL P-256 KEYPAIR
let ephereralPublicKeyAttributes: [CFString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits: 256,
]
guard let ephemeralPrivateKey = SecKeyCreateRandomKey(ephereralPublicKeyAttributes as CFDictionary, &err) else {
throw err!.takeRetainedValue() as Error
}
guard let ephemeralPublicKey = SecKeyCopyPublicKey(ephemeralPrivateKey) else {
throw SecurityError.PublicKeyCopyError
}
// Exported ephemeral public key for sending/MACing later (compressed format, per ANSI X9.62)
let exportPublicKey = try ephemeralPublicKey.CopyCompressedECPublicKey()
// COMPUTE SHARED SECRET
let params = [SecKeyKeyExchangeParameter.requestedSize.rawValue: 32]
guard let sharedSecret = SecKeyCopyKeyExchangeResult(ephemeralPrivateKey,
SecKeyAlgorithm.ecdhKeyExchangeStandard,
serverPublicKey,
params as CFDictionary,
&err) as Data? else {
throw err!.takeRetainedValue() as Error
}
// KDF THE SHARED SECRET TO GET ENC KEY, MAC KEY
var keysHashCtx = CC_SHA256_CTX()
// For keys we'll be using SHA256(sharedSecret)
var res: Int32
var keysHashValue = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
CC_SHA256_Init(&keysHashCtx)
res = sharedSecret.withUnsafeBytes {
return CC_SHA256_Update(&keysHashCtx, $0.baseAddress, CC_LONG(sharedSecret.count))
}
guard res == 1 else { throw SecurityError.DigestFailedError }
res = keysHashValue.withUnsafeMutableBytes {
return CC_SHA256_Final($0.bindMemory(to: UInt8.self).baseAddress, &keysHashCtx)
}
guard res == 1 else { throw SecurityError.DigestFailedError }
// Form the keys
let aesKey = keysHashValue[..<kCCKeySizeAES128]
let macKey = keysHashValue[kCCKeySizeAES128...]
// At return, the refs to ephemeralPrivateKey and sharedSecret will be dropped and they will be cleared
return (exportPublicKey, aesKey, macKey)
}
static func buildSecretData(_ serverPublicKey: Data, _ plaintext: Data) throws -> Data {
// Get our ephemeral secrets that will de disposed at the end of this function
let (cachedExportPublicKey, cachedAESKey, cachedMacKey, nonce) = try keyCacheQueue.sync { () -> (Data?, Data?, Data?, Data) in
if Crypto.keyGenTime <= Int64(Date().timeIntervalSince1970) - KEY_GEN_TIME_DELTA || Crypto.counter >= 65535 {
(Crypto.cachedExportPublicKey, Crypto.cachedAesKey, Crypto.cachedMacKey) = try getEphemeralSecrets(serverPublicKey)
Crypto.keyGenTime = Int64(Date().timeIntervalSince1970)
Crypto.counter = 0
} else {
Crypto.counter += 1
}
let nonce = withUnsafeBytes(of: Crypto.counter.bigEndian) { Data($0) }
return (Crypto.cachedExportPublicKey, Crypto.cachedAesKey, Crypto.cachedMacKey, nonce)
}
guard let exportPublicKey = cachedExportPublicKey, let aesKey = cachedAESKey, let macKey = cachedMacKey else {
throw SecurityError.UnexpectedNilKeys
}
// AES ENCRYPT DATA
// IV = AES(ctr, iv=null), AES(plaintext, iv=IV) === AES(ctr_with_padding || plaintext, iv=null)
// Using the latter construction to reduce key expansions
// Under PKCS#7 padding, we pad out to a complete blocksize but if the input is an exact multiple of blocksize,
// then we add an extra block on. So in both cases it's 16 bytes + (dataLen/16 + 1) * 16 bytes long
let outputLen = ((plaintext.count / kCCBlockSizeAES128) + 2) * kCCBlockSizeAES128
let nullIV = Data(count: 16)
var plaintextWithIV = Data(capacity: plaintext.count + 16)
plaintextWithIV.append(nonce)
plaintextWithIV.append(NONCE_PADDING)
plaintextWithIV.append(plaintext)
var ciphertextWithIV = Data(count: outputLen)
var dataWrittenLen = 0
let status = ciphertextWithIV.withUnsafeMutableBytes { ciphertextPtr in
plaintextWithIV.withUnsafeBytes { plaintextPtr in
nullIV.withUnsafeBytes { ivPtr in
aesKey.withUnsafeBytes { aesKeyPtr in
return CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding),
aesKeyPtr.baseAddress, kCCKeySizeAES128, ivPtr.baseAddress,
plaintextPtr.baseAddress, plaintextWithIV.count,
ciphertextPtr.baseAddress, outputLen,
&dataWrittenLen)
}
}
}
}
guard status == kCCSuccess else {
throw SecurityError.EncryptionFailedError(status)
}
guard outputLen == dataWrittenLen else {
throw SecurityError.EncryptionLengthError
}
let ciphertext = ciphertextWithIV[16...]
// HMAC
var macValue = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
var hmacContext = CCHmacContext()
macKey.withUnsafeBytes { CCHmacInit(&hmacContext, CCHmacAlgorithm(kCCHmacAlgSHA256), $0.baseAddress, macKey.count) }
exportPublicKey.withUnsafeBytes { CCHmacUpdate(&hmacContext, $0.baseAddress, exportPublicKey.count) }
nonce.withUnsafeBytes { CCHmacUpdate(&hmacContext, $0.baseAddress, nonce.count) }
ciphertext.withUnsafeBytes { CCHmacUpdate(&hmacContext, $0.baseAddress, ciphertext.count) }
macValue.withUnsafeMutableBytes { CCHmacFinal(&hmacContext, $0.bindMemory(to: UInt8.self).baseAddress) }
// Build the final payload: ephemeral public key || nonce || encrypted data || HMAC
var finalData = Data(capacity: exportPublicKey.count + ciphertext.count + 18)
finalData.append(exportPublicKey)
finalData.append(nonce)
finalData.append(ciphertext)
finalData.append(macValue[..<16])
return finalData
}
public static func encrypt(dataToEncrypt: Data) throws -> String {
guard let publicKey = publicKey else {
throw SecurityError.PublicKeyCopyError
}
let encryptedData = try buildSecretData(publicKey, dataToEncrypt)
return encryptedData.base64EncodedString()
}
}

View file

@ -117,12 +117,12 @@
<action selector="logoutBtn:" destination="dhe-6o-fvJ" eventType="touchUpInside" id="wv7-nj-02U"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EKG-5D-Wyg">
<rect key="frame" x="16" y="493" width="155" height="30"/>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FMQ-oF-sLW">
<rect key="frame" x="161" y="59" width="70" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Request Upload PIN"/>
<state key="normal" title="Export DB"/>
<connections>
<action selector="requestUploadOTP:" destination="dhe-6o-fvJ" eventType="touchUpInside" id="C8K-BY-12Z"/>
<action selector="dumpDBpressed:" destination="dhe-6o-fvJ" eventType="touchUpInside" id="4VP-XF-A4v"/>
</connections>
</button>
</subviews>

View file

@ -15,13 +15,10 @@ extension Encounter {
enum CodingKeys: String, CodingKey {
case timestamp
case msg
case modelC
case modelP
case rssi
case txPower
case org
case v
case localBlob
case remoteBlob
}
@nonobjc public class func fetchRequest() -> NSFetchRequest<Encounter> {
@ -30,24 +27,6 @@ extension Encounter {
@nonobjc public class func fetchRequestForRecords() -> NSFetchRequest<Encounter> {
let fetchRequest = NSFetchRequest<Encounter>(entityName: "Encounter")
let predicateString = Encounter.Event
.allCases
.map { "msg != '\($0.rawValue)'" }
.joined(separator: " and ")
fetchRequest.predicate = NSPredicate(format: predicateString)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)]
return fetchRequest
}
@nonobjc public class func fetchRequestForEvents() -> NSFetchRequest<Encounter> {
let fetchRequest = NSFetchRequest<Encounter>(entityName: "Encounter")
let predicateString = Encounter.Event
.allCases
.map { "msg = '\($0.rawValue)'" }
.joined(separator: " or ")
fetchRequest.predicate = NSPredicate(format: predicateString)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)]
return fetchRequest
}
@ -70,39 +49,33 @@ extension Encounter {
}
@NSManaged public var timestamp: Date?
@NSManaged public var msg: String?
@NSManaged public var modelC: String?
@NSManaged public var modelP: String?
@NSManaged public var rssi: NSNumber?
@NSManaged public var txPower: NSNumber?
@NSManaged public var org: String?
@NSManaged public var v: NSNumber?
@NSManaged public var localBlob: String?
@NSManaged public var remoteBlob: String?
func set(encounterStruct: EncounterRecord) {
func set(encounterStruct: EncounterRecord, remoteBlob: String, localBlob: String) {
setValue(encounterStruct.timestamp, forKeyPath: "timestamp")
setValue(encounterStruct.msg, forKeyPath: "msg")
setValue(encounterStruct.modelC, forKeyPath: "modelC")
setValue(encounterStruct.modelP, forKeyPath: "modelP")
setValue(encounterStruct.rssi, forKeyPath: "rssi")
setValue(encounterStruct.txPower, forKeyPath: "txPower")
setValue(encounterStruct.org, forKeyPath: "org")
// when we save locally we've already converted v1 messages to encrypted v2 spec, so we save the record as a v2 record
if (encounterStruct.v == 1) {
setValue(BluetraceConfig.ProtocolVersion, forKeyPath: "v")
} else {
setValue(encounterStruct.v, forKeyPath: "v")
}
setValue(remoteBlob, forKey: "remoteBlob")
setValue(localBlob, forKey: "localBlob")
}
// MARK: - Encodable
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Int(timestamp!.timeIntervalSince1970), forKey: .timestamp)
try container.encode(msg, forKey: .msg)
if let modelC = modelC, let modelP = modelP {
try container.encode(modelC, forKey: .modelC)
try container.encode(modelP, forKey: .modelP)
try container.encode(rssi?.doubleValue, forKey: .rssi)
try container.encode(txPower?.doubleValue, forKey: .txPower)
try container.encode(localBlob, forKey: .localBlob)
try container.encode(remoteBlob, forKey: .remoteBlob)
try container.encode(org, forKey: .org)
try container.encode(v?.intValue, forKey: .v)
}
}
}

View file

@ -12,8 +12,7 @@ extension EncounterRecord {
func saveToCoreData() {
DispatchQueue.main.async {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
guard let persistentContainer = EncounterDB.shared.persistentContainer else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext

View file

@ -10,10 +10,7 @@ import CoreData
extension Encounter {
@nonobjc public class func deleteAll() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
appDelegate.persistentContainer.performBackgroundTask { (backgroundContext) in
EncounterDB.shared.persistentContainer?.performBackgroundTask { (backgroundContext) in
let oldFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Encounter")
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: oldFetchRequest)
do {

View file

@ -0,0 +1,110 @@
//
// EncounterDB+migration.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import CoreData
protocol EncounterDBMigrationProgress {
func migrationBegun()
func migrationComplete()
func migrationFailed(error: Error)
}
enum MigrationError: Error {
case unableToRetrieveSourceModel
}
extension EncounterDB {
func store(_ storeURL:URL, isCompatibleWithModel model:NSManagedObjectModel) -> Bool {
do {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil)
if model.isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata) {
return true
}
} catch {
DLog("ERROR getting metadata from \(storeURL) \(error)")
}
DLog("The store is NOT compatible with the current version of the model")
return false
}
func migrateStoreIfNecessary (storeURL:URL, destinationModel:NSManagedObjectModel) {
guard FileManager.default.fileExists(atPath: storeURL.path) else {
return
}
guard store(storeURL, isCompatibleWithModel: destinationModel) == false else {
return
}
registerBackgroundTask()
do {
self.migrationDelegate?.migrationBegun()
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil)
if let sourceModel = NSManagedObjectModel.mergedModel(from: [Bundle.main], forStoreMetadata: metadata) {
//do the migration, on the background
DispatchQueue.global(qos: .background).async {
do {
DLog("STARTING MIGRATION")
try self.migrateStore(store: storeURL, sourceModel: sourceModel, destinationModel: destinationModel)
DispatchQueue.main.async {
self.endBackgroundTask()
self.migrationDelegate?.migrationComplete()
}
} catch {
DispatchQueue.main.async {
self.endBackgroundTask()
self.migrationDelegate?.migrationFailed(error: error)
}
DLog("Failed to migrate")
}
}
} else {
self.endBackgroundTask()
self.migrationDelegate?.migrationFailed(error: MigrationError.unableToRetrieveSourceModel)
}
} catch {
endBackgroundTask()
self.migrationDelegate?.migrationFailed(error: error)
print("FAILED to get metadata \(error)")
}
}
func migrateStore(store:URL, sourceModel:NSManagedObjectModel, destinationModel:NSManagedObjectModel) throws {
let tempdir = store.deletingLastPathComponent()
let tempStore = tempdir.appendingPathComponent("temp.sqlite", isDirectory: false)
let mappingModel = NSMappingModel(from: nil, forSourceModel: sourceModel, destinationModel: destinationModel)
let migrationManager = NSMigrationManager(sourceModel: sourceModel, destinationModel: destinationModel)
let options = [NSSQLitePragmasOption: ["journal_mode":"DELETE"]]
do {
try migrationManager.migrateStore(from: store,
sourceType: NSSQLiteStoreType,
options: options,
with: mappingModel,
toDestinationURL: tempStore,
destinationType: NSSQLiteStoreType,
destinationOptions: nil)
let psc = NSPersistentStoreCoordinator(managedObjectModel: sourceModel)
try psc.replacePersistentStore(at: store,
destinationOptions: nil,
withPersistentStoreFrom: tempStore,
sourceOptions: nil,
ofType: NSSQLiteStoreType)
try psc.destroyPersistentStore(at: tempStore, ofType: NSSQLiteStoreType, options: [NSSQLitePragmasOption: ["secure_delete": true]])
try FileManager.default.removeItem(at: tempStore)
DLog("SUCCESSFULLY MIGRATED \(store) to the Current Model")
} catch {
DLog("FAILED MIGRATION: \(error)")
if FileManager.default.fileExists(atPath: tempStore.path) {
try FileManager.default.removeItem(at: tempStore)
}
throw error
}
}
}

View file

@ -0,0 +1,75 @@
//
// EncounterDB.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import UIKit
import CoreData
class EncounterDB {
static let shared = EncounterDB()
private let modelName = "tracer"
private var localStoreUrl: URL?
private var _persistentContainer: CovidPersistentContainer?
var migrationDelegate: EncounterDBMigrationProgress?
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
private lazy var managedObjectModel: NSManagedObjectModel = {
guard let modelURL = Bundle.main.url(forResource: self.modelName, withExtension: "momd") else {
fatalError("Unable to Find Data Model")
}
guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Unable to Load Data Model")
}
return managedObjectModel
}()
public var persistentContainer: CovidPersistentContainer? {
get {
if let container = _persistentContainer {
return container
}
//check if we need to migrate store first
if let localStoreUrl = self.localStoreUrl {
if FileManager.default.fileExists(atPath: localStoreUrl.path) &&
store(localStoreUrl, isCompatibleWithModel: self.managedObjectModel) == false {
return nil // Don't return a store if it's not compatible with the model
}
}
let container = CovidPersistentContainer(name: self.modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
_persistentContainer = container
return _persistentContainer
}
}
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != .invalid)
}
func endBackgroundTask() {
if(backgroundTask != .invalid){
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
}
func setup(migrationDelegate: EncounterDBMigrationProgress?) {
self.migrationDelegate = migrationDelegate
self.localStoreUrl = CovidPersistentContainer.defaultDirectoryURL().appendingPathComponent(self.modelName, isDirectory: true).appendingPathExtension("sqlite")
migrateStoreIfNecessary(storeURL: self.localStoreUrl!, destinationModel: self.managedObjectModel)
}
}

View file

@ -12,7 +12,19 @@ class EncounterMessageManager {
}
var advertisedPayload: Data? {
return UserDefaults.standard.data(forKey: userDefaultsAdvtKey)
do {
let broadcastPayload = EncounterBlob(modelC: nil,
rssi: nil,
txPower: nil,
modelP: DeviceIdentifier.getModel(),
msg: tempId)
let jsonMsg = try JSONEncoder().encode(broadcastPayload)
let encryptedJsonMsg = try Crypto.encrypt(dataToEncrypt: jsonMsg)
let peripheralCharStruct = PeripheralCharacteristicsData(modelP: BluetraceConfig.DummyModel, msg: encryptedJsonMsg, org: BluetraceConfig.OrgID, v: BluetraceConfig.ProtocolVersion)
return try JSONEncoder().encode(peripheralCharStruct)
} catch {
return nil
}
}
// This variable stores the expiry date of the broadcast message. At the same time, we will use this expiry date as the expiry date for the encryted advertisement payload
@ -29,7 +41,6 @@ class EncounterMessageManager {
DLog("No response, Error: \(String(describing: error))")
return
}
_ = self.setAdvertisementPayloadIntoUserDefaults(response)
UserDefaults.standard.set(response.tempId, forKey: self.userDefaultsTempIdKey)
}
}
@ -72,8 +83,10 @@ class EncounterMessageManager {
onComplete(nil)
return
}
UserDefaults.standard.set(response.tempId, forKey: self.userDefaultsTempIdKey)
UserDefaults.standard.set(response.expiry, forKey: self.userDefaultsAdvtExpiryKey)
if let newPayload = self.setAdvertisementPayloadIntoUserDefaults(response) {
if let newPayload = self.advertisedPayload {
onComplete(newPayload)
}
onComplete(nil)
@ -122,18 +135,4 @@ class EncounterMessageManager {
onComplete?(nil, (tempId, date))
}
}
private func setAdvertisementPayloadIntoUserDefaults(_ response: (tempId: String, expiry: Date)) -> Data? {
let peripheralCharStruct = PeripheralCharacteristicsData(modelP: DeviceIdentifier.getModel(), msg: response.tempId, org: BluetraceConfig.OrgID, v: BluetraceConfig.ProtocolVersion)
do {
let encodedPeriCharStruct = try JSONEncoder().encode(peripheralCharStruct)
UserDefaults.standard.set(encodedPeriCharStruct, forKey: self.userDefaultsAdvtKey)
UserDefaults.standard.set(response.expiry, forKey: self.userDefaultsAdvtExpiryKey)
return encodedPeriCharStruct
} catch {
DLog("Error: \(error)")
}
return nil
}
}

View file

@ -7,6 +7,14 @@
import Foundation
struct EncounterBlob: Encodable {
var modelC: String?
var rssi: Double?
var txPower: Double?
var modelP: String?
var msg: String?
}
struct EncounterRecord: Encodable {
var timestamp: Date?
var msg: String?

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,102 @@
//
// EncounterV2Mapping.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import CoreData
struct MigrationRemoteBlob: Encodable {
enum CodingKeys: String, CodingKey {
case msg
case modelC
case modelP
case rssi
case txPower
}
var msg: String?
var modelC: String?
var modelP: String?
var rssi: NSNumber?
var txPower: NSNumber?
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let msg = msg {
try container.encode(msg, forKey: .msg)
}
if let modelC = modelC, modelC != BluetraceConfig.DummyModel{
try container.encode(modelC, forKey: .modelC)
}
if let modelP = modelP, modelP != BluetraceConfig.DummyModel {
try container.encode(modelP, forKey: .modelP)
}
if let rssi = rssi, rssi.intValue != BluetraceConfig.DummyRSSI {
try container.encode(rssi.doubleValue, forKey: .rssi)
}
if let txPower = txPower, txPower.intValue != BluetraceConfig.DummyTxPower {
try container.encode(txPower.doubleValue, forKey: .txPower)
}
}
}
class EncounterV2Mapping: NSEntityMigrationPolicy {
var localEmptyBlob: String = ""
override func begin(_ mapping: NSEntityMapping, with manager: NSMigrationManager) throws {
try super.begin(mapping, with: manager)
let emptyLocal = try JSONEncoder().encode(MigrationRemoteBlob())
localEmptyBlob = try Crypto.encrypt(dataToEncrypt: emptyLocal)
}
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
if (sInstance.entity.name == "Encounter") {
guard let version = sInstance.primitiveValue(forKey: "v") as? Int64 else {
return
}
let encounterV2 = NSEntityDescription.insertNewObject(forEntityName: "Encounter", into: manager.destinationContext)
let msg = sInstance.primitiveValue(forKey: "msg") as? String
let modelC = sInstance.primitiveValue(forKey: "modelC") as? String
let modelP = sInstance.primitiveValue(forKey: "modelP") as? String
let rssi = sInstance.primitiveValue(forKey: "rssi") as? NSNumber
let txPower = sInstance.primitiveValue(forKey: "txPower") as? NSNumber
let org = sInstance.primitiveValue(forKey: "org") as? String
let timestamp = sInstance.primitiveValue(forKey: "timestamp") as? Date
encounterV2.setPrimitiveValue(org, forKey: "org")
encounterV2.setPrimitiveValue(BluetraceConfig.ProtocolVersion, forKey: "v")
encounterV2.setPrimitiveValue(timestamp, forKey: "timestamp")
do {
try autoreleasepool {
if version == 1 {
//convert a regular old v1 entry
encounterV2.setPrimitiveValue(localEmptyBlob, forKey: "localBlob")
let remoteBlob = MigrationRemoteBlob(msg: msg, modelC: modelC, modelP: modelP, rssi: rssi, txPower: txPower)
let blobJson = try JSONEncoder().encode(remoteBlob)
let remoteBlobEncrypted = try Crypto.encrypt(dataToEncrypt: blobJson)
encounterV2.setPrimitiveValue(remoteBlobEncrypted, forKey: "remoteBlob")
manager.associate(sourceInstance: sInstance, withDestinationInstance: encounterV2, for: mapping)
} else if version == 2 {
//convert an entry that was recieved from an already updated app (the msg will already be encrypted)
let localMigrationBlob = MigrationRemoteBlob(msg: nil, modelC: modelC, modelP: modelP, rssi: rssi, txPower: txPower)
let jsonLocal = try JSONEncoder().encode(localMigrationBlob)
let localBlob = try Crypto.encrypt(dataToEncrypt: jsonLocal)
encounterV2.setPrimitiveValue(localBlob, forKey: "localBlob")
encounterV2.setPrimitiveValue(msg, forKey: "remoteBlob")
manager.associate(sourceInstance: sInstance, withDestinationInstance: encounterV2, for: mapping)
}
}
} catch {
throw(error)
}
// we're dropping all the debug messages save to the db during the migration as they're unnessecary
}
}
}

View file

@ -0,0 +1,94 @@
# Feedback
Provides a way for users or testers to send feedback into your JIRA instance from within the app along with a screenshot.
## Configuration
#### JIRA
JIRA Mobile Connect needs to be enabled on a per project basis, otherwise it will not work with your app. Remember, if you are hosting your own JIRA instance, you will need to install the JIRA Mobile Connect plugin on your server before you can enable it.
To enable JIRA Mobile Connect for a project:
1. Navigate to the desired project > **Project Settings**.
2. Find the **Settings** section on the page and click **Enable** for the **JIRA Mobile Connect** setting.
This will enable the JIRA Mobile Connect plugin for the project, as well as create a user ('jiraconnectuser') in JIRA that is used to create all feedback and crash reports.
3. To enable the user to create tickets, you must grant it permission to create issues in the project. To do this, grant the 'Create Issues' permission to the 'jiraconnectuser' user. You can do this by adding the user to a group or project role that has the 'Create Issues' permission or grant the permission to the user directly (see [Managing project permissions](https://confluence.atlassian.com/display/AdminJIRACloud/Managing+project+permissions) for help).
#### iOS
Before you can use JIRA Mobile Connect with iOS, you need to identify the JIRA instance that feedback will be sent to. This is done via a JSON file named **JMCTarget**. You'll need to create this **JMCTarget.json** file in your Xcode project and include it in the iOS app's target so it gets bundled with the app.
1. Right-click your project in Xcode and click **New File...**
2. Select **Other** in the **iOS** section and click **Next**.
3. In the **Save As** field, type 'JMCTarget.JSON' then select the desired **Targets**.
4. Click **Create**.
5. Add the following code to your new** JMCTarget.JSON** file. Make sure to replace the values with your own:
```
{
"host": "example-dev.atlassian.net",
"projectKey": "EXAMPLEKEY",
"apiKey": "myApiKey"
}
```
_**Note:** Do not prefix the host with `https://`._
## Using JIRA Mobile Connect in your app
### Trigger from the shake-motion event
The shake-motion event is one of the [motion events](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/motion_event_basics/motion_event_basics.html#//apple_ref/doc/uid/TP40009541-CH6-SW14) that are detected by the device when it is moved. The following instructions will show you how to trigger a feedback flow (i.e. create a JIRA issue) in your app when the user shakes the device.
_Note that as of iOS 10, `motionEnded` is no longer called on the `AppDelegate`, so you must override `motionEnded` on a `UIResponder` (subclass `UIWindow` or use a top-level view controller)._
Wherever you choose to override `motionEnded`, add the following:
```swift
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
do {
let settings = try FeedbackSettings()
<WINDOW>.presentFeedbackIfShakeMotion(motion, promptUser: false, settings: settings)
} catch {
preconditionFailure("Error retrieving feedback settings: \(error.localizedDescription)")
}
}
```
### Programmatic trigger
You can also trigger a feedback flow in your app from other events, for example, a button that is tapped in the app. This section shows you how to implement this.
Call `presentFeedback()` on any view controller to present feedback:
```swift
do {
let feedbackSettings = try FeedbackSettings()
currentViewController.presentFeedback(true, settings: feedbackSettings)
} catch {
preconditionFailure("Error retrieving feedback settings: \(error.localizedDescription)")
}
```
### Additional Configuration
##### Action sheet prompt
To present an action sheet prompt before taking the user to the feedback flow, set **`promptUser`** to true.
```swift
// Shake Motion
<WINDOW>.presentFeedbackIfShakeMotion(motion, promptUser: true, settings: settings)
// Programmatic
currentViewController.presentFeedback(true, settings: feedbackSettings)
```
##### Reporter details
To pass a string to identify who is sending the feedback, set **`reporterUsernameOrEmail`** to the reporter's Id string.
_**Important**: The current version does not let the user know their ID is being sent. You may want to only use this feature for non-Appstore builds. To do this, just set reporterUsernameOrEmail to `nil` for release builds._
```swift
let settings = try FeedbackSettings(reporterUsernameOrEmail: "someone@example.com")
```

View file

@ -14,7 +14,6 @@ class HomeViewController: UIViewController {
@IBOutlet weak var homeHeaderInfoText: UILabel!
@IBOutlet weak var homeHeaderPermissionsOffImage: UIImageView!
@IBOutlet weak var shareView: UIView!
@IBOutlet weak var thanksForTheHelp: UIView!
@IBOutlet weak var appPermissionsLabel: UIView!
@IBOutlet weak var animatedBluetoothHeader: UIView!
@IBOutlet weak var versionNumberLabel: UILabel!
@ -25,6 +24,7 @@ class HomeViewController: UIViewController {
@IBOutlet weak var pushNotificationStatusTitle: UILabel!
@IBOutlet weak var pushNotificationStatusIcon: UIImageView!
@IBOutlet weak var pushNotificationStatusLabel: UILabel!
@IBOutlet weak var uploadDateLabel: UILabel!
var lottieBluetoothView: AnimationView!
@ -35,13 +35,31 @@ class HomeViewController: UIViewController {
var didUploadData: Bool {
let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate")
let lastUpload = Date(timeIntervalSince1970: uploadTimestamp)
return Date().timeIntervalSince(lastUpload) < 86400 * 21
return Date().timeIntervalSince(lastUpload) < 86400 * 14
}
var shouldShowEndOfIsolationScreen: Bool {
let uploadTimestamp = UserDefaults.standard.double(forKey: "firstUploadDataDate")
var dataUploadedAttributedString: NSAttributedString? {
let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate")
if(uploadTimestamp > 0){
let lastUpload = Date(timeIntervalSince1970: uploadTimestamp)
return Date().timeIntervalSince(lastUpload) >= 86400 * 21
let dateFormatterPrint = DateFormatter()
dateFormatterPrint.dateFormat = "dd MMM yyyy"
let formattedDate = dateFormatterPrint.string(from: lastUpload)
let newAttributedString = NSMutableAttributedString(string: "Your information was uploaded on \(formattedDate).")
guard let dateRange = newAttributedString.string.range(of: formattedDate) else { return nil }
let nsRange = NSRange(dateRange, in: newAttributedString.string)
newAttributedString.addAttribute(.font,
value: UIFont.boldSystemFont(ofSize: 18),
range: nsRange)
return newAttributedString
}
return nil
}
var shouldShowUploadDate: Bool {
let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate")
if(uploadTimestamp > 0){
let lastUpload = Date(timeIntervalSince1970: uploadTimestamp)
return Date().timeIntervalSince(lastUpload) <= 86400 * 14
}
return false
}
@ -67,6 +85,7 @@ class HomeViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(enableUserInteraction(_:)), name: .enableUserInteraction, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
if let versionNumber = Bundle.main.versionShort, let buildNumber = Bundle.main.version {
self.versionNumberLabel.text = "Version number: \(versionNumber) Build: \(buildNumber)"
} else {
@ -89,10 +108,6 @@ class HomeViewController: UIViewController {
super.viewDidAppear(animated)
self.lottieBluetoothView?.play()
self.becomeFirstResponder()
if(shouldShowEndOfIsolationScreen){
self.performSegue(withIdentifier: "IsolationSuccessSegue", sender: self)
}
}
override func viewWillDisappear(_ animated: Bool) {
@ -133,11 +148,21 @@ class HomeViewController: UIViewController {
self?.toggleBluetoothPermissionStatusView()
self?.toggleHeaderView()
self?.toggleUploadView()
self?.toggleUploadDateView()
}
})
}
}
fileprivate func toggleUploadDateView() {
if shouldShowUploadDate, let lastUploadText = self.dataUploadedAttributedString {
uploadDateLabel.attributedText = lastUploadText
uploadDateLabel.isHidden = false
} else {
uploadDateLabel.isHidden = true
}
}
fileprivate func readPermissions(notificationSettings: UNNotificationSettings) {
self.bluetoothStatusOn = BluetraceManager.shared.isBluetoothOn()
self.bluetoothPermissionOn = BluetraceManager.shared.isBluetoothAuthorized()
@ -169,15 +194,9 @@ class HomeViewController: UIViewController {
fileprivate func toggleHeaderView() {
self.allPermissionOn ? self.lottieBluetoothView?.play() : self.lottieBluetoothView?.stop()
toggleViewVisibility(view: appPermissionsLabel, isVisible: !self.allPermissionOn)
toggleViewVisibility(view: thanksForTheHelp, isVisible: self.allPermissionOn && self.didUploadData)
toggleViewVisibility(view: homeHeaderPermissionsOffImage, isVisible: !self.allPermissionOn)
toggleViewVisibility(view: lottieBluetoothView, isVisible: self.allPermissionOn)
if (self.allPermissionOn && self.didUploadData) {
self.homeHeaderInfoText.textColor = UIColor.white
} else {
self.homeHeaderInfoText.textColor = UIColor(0x131313)
}
self.helpButton.setImage(UIImage(named: "ic-help-selected"), for: .normal)
self.helpButton.setTitleColor(UIColor.black, for: .normal)
@ -187,12 +206,6 @@ class HomeViewController: UIViewController {
if (!self.allPermissionOn) {
self.homeHeaderInfoText.text = "COVIDSafe is not active.\nCheck your permissions."
self.homeHeaderView.backgroundColor = UIColor.covidHomePermissionErrorColor
} else if (self.didUploadData) {
self.helpButton.setImage(UIImage(named: "ic-help"), for: .normal)
self.helpButton.setTitleColor(UIColor.white, for: .normal)
self.homeHeaderInfoText.font = UIFont.systemFont(ofSize: 18, weight: .bold)
self.homeHeaderView.backgroundColor = UIColor.covidSafeButtonDarkerColor
updateAnimationViewWithAnimationName(name: "Spinner_home_upload_complete")
} else {
self.homeHeaderView.backgroundColor = UIColor.covidHomeActiveColor
updateAnimationViewWithAnimationName(name: "Spinner_home")
@ -279,6 +292,15 @@ class HomeViewController: UIViewController {
present(safariVC, animated: true, completion: nil)
}
@IBAction func bluetoothPairingTapped(_ sender: Any) {
guard let url = URL(string: "https://www.covidsafe.gov.au/help-topics.html#bluetooth-pairing-request") else {
return
}
let safariVC = SFSafariViewController(url: url)
present(safariVC, animated: true, completion: nil)
}
@IBAction func onPositiveButtonTapped(_ sender: UITapGestureRecognizer) {
guard let helpVC = UIStoryboard(name: "UploadData", bundle: nil).instantiateInitialViewController() else {
return

View file

@ -54,15 +54,16 @@ final class InfoViewController: UIViewController {
}
func fetchDevicesEncounteredCount() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
guard let persistentContainer =
EncounterDB.shared.persistentContainer else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let managedContext = persistentContainer.viewContext
let fetchRequest = Encounter.fetchRequestForRecords()
do {
let devicesEncountered = try managedContext.fetch(fetchRequest)
let uniqueIDs = Set(devicesEncountered.map { $0.msg })
let uniqueIDs = Set(devicesEncountered.map { $0.timestamp })
self.devicesEncounteredLabel.text = String(uniqueIDs.count)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
@ -79,12 +80,29 @@ final class InfoViewController: UIViewController {
BluetraceManager.shared.toggleScanning(mySwitch.isOn)
}
@IBAction func dumpDBpressed(_ sender: UIButton) {
var activityItems: [Any] = []
let localStoreUrl = CovidPersistentContainer.defaultDirectoryURL().appendingPathComponent("tracer", isDirectory: false).appendingPathExtension("sqlite")
activityItems.append(localStoreUrl)
let localStoreUrlshm = CovidPersistentContainer.defaultDirectoryURL().appendingPathComponent("tracer", isDirectory: false).appendingPathExtension("sqlite-shm")
if FileManager.default.fileExists(atPath: localStoreUrlshm.path) {
activityItems.append(localStoreUrlshm)
}
let localStoreUrlwal = CovidPersistentContainer.defaultDirectoryURL().appendingPathComponent("tracer", isDirectory: false).appendingPathExtension("sqlite-wal")
if FileManager.default.fileExists(atPath: localStoreUrlwal.path) {
activityItems.append(localStoreUrlwal)
}
let activity = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
present(activity, animated: true, completion: nil)
}
@objc
func clearLogsButtonClicked() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
guard let persistentContainer =
EncounterDB.shared.persistentContainer else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let managedContext = persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Encounter>(entityName: "Encounter")
fetchRequest.includesPropertyValues = false
do {

View file

@ -8,14 +8,17 @@
import UIKit
import KeychainSwift
class InitialScreenViewController: UIViewController {
class InitialScreenViewController: UIViewController, EncounterDBMigrationProgress {
let displayTimeSeconds: Int = 4
let displayTimeSeconds = 4.0
let giveupTimeSeconds = 8.0
var migrationStart: Date?
var isKeychainAvailable = false
var isDisplayTimeElapsed = false
let keychain = KeychainSwift()
var giveupTimer: Timer?
var initialDelayTimer: Timer?
var migrationViewController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
@ -27,23 +30,26 @@ class InitialScreenViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(setKeychainAvailable(_:)), name: UIApplication.protectedDataDidBecomeAvailableNotification, object: nil)
break
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.window?.tintColor = .covidSafeColor
let showAppDelay = DispatchTime.now() + .seconds(displayTimeSeconds)
DispatchQueue.main.asyncAfter(deadline: showAppDelay, execute: {
continueAfterDelay(delay: displayTimeSeconds)
// add give up action in case the keychain notification in not received after 8 seconds
giveupTimer = Timer.scheduledTimer(withTimeInterval: giveupTimeSeconds, repeats: false) { timer in
self.performSegue(withIdentifier: "initialPersonalDetailsSegue", sender: self)
}
EncounterDB.shared.setup(migrationDelegate: self)
}
func continueAfterDelay(delay: TimeInterval) {
initialDelayTimer = Timer.scheduledTimer(withTimeInterval: delay,
repeats: false,
block: { (_) in
self.isDisplayTimeElapsed = true
if(self.proceedWithChecks()) {
self.performCheck()
}
})
// add give up action in case the keychain notification in not received after 8 seconds
giveupTimer = Timer.scheduledTimer(withTimeInterval: giveupTimeSeconds, repeats: false) { timer in
self.performSegue(withIdentifier: "initialPersonalDetailsSegue", sender: self)
}
}
@objc
@ -61,9 +67,12 @@ class InitialScreenViewController: UIViewController {
private func performCheck() {
giveupTimer?.invalidate()
initialDelayTimer?.invalidate()
if let migrationVc = migrationViewController {
migrationVc.dismiss(animated: true, completion: nil)
}
let isLoggedIn: Bool = (keychain.get("JWT_TOKEN") != nil)
if !UserDefaults.standard.bool(forKey: "completedIWantToHelp") {
// old app signed out here
keychain.delete("JWT_TOKEN")
self.performSegue(withIdentifier: "initialScreenToIWantToHelpSegue", sender: self)
} else if !UserDefaults.standard.bool(forKey: "hasConsented") {
@ -72,10 +81,40 @@ class InitialScreenViewController: UIViewController {
self.performSegue(withIdentifier: "initialPersonalDetailsSegue", sender: self)
} else if !UserDefaults.standard.bool(forKey: "allowedPermissions") {
self.performSegue(withIdentifier: "initialScreenToAllowPermissionsSegue", sender: self)
} else if !UserDefaults.standard.bool(forKey: "turnedOnBluetooth") {
self.performSegue(withIdentifier: "initialScreenToTurnOnBtSegue", sender: self)
} else {
self.performSegue(withIdentifier: "initialScreenToHomeSegue", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "presentMigrationSegue" {
migrationViewController = segue.destination
}
}
func migrationBegun() {
DLog("MIGRATION BEGUN")
giveupTimer?.invalidate()
initialDelayTimer?.invalidate()
migrationStart = Date()
performSegue(withIdentifier: "presentMigrationSegue", sender: nil)
}
func migrationComplete() {
DLog("MIGRATION COMPLETE")
if let migrationStart = migrationStart {
let migrationDuration = abs(migrationStart.timeIntervalSinceNow)
if migrationDuration > displayTimeSeconds {
performCheck()
} else {
// migration was quick, still need to delay minimum 4 seconds
DLog("Migration too quick, waiting \(displayTimeSeconds - migrationDuration) seconds")
continueAfterDelay(delay: displayTimeSeconds - migrationDuration)
}
}
}
func migrationFailed(error: Error) {
fatalError("Migration Failed \(error)")
}
}

View file

@ -23,10 +23,11 @@ final class LogViewController: UIViewController {
}
func fetchEncounters() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
guard let persistentContainer =
EncounterDB.shared.persistentContainer else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let managedContext = persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Encounter>(entityName: "Encounter")
let sortByDate = NSSortDescriptor(key: "timestamp", ascending: false)
fetchRequest.sortDescriptors = [sortByDate]
@ -62,26 +63,23 @@ extension LogViewController: UITableViewDataSource {
return
}
let datetime = encounter.timestamp
let msg = encounter.msg
// let msg = encounter.msg
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .medium
let timeString = "\(datetime == nil ? "<NONE>" : dateFormatter.string(from: datetime!))"
if let rawMessage = encounter.msg, let event = Encounter.Event(rawValue: rawMessage) {
cell.textLabel?.text = "\(timeString) - \(event.rawValue)"
} else {
// if let rawMessage = encounter.msg, let event = Encounter.Event(rawValue: rawMessage) {
// cell.textLabel?.text = "\(timeString) - \(event.rawValue)"
// } else {
cell.textLabel?.text = """
\(timeString)
msg: \(msg ?? "<MISSING>")
modelC: \(encounter.modelC != nil ? "\(encounter.modelC!)" : "nil")
modelP: \(encounter.modelP != nil ? "\(encounter.modelP!)" : "nil")
RSSI: \(encounter.rssi != nil ? "\(encounter.rssi!)" : "nil")
txPower: \(encounter.txPower != nil ? "\(encounter.txPower!)" : "nil")
org: \(encounter.org != nil ? "\(encounter.org!)" : "nil")
v: \(encounter.v != nil ? "\(encounter.v!)" : "nil")
remoteBlob: \(encounter.remoteBlob ?? "nil")
localBlob: \(encounter.localBlob ?? "nil")
"""
cell.textLabel?.numberOfLines = 0
}
// }
}
}

View file

@ -0,0 +1,22 @@
//
// MigrationViewController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import UIKit
import Lottie
class MigrationViewController: UIViewController {
@IBOutlet weak var animationContainer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let migrationAnimation = AnimationView(name: "spinner_migrating_db")
migrationAnimation.loopMode = .loop
migrationAnimation.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: self.animationContainer.frame.size)
self.animationContainer.addSubview(migrationAnimation)
migrationAnimation.play()
}
}

View file

@ -237,8 +237,6 @@ class OTPViewController: UIViewController, RegistrationHandler {
}
if !UserDefaults.standard.bool(forKey: "allowedPermissions") {
viewController.performSegue(withIdentifier: "showAllowPermissionsFromOTPSegue", sender: self)
} else if !UserDefaults.standard.bool(forKey: "turnedOnBluetooth") {
self.performSegue(withIdentifier: "OTPToTurnOnBtSegue", sender: self)
} else {
self.performSegue(withIdentifier: "OTPToHomeSegue", sender: self)
}

View file

@ -20,8 +20,10 @@ public class PeripheralController: NSObject {
}
var didUpdateState: ((String) -> Void)?
private let encounteredCentralExpiryTime:TimeInterval = 1800.0 // 30 minutes
private let restoreIdentifierKey = "com.joelkek.tracer.peripheral"
private let peripheralName: String
private var encounteredCentrals = [UUID: (EncounterRecord)]()
private var characteristicData: PeripheralCharacteristicsData
@ -99,7 +101,13 @@ extension PeripheralController: CBPeripheralManagerDelegate {
if let characteristicValue = request.value {
let dataFromCentral = try JSONDecoder().decode(CentralWriteData.self, from: characteristicValue)
let encounter = EncounterRecord(from: dataFromCentral)
encounter.saveToCoreData()
if (shouldRecordEncounterWithCentral(request.central)) {
try encounter.saveRemoteCentralToCoreData()
encounteredCentrals.updateValue(encounter, forKey: request.central.identifier)
removeOldEncounters()
} else {
DLog("Encounterd central too recently, not recording")
}
}
}
peripheral.respond(to: requests[0], withResult: .success)
@ -107,6 +115,28 @@ extension PeripheralController: CBPeripheralManagerDelegate {
DLog("Error: \(error)")
peripheral.respond(to: requests[0], withResult: .unlikelyError)
}
}
private func removeOldEncounters() {
encounteredCentrals = encounteredCentrals.filter { (uuid, encounter) -> Bool in
guard let encDate = encounter.timestamp else {
return true
}
return abs(encDate.timeIntervalSinceNow) < encounteredCentralExpiryTime
}
}
private func shouldRecordEncounterWithCentral(_ central: CBCentral) -> Bool {
guard let previousEncounter = encounteredCentrals[central.identifier] else {
return true
}
guard let lastEncDate = previousEncounter.timestamp else {
return true
}
if abs(lastEncDate.timeIntervalSinceNow) > BluetraceConfig.CentralScanInterval {
return true
}
return false
}
}

View file

@ -9,8 +9,8 @@ struct PushNotificationConstants {
// Bluetooth Status
static let btStatusPushNotifContents = [
[
"contentTitle": "Turned Bluetooth off by mistake?",
"contentBody": "Help stop the spread of COVID-19 by keeping your phones Bluetooth on until the outbreak is over."
"contentTitle": "COVIDSafe is currently inactive",
"contentBody": "Make sure it's active before you leave home and when in public places by enabling Bluetooth®"
]
]

View file

@ -0,0 +1,67 @@
//
// SecKey+CovidSafe.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import Foundation
extension SecKey {
enum SecKeyError: Error {
case UnableToInspectAttributesError
case UnsupportedKeyTypeError
case InvalidUncompressedRepresentationError
}
/**
Copy the compressed elliptic-curve public key representation as defined in
section 4.3.6 of ANSI X9.62.
- Throws:
- `SecKeyError.UnableToInspectAttributesError`: if `SecKeyCopyAttributes()` fails on the key
- `SecKeyError.UnsupportedKeyTypeError`: if the key isn't of type `kSecAttrKeyTypeECSECPrimeRandom`
- `SecKeyError.InvalidUncompressedRepresentationError`: if parsing the uncompressed representation
of the key failed (from `SecKeyCopyExternalRepresentation()`)
- Returns: Data with 1 + key size bytes, representing the public key
*/
func CopyCompressedECPublicKey() throws -> Data {
// Validate the key is of EC type
guard let attributes = SecKeyCopyAttributes(self) as? [CFString: AnyObject] else {
throw SecKeyError.UnableToInspectAttributesError
}
guard attributes[kSecAttrKeyType] as! CFNumber == kSecAttrKeyTypeECSECPrimeRandom else {
throw SecKeyError.UnsupportedKeyTypeError
}
// Get key size in bytes
let keySize = Int(truncating: attributes[kSecAttrKeySizeInBits] as! CFNumber) / 8
// Get the uncompressed key representation
var err: Unmanaged<CFError>?
guard let uncompressed = SecKeyCopyExternalRepresentation(self, &err) as Data? else {
throw err!.takeRetainedValue() as Error
}
// Uncompressed begins with 0x04 per section 4.3.6 of X9.62
guard uncompressed[0] == 4 else {
throw SecKeyError.InvalidUncompressedRepresentationError
}
// Uncompressed public key will be 1 + keysize * 2, private has K appended to public
guard uncompressed.count >= 1 + keySize * 2 else {
throw SecKeyError.InvalidUncompressedRepresentationError
}
// To compress, take the X coordinate
let x = uncompressed[1...keySize]
// And determine whether Y coordinate is odd or even
let y_lsb = uncompressed[keySize*2] & 1
// Compressed format is PC || X_1
var compressed = Data(capacity: 1 + keySize)
// PC: 0x02 if even, 0x03 if odd
compressed.append(2 | y_lsb)
compressed.append(x)
return compressed
}
}

View file

@ -13,11 +13,12 @@ final class UploadHelper {
public static func uploadEncounterData(pin: String?, _ result: @escaping (UploadResult) -> Void) {
let keychain = KeychainSwift()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
guard let managedContext = EncounterDB.shared.persistentContainer?.viewContext else {
result(.Failed)
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let recordsFetchRequest: NSFetchRequest<Encounter> = Encounter.fetchRequestForRecords()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19E266" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Encounter" representedClassName="Encounter" syncable="YES">
<attribute name="localBlob" optional="YES" attributeType="String"/>
<attribute name="org" optional="YES" attributeType="String"/>
<attribute name="remoteBlob" optional="YES" attributeType="String"/>
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="v" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<fetchedProperty name="fetchedProperty" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="Encounter"/>
</fetchedProperty>
</entity>
<elements>
<element name="Encounter" positionX="-63" positionY="-18" width="128" height="146"/>
</elements>
</model>

View file

@ -19,6 +19,6 @@ SPEC CHECKSUMS:
KeychainSwift: a06190cf933ad46b1e0abc3d77d29c06331715c7
lottie-ios: 85ce835dd8c53e02509f20729fc7d6a4e6645a0a
PODFILE CHECKSUM: 8bc87fadafd4dd4f9d03ad3edbfbd84d778c1b19
PODFILE CHECKSUM: 267f7fcda5f8a86683b76b362dfc6c6703e3033f
COCOAPODS: 1.9.1