Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add attempt count changes #137

Merged
merged 5 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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)
phantumcode marked this conversation as resolved.
Show resolved Hide resolved
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
phantumcode marked this conversation as resolved.
Show resolved Hide resolved
} 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
Loading