Skip to content

Commit

Permalink
chore: Add attempt count changes (#137)
Browse files Browse the repository at this point in the history
* chore: Add attempt count changes

* Fix unit tests

* add unit tests

* Update region for example liveness view

* Update amplify-swift dependency
  • Loading branch information
thisisabhash authored May 6, 2024
1 parent cc166eb commit 3073bf1
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"location" : "https://github.com/aws-amplify/amplify-swift",
"state" : {
"branch" : "feat/no-light-support",
"revision" : "7c1fa2f7a766208f5af69ca8dce5fd02e6de4db6"
"revision" : "22e02fa21399122aac1d8b4f6ab23c242c79dae6"
}
},
{
Expand Down
4 changes: 4 additions & 0 deletions HostApp/HostApp/Model/LivenessResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
//

import Foundation
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin

struct LivenessResult: Codable {
let auditImageBytes: String?
let confidenceScore: Double
let isLive: Bool
let challenge: Challenge?
}

extension LivenessResult: CustomDebugStringConvertible {
Expand All @@ -20,6 +22,8 @@ extension LivenessResult: CustomDebugStringConvertible {
- confidenceScore: \(confidenceScore)
- isLive: \(isLive)
- auditImageBytes: \(auditImageBytes == nil ? "nil" : "<placeholder>")
- challengeType: \(String(describing: challenge?.type))
- challengeVersion: \(String(describing: challenge?.version))
"""
}
}
3 changes: 1 addition & 2 deletions HostApp/HostApp/Views/ExampleLivenessView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ struct ExampleLivenessView: View {
case .liveness:
FaceLivenessDetectorView(
sessionID: viewModel.sessionID,
// TODO: Change before merging to main
region: "us-west-2",
region: "us-east-1",
isPresented: Binding(
get: { viewModel.presentationState == .liveness },
set: { _ in }
Expand Down
4 changes: 4 additions & 0 deletions HostApp/HostApp/Views/LivenessResultContentView+Result.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import SwiftUI
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin

extension LivenessResultContentView {
struct Result {
Expand All @@ -15,6 +16,7 @@ extension LivenessResultContentView {
let valueBackgroundColor: Color
let auditImage: Data?
let isLive: Bool
let challenge: Challenge?

init(livenessResult: LivenessResult) {
guard livenessResult.confidenceScore > 0 else {
Expand All @@ -24,6 +26,7 @@ extension LivenessResultContentView {
valueBackgroundColor = .clear
auditImage = nil
isLive = false
challenge = nil
return
}
isLive = livenessResult.isLive
Expand All @@ -41,6 +44,7 @@ extension LivenessResultContentView {
auditImage = livenessResult.auditImageBytes.flatMap{
Data(base64Encoded: $0)
}
challenge = livenessResult.challenge
}
}

Expand Down
64 changes: 44 additions & 20 deletions HostApp/HostApp/Views/LivenessResultContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
//

import SwiftUI
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin

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

var body: some View {
Expand Down Expand Up @@ -67,26 +68,48 @@ struct LivenessResultContentView: View {
}
}

func step(number: Int, text: String) -> some View {
HStack(alignment: .top) {
Text("\(number).")
Text(text)
}
}

@ViewBuilder
private func steps() -> some View {
func step(number: Int, text: String) -> some View {
HStack(alignment: .top) {
Text("\(number).")
Text(text)
switch result.challenge?.type {
case .faceMovementChallenge:
VStack(
alignment: .leading,
spacing: 8
) {
Text("Tips to pass the video check:")
.fontWeight(.semibold)

Text("Remove sunglasses, mask, hat, or anything blocking your face.")
.accessibilityElement(children: .combine)
}
case .faceMovementAndLightChallenge:
VStack(
alignment: .leading,
spacing: 8
) {
Text("Tips to pass the video check:")
.fontWeight(.semibold)

step(number: 1, text: "Avoid very bright lighting conditions, such as direct sunlight.")
.accessibilityElement(children: .combine)

step(number: 2, text: "Remove sunglasses, mask, hat, or anything blocking your face.")
.accessibilityElement(children: .combine)
}
case .none:
VStack(
alignment: .leading,
spacing: 8
) {
EmptyView()
}
}

return VStack(
alignment: .leading,
spacing: 8
) {
Text("Tips to pass the video check:")
.fontWeight(.semibold)

step(number: 1, text: "Avoid very bright lighting conditions, such as direct sunlight.")
.accessibilityElement(children: .combine)

step(number: 2, text: "Remove sunglasses, mask, hat, or anything blocking your face.")
.accessibilityElement(children: .combine)
}
}
}
Expand All @@ -99,7 +122,8 @@ extension LivenessResultContentView {
livenessResult: .init(
auditImageBytes: nil,
confidenceScore: 99.8329,
isLive: true
isLive: true,
challenge: nil
)
)
}
Expand Down
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"location" : "https://github.com/aws-amplify/amplify-swift",
"state" : {
"branch" : "feat/no-light-support",
"revision" : "7c1fa2f7a766208f5af69ca8dce5fd02e6de4db6"
"revision" : "22e02fa21399122aac1d8b4f6ab23c242c79dae6"
}
},
{
Expand Down
35 changes: 13 additions & 22 deletions Sources/FaceLiveness/Views/GetReadyPage/GetReadyPageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,19 @@ struct GetReadyPageView: View {
VStack {
ZStack {
CameraPreviewView()
switch self.challenge.type {
case .faceMovementChallenge:
VStack {
Text(LocalizedStrings.preview_center_your_face_text)
.font(.title)
.multilineTextAlignment(.center)
Spacer()
}.padding()
case . faceMovementAndLightChallenge:
VStack {
WarningBox(
titleText: LocalizedStrings.get_ready_photosensitivity_title,
bodyText: LocalizedStrings.get_ready_photosensitivity_description,
popoverContent: { photosensitivityWarningPopoverContent }
)
.accessibilityElement(children: .combine)
Text(LocalizedStrings.preview_center_your_face_text)
.font(.title)
.multilineTextAlignment(.center)
Spacer()
}.padding()
}
VStack {
WarningBox(
titleText: LocalizedStrings.get_ready_photosensitivity_title,
bodyText: LocalizedStrings.get_ready_photosensitivity_description,
popoverContent: { photosensitivityWarningPopoverContent }
)
.accessibilityElement(children: .combine)
.opacity(challenge.type == .faceMovementAndLightChallenge ? 1.0 : 0.0)
Text(LocalizedStrings.preview_center_your_face_text)
.font(.title)
.multilineTextAlignment(.center)
Spacer()
}.padding()
}
beginCheckButton
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public struct FaceLivenessDetectorView: View {
@State var displayingCameraPermissionsNeededAlert = false

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

let sessionTask: Task<FaceLivenessSession, Error>
Expand All @@ -32,9 +31,7 @@ public struct FaceLivenessDetectorView: View {
disableStartView: Bool = false,
isPresented: Binding<Bool>,
onCompletion: @escaping (Result<Void, FaceLivenessDetectionError>) -> Void
) {
let viewId = UUID().uuidString
self.facelivenessDetectorViewId = viewId
) {
self.disableStartView = disableStartView
self._isPresented = isPresented
self.onCompletion = onCompletion
Expand All @@ -44,8 +41,6 @@ public struct FaceLivenessDetectorView: View {
withID: sessionID,
credentialsProvider: credentialsProvider,
region: region,
options: .init(faceLivenessDetectorViewId: viewId,
preCheckViewEnabled: !disableStartView),
completion: map(detectionCompletion: onCompletion)
)
return session
Expand Down Expand Up @@ -83,7 +78,8 @@ public struct FaceLivenessDetectorView: View {
captureSession: captureSession,
videoChunker: videoChunker,
closeButtonAction: { onCompletion(.failure(.userCancelled)) },
sessionID: sessionID
sessionID: sessionID,
isPreviewScreenEnabled: !disableStartView
)
)

Expand All @@ -99,8 +95,6 @@ public struct FaceLivenessDetectorView: View {
onCompletion: @escaping (Result<Void, FaceLivenessDetectionError>) -> Void,
captureSession: LivenessCaptureSession
) {
let viewId = UUID().uuidString
self.facelivenessDetectorViewId = viewId
self.disableStartView = disableStartView
self._isPresented = isPresented
self.onCompletion = onCompletion
Expand All @@ -110,8 +104,6 @@ public struct FaceLivenessDetectorView: View {
withID: sessionID,
credentialsProvider: credentialsProvider,
region: region,
options: .init(faceLivenessDetectorViewId: viewId,
preCheckViewEnabled: !disableStartView),
completion: map(detectionCompletion: onCompletion)
)
return session
Expand All @@ -128,7 +120,8 @@ public struct FaceLivenessDetectorView: View {
captureSession: captureSession,
videoChunker: captureSession.outputSampleBufferCapturer!.videoChunker,
closeButtonAction: { onCompletion(.failure(.userCancelled)) },
sessionID: sessionID
sessionID: sessionID,
isPreviewScreenEnabled: !disableStartView
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,7 @@ extension FaceLivenessDetectionViewModel: FaceDetectionResultHandler {
}
}
case .faceMovementChallenge:
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.livenessViewControllerDelegate?.completeNoLightCheck()
}
self.livenessViewControllerDelegate?.completeNoLightCheck()
default:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AVFoundation

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

@MainActor
class FaceLivenessDetectionViewModel: ObservableObject {
Expand All @@ -28,6 +29,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
let faceDetector: FaceDetector
let faceInOvalMatching: FaceInOvalMatching
let challengeID: String = UUID().uuidString
let isPreviewScreenEnabled : Bool
var colorSequences: [ColorSequence] = []
var hasSentFinalVideoEvent = false
var hasSentFirstVideo = false
Expand All @@ -43,6 +45,9 @@ class FaceLivenessDetectionViewModel: ObservableObject {
var faceMatchedTimestamp: UInt64?
var noFitStartTime: Date?

static var attemptCount: Int = 0
static var attemptIdTimeStamp: Date = Date()

var noFitTimeoutInterval: TimeInterval {
if let sessionTimeoutMilliSec = sessionConfiguration?.ovalMatchChallenge.oval.ovalFitTimeout {
return TimeInterval(sessionTimeoutMilliSec/1_000)
Expand All @@ -58,7 +63,8 @@ class FaceLivenessDetectionViewModel: ObservableObject {
videoChunker: VideoChunker,
stateMachine: LivenessStateMachine = .init(state: .initial),
closeButtonAction: @escaping () -> Void,
sessionID: String
sessionID: String,
isPreviewScreenEnabled: Bool
) {
self.closeButtonAction = closeButtonAction
self.videoChunker = videoChunker
Expand All @@ -67,6 +73,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
self.captureSession = captureSession
self.faceDetector = faceDetector
self.faceInOvalMatching = faceInOvalMatching
self.isPreviewScreenEnabled = isPreviewScreenEnabled

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

func initializeLivenessStream() {
do {
guard let livenessSession = livenessService as? FaceLivenessSession else {
throw FaceLivenessDetectionError.unknown
if (abs(Self.attemptIdTimeStamp.timeIntervalSinceNow) > defaultAttemptCountResetInterval) {
Self.attemptCount = 1
} else {
Self.attemptCount += 1
}
Self.attemptIdTimeStamp = Date()

try livenessSession.initializeLivenessStream(
try livenessService?.initializeLivenessStream(
withSessionID: sessionID,
userAgent: UserAgentValues.standard().userAgentString
userAgent: UserAgentValues.standard().userAgentString,
challenges: FaceLivenessSession.supportedChallenges,
options: .init(
attemptCount: Self.attemptCount,
preCheckViewEnabled: isPreviewScreenEnabled)
)
} catch {
DispatchQueue.main.async {
Expand Down
3 changes: 2 additions & 1 deletion Tests/FaceLivenessTests/CredentialsProviderTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ final class CredentialsProviderTestCase: XCTestCase {
captureSession: captureSession,
videoChunker: videoChunker,
closeButtonAction: {},
sessionID: UUID().uuidString
sessionID: UUID().uuidString,
isPreviewScreenEnabled: false
)

self.videoChunker = videoChunker
Expand Down
Loading

0 comments on commit 3073bf1

Please sign in to comment.