Skip to content

Commit 3073bf1

Browse files
authored
chore: Add attempt count changes (#137)
* chore: Add attempt count changes * Fix unit tests * add unit tests * Update region for example liveness view * Update amplify-swift dependency
1 parent cc166eb commit 3073bf1

13 files changed

+155
-72
lines changed

Diff for: HostApp/HostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"location" : "https://github.com/aws-amplify/amplify-swift",
77
"state" : {
88
"branch" : "feat/no-light-support",
9-
"revision" : "7c1fa2f7a766208f5af69ca8dce5fd02e6de4db6"
9+
"revision" : "22e02fa21399122aac1d8b4f6ab23c242c79dae6"
1010
}
1111
},
1212
{

Diff for: HostApp/HostApp/Model/LivenessResult.swift

+4
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
//
77

88
import Foundation
9+
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
910

1011
struct LivenessResult: Codable {
1112
let auditImageBytes: String?
1213
let confidenceScore: Double
1314
let isLive: Bool
15+
let challenge: Challenge?
1416
}
1517

1618
extension LivenessResult: CustomDebugStringConvertible {
@@ -20,6 +22,8 @@ extension LivenessResult: CustomDebugStringConvertible {
2022
- confidenceScore: \(confidenceScore)
2123
- isLive: \(isLive)
2224
- auditImageBytes: \(auditImageBytes == nil ? "nil" : "<placeholder>")
25+
- challengeType: \(String(describing: challenge?.type))
26+
- challengeVersion: \(String(describing: challenge?.version))
2327
"""
2428
}
2529
}

Diff for: HostApp/HostApp/Views/ExampleLivenessView.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ struct ExampleLivenessView: View {
2222
case .liveness:
2323
FaceLivenessDetectorView(
2424
sessionID: viewModel.sessionID,
25-
// TODO: Change before merging to main
26-
region: "us-west-2",
25+
region: "us-east-1",
2726
isPresented: Binding(
2827
get: { viewModel.presentationState == .liveness },
2928
set: { _ in }

Diff for: HostApp/HostApp/Views/LivenessResultContentView+Result.swift

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import SwiftUI
9+
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
910

1011
extension LivenessResultContentView {
1112
struct Result {
@@ -15,6 +16,7 @@ extension LivenessResultContentView {
1516
let valueBackgroundColor: Color
1617
let auditImage: Data?
1718
let isLive: Bool
19+
let challenge: Challenge?
1820

1921
init(livenessResult: LivenessResult) {
2022
guard livenessResult.confidenceScore > 0 else {
@@ -24,6 +26,7 @@ extension LivenessResultContentView {
2426
valueBackgroundColor = .clear
2527
auditImage = nil
2628
isLive = false
29+
challenge = nil
2730
return
2831
}
2932
isLive = livenessResult.isLive
@@ -41,6 +44,7 @@ extension LivenessResultContentView {
4144
auditImage = livenessResult.auditImageBytes.flatMap{
4245
Data(base64Encoded: $0)
4346
}
47+
challenge = livenessResult.challenge
4448
}
4549
}
4650

Diff for: HostApp/HostApp/Views/LivenessResultContentView.swift

+44-20
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
//
77

88
import SwiftUI
9+
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
910

1011
struct LivenessResultContentView: View {
11-
@State var result: Result = .init(livenessResult: .init(auditImageBytes: nil, confidenceScore: -1, isLive: false))
12+
@State var result: Result = .init(livenessResult: .init(auditImageBytes: nil, confidenceScore: -1, isLive: false, challenge: nil))
1213
let fetchResults: () async throws -> Result
1314

1415
var body: some View {
@@ -67,26 +68,48 @@ struct LivenessResultContentView: View {
6768
}
6869
}
6970

71+
func step(number: Int, text: String) -> some View {
72+
HStack(alignment: .top) {
73+
Text("\(number).")
74+
Text(text)
75+
}
76+
}
77+
78+
@ViewBuilder
7079
private func steps() -> some View {
71-
func step(number: Int, text: String) -> some View {
72-
HStack(alignment: .top) {
73-
Text("\(number).")
74-
Text(text)
80+
switch result.challenge?.type {
81+
case .faceMovementChallenge:
82+
VStack(
83+
alignment: .leading,
84+
spacing: 8
85+
) {
86+
Text("Tips to pass the video check:")
87+
.fontWeight(.semibold)
88+
89+
Text("Remove sunglasses, mask, hat, or anything blocking your face.")
90+
.accessibilityElement(children: .combine)
91+
}
92+
case .faceMovementAndLightChallenge:
93+
VStack(
94+
alignment: .leading,
95+
spacing: 8
96+
) {
97+
Text("Tips to pass the video check:")
98+
.fontWeight(.semibold)
99+
100+
step(number: 1, text: "Avoid very bright lighting conditions, such as direct sunlight.")
101+
.accessibilityElement(children: .combine)
102+
103+
step(number: 2, text: "Remove sunglasses, mask, hat, or anything blocking your face.")
104+
.accessibilityElement(children: .combine)
105+
}
106+
case .none:
107+
VStack(
108+
alignment: .leading,
109+
spacing: 8
110+
) {
111+
EmptyView()
75112
}
76-
}
77-
78-
return VStack(
79-
alignment: .leading,
80-
spacing: 8
81-
) {
82-
Text("Tips to pass the video check:")
83-
.fontWeight(.semibold)
84-
85-
step(number: 1, text: "Avoid very bright lighting conditions, such as direct sunlight.")
86-
.accessibilityElement(children: .combine)
87-
88-
step(number: 2, text: "Remove sunglasses, mask, hat, or anything blocking your face.")
89-
.accessibilityElement(children: .combine)
90113
}
91114
}
92115
}
@@ -99,7 +122,8 @@ extension LivenessResultContentView {
99122
livenessResult: .init(
100123
auditImageBytes: nil,
101124
confidenceScore: 99.8329,
102-
isLive: true
125+
isLive: true,
126+
challenge: nil
103127
)
104128
)
105129
}

Diff for: Package.resolved

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"location" : "https://github.com/aws-amplify/amplify-swift",
77
"state" : {
88
"branch" : "feat/no-light-support",
9-
"revision" : "7c1fa2f7a766208f5af69ca8dce5fd02e6de4db6"
9+
"revision" : "22e02fa21399122aac1d8b4f6ab23c242c79dae6"
1010
}
1111
},
1212
{

Diff for: Sources/FaceLiveness/Views/GetReadyPage/GetReadyPageView.swift

+13-22
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,19 @@ struct GetReadyPageView: View {
2727
VStack {
2828
ZStack {
2929
CameraPreviewView()
30-
switch self.challenge.type {
31-
case .faceMovementChallenge:
32-
VStack {
33-
Text(LocalizedStrings.preview_center_your_face_text)
34-
.font(.title)
35-
.multilineTextAlignment(.center)
36-
Spacer()
37-
}.padding()
38-
case . faceMovementAndLightChallenge:
39-
VStack {
40-
WarningBox(
41-
titleText: LocalizedStrings.get_ready_photosensitivity_title,
42-
bodyText: LocalizedStrings.get_ready_photosensitivity_description,
43-
popoverContent: { photosensitivityWarningPopoverContent }
44-
)
45-
.accessibilityElement(children: .combine)
46-
Text(LocalizedStrings.preview_center_your_face_text)
47-
.font(.title)
48-
.multilineTextAlignment(.center)
49-
Spacer()
50-
}.padding()
51-
}
30+
VStack {
31+
WarningBox(
32+
titleText: LocalizedStrings.get_ready_photosensitivity_title,
33+
bodyText: LocalizedStrings.get_ready_photosensitivity_description,
34+
popoverContent: { photosensitivityWarningPopoverContent }
35+
)
36+
.accessibilityElement(children: .combine)
37+
.opacity(challenge.type == .faceMovementAndLightChallenge ? 1.0 : 0.0)
38+
Text(LocalizedStrings.preview_center_your_face_text)
39+
.font(.title)
40+
.multilineTextAlignment(.center)
41+
Spacer()
42+
}.padding()
5243
}
5344
beginCheckButton
5445
}

Diff for: Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionView.swift

+5-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public struct FaceLivenessDetectorView: View {
2020
@State var displayingCameraPermissionsNeededAlert = false
2121

2222
let disableStartView: Bool
23-
let facelivenessDetectorViewId: String
2423
let onCompletion: (Result<Void, FaceLivenessDetectionError>) -> Void
2524

2625
let sessionTask: Task<FaceLivenessSession, Error>
@@ -32,9 +31,7 @@ public struct FaceLivenessDetectorView: View {
3231
disableStartView: Bool = false,
3332
isPresented: Binding<Bool>,
3433
onCompletion: @escaping (Result<Void, FaceLivenessDetectionError>) -> Void
35-
) {
36-
let viewId = UUID().uuidString
37-
self.facelivenessDetectorViewId = viewId
34+
) {
3835
self.disableStartView = disableStartView
3936
self._isPresented = isPresented
4037
self.onCompletion = onCompletion
@@ -44,8 +41,6 @@ public struct FaceLivenessDetectorView: View {
4441
withID: sessionID,
4542
credentialsProvider: credentialsProvider,
4643
region: region,
47-
options: .init(faceLivenessDetectorViewId: viewId,
48-
preCheckViewEnabled: !disableStartView),
4944
completion: map(detectionCompletion: onCompletion)
5045
)
5146
return session
@@ -83,7 +78,8 @@ public struct FaceLivenessDetectorView: View {
8378
captureSession: captureSession,
8479
videoChunker: videoChunker,
8580
closeButtonAction: { onCompletion(.failure(.userCancelled)) },
86-
sessionID: sessionID
81+
sessionID: sessionID,
82+
isPreviewScreenEnabled: !disableStartView
8783
)
8884
)
8985

@@ -99,8 +95,6 @@ public struct FaceLivenessDetectorView: View {
9995
onCompletion: @escaping (Result<Void, FaceLivenessDetectionError>) -> Void,
10096
captureSession: LivenessCaptureSession
10197
) {
102-
let viewId = UUID().uuidString
103-
self.facelivenessDetectorViewId = viewId
10498
self.disableStartView = disableStartView
10599
self._isPresented = isPresented
106100
self.onCompletion = onCompletion
@@ -110,8 +104,6 @@ public struct FaceLivenessDetectorView: View {
110104
withID: sessionID,
111105
credentialsProvider: credentialsProvider,
112106
region: region,
113-
options: .init(faceLivenessDetectorViewId: viewId,
114-
preCheckViewEnabled: !disableStartView),
115107
completion: map(detectionCompletion: onCompletion)
116108
)
117109
return session
@@ -128,7 +120,8 @@ public struct FaceLivenessDetectorView: View {
128120
captureSession: captureSession,
129121
videoChunker: captureSession.outputSampleBufferCapturer!.videoChunker,
130122
closeButtonAction: { onCompletion(.failure(.userCancelled)) },
131-
sessionID: sessionID
123+
sessionID: sessionID,
124+
isPreviewScreenEnabled: !disableStartView
132125
)
133126
)
134127
}

Diff for: Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionViewModel+FaceDetectionResultHandler.swift

+1-3
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,7 @@ extension FaceLivenessDetectionViewModel: FaceDetectionResultHandler {
121121
}
122122
}
123123
case .faceMovementChallenge:
124-
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
125-
self.livenessViewControllerDelegate?.completeNoLightCheck()
126-
}
124+
self.livenessViewControllerDelegate?.completeNoLightCheck()
127125
default:
128126
break
129127
}

Diff for: Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionViewModel.swift

+19-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import AVFoundation
1212

1313
fileprivate let videoSize: CGSize = .init(width: 480, height: 640)
1414
fileprivate let defaultNoFitTimeoutInterval: TimeInterval = 7
15+
fileprivate let defaultAttemptCountResetInterval: TimeInterval = 300.0
1516

1617
@MainActor
1718
class FaceLivenessDetectionViewModel: ObservableObject {
@@ -28,6 +29,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
2829
let faceDetector: FaceDetector
2930
let faceInOvalMatching: FaceInOvalMatching
3031
let challengeID: String = UUID().uuidString
32+
let isPreviewScreenEnabled : Bool
3133
var colorSequences: [ColorSequence] = []
3234
var hasSentFinalVideoEvent = false
3335
var hasSentFirstVideo = false
@@ -43,6 +45,9 @@ class FaceLivenessDetectionViewModel: ObservableObject {
4345
var faceMatchedTimestamp: UInt64?
4446
var noFitStartTime: Date?
4547

48+
static var attemptCount: Int = 0
49+
static var attemptIdTimeStamp: Date = Date()
50+
4651
var noFitTimeoutInterval: TimeInterval {
4752
if let sessionTimeoutMilliSec = sessionConfiguration?.ovalMatchChallenge.oval.ovalFitTimeout {
4853
return TimeInterval(sessionTimeoutMilliSec/1_000)
@@ -58,7 +63,8 @@ class FaceLivenessDetectionViewModel: ObservableObject {
5863
videoChunker: VideoChunker,
5964
stateMachine: LivenessStateMachine = .init(state: .initial),
6065
closeButtonAction: @escaping () -> Void,
61-
sessionID: String
66+
sessionID: String,
67+
isPreviewScreenEnabled: Bool
6268
) {
6369
self.closeButtonAction = closeButtonAction
6470
self.videoChunker = videoChunker
@@ -67,6 +73,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
6773
self.captureSession = captureSession
6874
self.faceDetector = faceDetector
6975
self.faceInOvalMatching = faceInOvalMatching
76+
self.isPreviewScreenEnabled = isPreviewScreenEnabled
7077

7178
self.closeButtonAction = { [weak self] in
7279
guard let self else { return }
@@ -186,13 +193,20 @@ class FaceLivenessDetectionViewModel: ObservableObject {
186193

187194
func initializeLivenessStream() {
188195
do {
189-
guard let livenessSession = livenessService as? FaceLivenessSession else {
190-
throw FaceLivenessDetectionError.unknown
196+
if (abs(Self.attemptIdTimeStamp.timeIntervalSinceNow) > defaultAttemptCountResetInterval) {
197+
Self.attemptCount = 1
198+
} else {
199+
Self.attemptCount += 1
191200
}
201+
Self.attemptIdTimeStamp = Date()
192202

193-
try livenessSession.initializeLivenessStream(
203+
try livenessService?.initializeLivenessStream(
194204
withSessionID: sessionID,
195-
userAgent: UserAgentValues.standard().userAgentString
205+
userAgent: UserAgentValues.standard().userAgentString,
206+
challenges: FaceLivenessSession.supportedChallenges,
207+
options: .init(
208+
attemptCount: Self.attemptCount,
209+
preCheckViewEnabled: isPreviewScreenEnabled)
196210
)
197211
} catch {
198212
DispatchQueue.main.async {

Diff for: Tests/FaceLivenessTests/CredentialsProviderTestCase.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ final class CredentialsProviderTestCase: XCTestCase {
4141
captureSession: captureSession,
4242
videoChunker: videoChunker,
4343
closeButtonAction: {},
44-
sessionID: UUID().uuidString
44+
sessionID: UUID().uuidString,
45+
isPreviewScreenEnabled: false
4546
)
4647

4748
self.videoChunker = videoChunker

0 commit comments

Comments
 (0)