From 7f7ad57f9b4d1b25f920b429037fe081874a1b33 Mon Sep 17 00:00:00 2001 From: Thomas Leing Date: Thu, 24 Aug 2023 15:47:53 -0700 Subject: [PATCH 1/5] API to add liveness version to websocket --- .../predictions/AWSPredictionsMetadataType.kt | 22 +++++++++++++++++++ .../predictions/aws/AWSPredictionsPlugin.java | 17 +++++++++++++- .../predictions/aws/http/LivenessWebSocket.kt | 10 ++++++++- .../aws/service/RunFaceLivenessSession.kt | 2 ++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 aws-core/src/main/java/com/amplifyframework/predictions/AWSPredictionsMetadataType.kt diff --git a/aws-core/src/main/java/com/amplifyframework/predictions/AWSPredictionsMetadataType.kt b/aws-core/src/main/java/com/amplifyframework/predictions/AWSPredictionsMetadataType.kt new file mode 100644 index 0000000000..3d80f914a5 --- /dev/null +++ b/aws-core/src/main/java/com/amplifyframework/predictions/AWSPredictionsMetadataType.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.predictions + +import com.amplifyframework.annotations.InternalAmplifyApi + +@InternalAmplifyApi +enum class AWSPredictionsMetadataType(val key: String) { + Liveness("liveness") +} diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java index 950289f6b2..3058649832 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java @@ -27,6 +27,7 @@ import com.amplifyframework.auth.CognitoCredentialsProvider; import com.amplifyframework.core.Action; import com.amplifyframework.core.Consumer; +import com.amplifyframework.predictions.AWSPredictionsMetadataType; import com.amplifyframework.predictions.PredictionsException; import com.amplifyframework.predictions.PredictionsPlugin; import com.amplifyframework.predictions.aws.models.AWSVoiceType; @@ -61,6 +62,8 @@ import org.json.JSONObject; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -72,6 +75,8 @@ public final class AWSPredictionsPlugin extends PredictionsPlugin { private static final String AWS_PREDICTIONS_PLUGIN_KEY = "awsPredictionsPlugin"; + private static Map userAgentPairs = new HashMap<>(); + private final ExecutorService executorService; private AWSPredictionsPluginConfiguration configuration; @@ -100,6 +105,16 @@ public String getPluginKey() { return AWS_PREDICTIONS_PLUGIN_KEY; } + /** + * Add version to liveness websocket user agent. + * @param type The type of version we're adding + * @param value The version + */ + @InternalAmplifyApi + public static void addToUserAgent(AWSPredictionsMetadataType type, String value) { + userAgentPairs.put(type.name(), value); + } + @Override public void configure(JSONObject pluginConfiguration, @NonNull Context context) throws PredictionsException { this.configuration = AWSPredictionsPluginConfiguration.fromJson(pluginConfiguration); @@ -358,6 +373,6 @@ public static void startFaceLivenessSession(@NonNull String sessionId, .convertToSdkCredentialsProvider(awsCredentialsProvider); } new RunFaceLivenessSession(sessionId, sessionInformation, credentialsProvider, - onSessionStarted, onComplete, onError); + userAgentPairs, onSessionStarted, onComplete, onError); } } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt index 0d36d9e8a0..97e1cc05b2 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt @@ -67,6 +67,7 @@ internal class LivenessWebSocket( val endpoint: String, val region: String, val sessionInformation: FaceLivenessSessionInformation, + val userAgentPairs: Map = mapOf(), val onSessionInformationReceived: Consumer, val onErrorReceived: Consumer, val onComplete: Action @@ -201,8 +202,15 @@ internal class LivenessWebSocket( val amplifyVersion = BuildConfig.VERSION_NAME val deviceManufacturer = Build.MANUFACTURER.replace(" ", "_") val deviceName = Build.MODEL.replace(" ", "_") - val userAgent = "${UserAgent.string()} os/Android/${Build.VERSION.SDK_INT} md/device/$deviceName " + + var userAgent = "${UserAgent.string()} os/Android/${Build.VERSION.SDK_INT} md/device/$deviceName " + "md/device-manufacturer/$deviceManufacturer api/rekognitionstreaming/$amplifyVersion" + + userAgentPairs.forEach {kv -> + val key = kv.key + val value = kv.value + userAgent += " $key:$value" + } + return userAgent.replace(Build.MANUFACTURER, deviceManufacturer).replace(Build.MODEL, deviceName) .replace("+", "_") } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt index 0f53d3e42e..f019f73d78 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt @@ -41,6 +41,7 @@ internal class RunFaceLivenessSession( sessionId: String, sessionInformation: FaceLivenessSessionInformation, val credentialsProvider: CredentialsProvider, + val userAgentPairs: Map = mapOf(), onSessionStarted: Consumer, onComplete: Action, onError: Consumer @@ -55,6 +56,7 @@ internal class RunFaceLivenessSession( "${sessionInformation.videoWidth.toInt()}&video-height=${sessionInformation.videoHeight.toInt()}", region = sessionInformation.region, sessionInformation = sessionInformation, + userAgentPairs = userAgentPairs, onSessionInformationReceived = { sessionInformation -> val challenges = processSessionInformation(sessionInformation) val faceLivenessSession = FaceLivenessSession( From 930209bdb886b1a3876c7225224e9de976402db8 Mon Sep 17 00:00:00 2001 From: Thomas Leing Date: Fri, 25 Aug 2023 11:48:10 -0700 Subject: [PATCH 2/5] lint + fix unit test --- .../amplifyframework/predictions/aws/http/LivenessWebSocket.kt | 2 +- .../predictions/aws/http/LivenessWebSocketTest.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt index 97e1cc05b2..69602bf300 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt @@ -205,7 +205,7 @@ internal class LivenessWebSocket( var userAgent = "${UserAgent.string()} os/Android/${Build.VERSION.SDK_INT} md/device/$deviceName " + "md/device-manufacturer/$deviceManufacturer api/rekognitionstreaming/$amplifyVersion" - userAgentPairs.forEach {kv -> + userAgentPairs.forEach { kv -> val key = kv.key val value = kv.value userAgent += " $key:$value" diff --git a/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt b/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt index 85c4cc841a..9e1c9f68b3 100644 --- a/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt +++ b/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt @@ -94,6 +94,7 @@ internal class LivenessWebSocketTest { server.url("/").toString(), "", sessionInformation, + mapOf(), onSessionInformationReceived, onErrorReceived, onComplete From ccc5f81f1245042d4ed8164acfdc8e91c4bdfd62 Mon Sep 17 00:00:00 2001 From: Thomas Leing Date: Thu, 31 Aug 2023 17:23:02 -0700 Subject: [PATCH 3/5] Add tests, use emptyMap and joinToString --- .../predictions/aws/http/LivenessWebSocket.kt | 10 +-- .../aws/http/LivenessWebSocketTest.kt | 80 ++++++++++++++++++- 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt index 69602bf300..dbeac66059 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt @@ -198,18 +198,18 @@ internal class LivenessWebSocket( } } - private fun getUserAgent(): String { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun getUserAgent(): String { val amplifyVersion = BuildConfig.VERSION_NAME val deviceManufacturer = Build.MANUFACTURER.replace(" ", "_") val deviceName = Build.MODEL.replace(" ", "_") var userAgent = "${UserAgent.string()} os/Android/${Build.VERSION.SDK_INT} md/device/$deviceName " + "md/device-manufacturer/$deviceManufacturer api/rekognitionstreaming/$amplifyVersion" - userAgentPairs.forEach { kv -> - val key = kv.key - val value = kv.value - userAgent += " $key:$value" + val additional = userAgentPairs.entries.joinToString(separator = "") { + " ${it.key}:${it.value}" } + userAgent += "$additional" return userAgent.replace(Build.MANUFACTURER, deviceManufacturer).replace(Build.MODEL, deviceName) .replace("+", "_") diff --git a/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt b/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt index 9e1c9f68b3..49fd448508 100644 --- a/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt +++ b/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt @@ -15,10 +15,12 @@ package com.amplifyframework.predictions.aws.http +import android.os.Build import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.util.Attributes import com.amplifyframework.core.Action +import com.amplifyframework.core.BuildConfig import com.amplifyframework.core.Consumer import com.amplifyframework.predictions.PredictionsException import com.amplifyframework.predictions.aws.models.liveness.ChallengeConfig @@ -94,7 +96,7 @@ internal class LivenessWebSocketTest { server.url("/").toString(), "", sessionInformation, - mapOf(), + emptyMap(), onSessionInformationReceived, onErrorReceived, onComplete @@ -276,6 +278,17 @@ internal class LivenessWebSocketTest { verify { livenessWebSocket.webSocket!!.close(1000, any()) } } + @Test + fun `web socket user agent base`() { + livenessWebSocket.webSocket = mockk() + + val version = BuildConfig.VERSION_NAME + val os = Build.VERSION.SDK_INT + val baseline = "amplify-android:$version md/unknown/robolectric md/locale/en_UNKNOWN os/Android/$os " + + "md/device/robolectric md/device-manufacturer/unknown api/rekognitionstreaming/$version" + assertEquals(livenessWebSocket.getUserAgent(), baseline) + } + @Test @Ignore("Need to work on parsing the onMessage byteString from ServerWebSocketListener") fun `sendInitialFaceDetectedEvent test`() { @@ -302,9 +315,72 @@ internal class LivenessWebSocketTest { } } +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(RobolectricTestRunner::class) +internal class LivenessWebSocketTestUserAgent { + private val json = Json { encodeDefaults = true } + + private lateinit var livenessWebSocket: LivenessWebSocket + private lateinit var server: MockWebServer + + private val onComplete = mockk(relaxed = true) + private val onSessionInformationReceived = mockk>(relaxed = true) + private val onErrorReceived = mockk>(relaxed = true) + private val credentialsProvider = object : CredentialsProvider { + override suspend fun resolve(attributes: Attributes): Credentials { + return Credentials( + "", + "", + "", + null, + "" + ) + } + } + private val sessionInformation = FaceLivenessSessionInformation(1f, 1f, "1", "3") + + @Before + fun setUp() { + Dispatchers.setMain(Dispatchers.Unconfined) + + server = MockWebServer() + + livenessWebSocket = LivenessWebSocket( + credentialsProvider, + server.url("/").toString(), + "", + sessionInformation, + mapOf(Pair("liveness", "1.1.1")), + onSessionInformationReceived, + onErrorReceived, + onComplete + ) + } + + @After + fun shutDown() { + server.shutdown() + Dispatchers.resetMain() + } + + @Test + fun `web socket user agent includes added UI version`() { + livenessWebSocket.webSocket = mockk() + + val version = BuildConfig.VERSION_NAME + val os = Build.VERSION.SDK_INT + val baseline = "amplify-android:$version md/unknown/robolectric md/locale/en_UNKNOWN os/Android/$os " + + "md/device/robolectric md/device-manufacturer/unknown api/rekognitionstreaming/$version" + val additional = livenessWebSocket.userAgentPairs.entries.joinToString { + "${it.key}:${it.value}" + } + assertEquals(livenessWebSocket.getUserAgent(), "$baseline $additional") + } +} + class LatchingWebSocketResponseListener( private val webSocketListener: WebSocketListener, - private val openLatch: CountDownLatch = CountDownLatch(1), + private val openLatch: CountDownLatch = CountDownLatch(1) ) : WebSocketListener() { override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { From 58eb46149aec7e8d827aca77b48a5c8a9e914039 Mon Sep 17 00:00:00 2001 From: Thomas Leing Date: Fri, 8 Sep 2023 22:23:27 -0700 Subject: [PATCH 4/5] adjust API to take version instead of adding new API + map for storing UA pairs --- .../predictions/AWSPredictionsMetadataType.kt | 22 ------ .../predictions/aws/AWSPredictionsPlugin.java | 23 ++---- .../predictions/aws/http/LivenessWebSocket.kt | 9 +-- .../aws/service/RunFaceLivenessSession.kt | 4 +- .../aws/http/LivenessWebSocketTest.kt | 77 ++++--------------- 5 files changed, 25 insertions(+), 110 deletions(-) delete mode 100644 aws-core/src/main/java/com/amplifyframework/predictions/AWSPredictionsMetadataType.kt diff --git a/aws-core/src/main/java/com/amplifyframework/predictions/AWSPredictionsMetadataType.kt b/aws-core/src/main/java/com/amplifyframework/predictions/AWSPredictionsMetadataType.kt deleted file mode 100644 index 3d80f914a5..0000000000 --- a/aws-core/src/main/java/com/amplifyframework/predictions/AWSPredictionsMetadataType.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -package com.amplifyframework.predictions - -import com.amplifyframework.annotations.InternalAmplifyApi - -@InternalAmplifyApi -enum class AWSPredictionsMetadataType(val key: String) { - Liveness("liveness") -} diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java index 3058649832..547e4b3729 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java @@ -27,7 +27,6 @@ import com.amplifyframework.auth.CognitoCredentialsProvider; import com.amplifyframework.core.Action; import com.amplifyframework.core.Consumer; -import com.amplifyframework.predictions.AWSPredictionsMetadataType; import com.amplifyframework.predictions.PredictionsException; import com.amplifyframework.predictions.PredictionsPlugin; import com.amplifyframework.predictions.aws.models.AWSVoiceType; @@ -62,8 +61,6 @@ import org.json.JSONObject; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -75,8 +72,6 @@ public final class AWSPredictionsPlugin extends PredictionsPlugin { private static final String AWS_PREDICTIONS_PLUGIN_KEY = "awsPredictionsPlugin"; - private static Map userAgentPairs = new HashMap<>(); - private final ExecutorService executorService; private AWSPredictionsPluginConfiguration configuration; @@ -105,16 +100,6 @@ public String getPluginKey() { return AWS_PREDICTIONS_PLUGIN_KEY; } - /** - * Add version to liveness websocket user agent. - * @param type The type of version we're adding - * @param value The version - */ - @InternalAmplifyApi - public static void addToUserAgent(AWSPredictionsMetadataType type, String value) { - userAgentPairs.put(type.name(), value); - } - @Override public void configure(JSONObject pluginConfiguration, @NonNull Context context) throws PredictionsException { this.configuration = AWSPredictionsPluginConfiguration.fromJson(pluginConfiguration); @@ -329,6 +314,7 @@ public InterpretOperation interpret( * Starts a Liveness session. * @param sessionId ID for the session to start. * @param sessionInformation Information about the face liveness session. + * @param livenessVersion The version of liveness, which will be attached to the user agent. * @param onSessionStarted Called when the face liveness session has been started. * @param onComplete Called when the session is complete. * @param onError Called when an error occurs during the session. @@ -336,17 +322,19 @@ public InterpretOperation interpret( @InternalAmplifyApi public static void startFaceLivenessSession(@NonNull String sessionId, @NonNull FaceLivenessSessionInformation sessionInformation, + @NonNull String livenessVersion, @NonNull Consumer onSessionStarted, @NonNull Action onComplete, @NonNull Consumer onError) { startFaceLivenessSession(sessionId, sessionInformation, FaceLivenessSessionOptions.defaults(), - onSessionStarted, onComplete, onError); + livenessVersion, onSessionStarted, onComplete, onError); } /** * Starts a Liveness session with the given options. * @param sessionId ID for the session to start. * @param sessionInformation Information about the face liveness session. + * @param livenessVersion The version of liveness, which will be attached to the user agent. * @param options The options for this session. * @param onSessionStarted Called when the face liveness session has been started. * @param onComplete Called when the session is complete. @@ -356,6 +344,7 @@ public static void startFaceLivenessSession(@NonNull String sessionId, public static void startFaceLivenessSession(@NonNull String sessionId, @NonNull FaceLivenessSessionInformation sessionInformation, @NonNull FaceLivenessSessionOptions options, + @NonNull String livenessVersion, @NonNull Consumer onSessionStarted, @NonNull Action onComplete, @NonNull Consumer onError) { @@ -373,6 +362,6 @@ public static void startFaceLivenessSession(@NonNull String sessionId, .convertToSdkCredentialsProvider(awsCredentialsProvider); } new RunFaceLivenessSession(sessionId, sessionInformation, credentialsProvider, - userAgentPairs, onSessionStarted, onComplete, onError); + livenessVersion, onSessionStarted, onComplete, onError); } } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt index dbeac66059..1383b6c7d6 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt @@ -67,7 +67,7 @@ internal class LivenessWebSocket( val endpoint: String, val region: String, val sessionInformation: FaceLivenessSessionInformation, - val userAgentPairs: Map = mapOf(), + val livenessVersion: String = "", val onSessionInformationReceived: Consumer, val onErrorReceived: Consumer, val onComplete: Action @@ -199,17 +199,16 @@ internal class LivenessWebSocket( } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun getUserAgent(): String { + fun getUserAgent(livenessVersion: String = ""): String { val amplifyVersion = BuildConfig.VERSION_NAME val deviceManufacturer = Build.MANUFACTURER.replace(" ", "_") val deviceName = Build.MODEL.replace(" ", "_") var userAgent = "${UserAgent.string()} os/Android/${Build.VERSION.SDK_INT} md/device/$deviceName " + "md/device-manufacturer/$deviceManufacturer api/rekognitionstreaming/$amplifyVersion" - val additional = userAgentPairs.entries.joinToString(separator = "") { - " ${it.key}:${it.value}" + if (livenessVersion != "") { + userAgent += " api/liveness/$livenessVersion" } - userAgent += "$additional" return userAgent.replace(Build.MANUFACTURER, deviceManufacturer).replace(Build.MODEL, deviceName) .replace("+", "_") diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt index f019f73d78..ebc3da22ab 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt @@ -41,7 +41,7 @@ internal class RunFaceLivenessSession( sessionId: String, sessionInformation: FaceLivenessSessionInformation, val credentialsProvider: CredentialsProvider, - val userAgentPairs: Map = mapOf(), + livenessVersion: String, onSessionStarted: Consumer, onComplete: Action, onError: Consumer @@ -56,7 +56,7 @@ internal class RunFaceLivenessSession( "${sessionInformation.videoWidth.toInt()}&video-height=${sessionInformation.videoHeight.toInt()}", region = sessionInformation.region, sessionInformation = sessionInformation, - userAgentPairs = userAgentPairs, + livenessVersion = livenessVersion, onSessionInformationReceived = { sessionInformation -> val challenges = processSessionInformation(sessionInformation) val faceLivenessSession = FaceLivenessSession( diff --git a/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt b/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt index 49fd448508..393caa90a9 100644 --- a/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt +++ b/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt @@ -96,7 +96,7 @@ internal class LivenessWebSocketTest { server.url("/").toString(), "", sessionInformation, - emptyMap(), + "", onSessionInformationReceived, onErrorReceived, onComplete @@ -289,6 +289,18 @@ internal class LivenessWebSocketTest { assertEquals(livenessWebSocket.getUserAgent(), baseline) } + @Test + fun `web socket user agent includes added UI version`() { + livenessWebSocket.webSocket = mockk() + + val version = BuildConfig.VERSION_NAME + val os = Build.VERSION.SDK_INT + val baseline = "amplify-android:$version md/unknown/robolectric md/locale/en_UNKNOWN os/Android/$os " + + "md/device/robolectric md/device-manufacturer/unknown api/rekognitionstreaming/$version" + val additional = "api/liveness/1.1.1" + assertEquals(livenessWebSocket.getUserAgent("1.1.1"), "$baseline $additional") + } + @Test @Ignore("Need to work on parsing the onMessage byteString from ServerWebSocketListener") fun `sendInitialFaceDetectedEvent test`() { @@ -315,69 +327,6 @@ internal class LivenessWebSocketTest { } } -@OptIn(ExperimentalCoroutinesApi::class) -@RunWith(RobolectricTestRunner::class) -internal class LivenessWebSocketTestUserAgent { - private val json = Json { encodeDefaults = true } - - private lateinit var livenessWebSocket: LivenessWebSocket - private lateinit var server: MockWebServer - - private val onComplete = mockk(relaxed = true) - private val onSessionInformationReceived = mockk>(relaxed = true) - private val onErrorReceived = mockk>(relaxed = true) - private val credentialsProvider = object : CredentialsProvider { - override suspend fun resolve(attributes: Attributes): Credentials { - return Credentials( - "", - "", - "", - null, - "" - ) - } - } - private val sessionInformation = FaceLivenessSessionInformation(1f, 1f, "1", "3") - - @Before - fun setUp() { - Dispatchers.setMain(Dispatchers.Unconfined) - - server = MockWebServer() - - livenessWebSocket = LivenessWebSocket( - credentialsProvider, - server.url("/").toString(), - "", - sessionInformation, - mapOf(Pair("liveness", "1.1.1")), - onSessionInformationReceived, - onErrorReceived, - onComplete - ) - } - - @After - fun shutDown() { - server.shutdown() - Dispatchers.resetMain() - } - - @Test - fun `web socket user agent includes added UI version`() { - livenessWebSocket.webSocket = mockk() - - val version = BuildConfig.VERSION_NAME - val os = Build.VERSION.SDK_INT - val baseline = "amplify-android:$version md/unknown/robolectric md/locale/en_UNKNOWN os/Android/$os " + - "md/device/robolectric md/device-manufacturer/unknown api/rekognitionstreaming/$version" - val additional = livenessWebSocket.userAgentPairs.entries.joinToString { - "${it.key}:${it.value}" - } - assertEquals(livenessWebSocket.getUserAgent(), "$baseline $additional") - } -} - class LatchingWebSocketResponseListener( private val webSocketListener: WebSocketListener, private val openLatch: CountDownLatch = CountDownLatch(1) From 4cd41d80fe1cd51c3f81eb85517827c6b56e63f5 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Mon, 11 Sep 2023 16:44:17 -0300 Subject: [PATCH 5/5] Fix setting the Liveness user agent string --- .../predictions/aws/AWSPredictionsPlugin.java | 44 +++++++++++++- .../predictions/aws/http/LivenessWebSocket.kt | 6 +- .../aws/service/RunFaceLivenessSession.kt | 2 +- .../aws/http/LivenessWebSocketTest.kt | 60 ++++++++++++++----- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java index 547e4b3729..88ff64ff07 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/AWSPredictionsPlugin.java @@ -18,6 +18,7 @@ import android.content.Context; import android.graphics.Bitmap; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.amplifyframework.annotations.InternalAmplifyApi; @@ -310,6 +311,45 @@ public InterpretOperation interpret( return operation; } + /** + * Starts a Liveness session. + * @param sessionId ID for the session to start. + * @param sessionInformation Information about the face liveness session. + * @param onSessionStarted Called when the face liveness session has been started. + * @param onComplete Called when the session is complete. + * @param onError Called when an error occurs during the session. + */ + @InternalAmplifyApi + public static void startFaceLivenessSession(@NonNull String sessionId, + @NonNull FaceLivenessSessionInformation sessionInformation, + @NonNull Consumer onSessionStarted, + @NonNull Action onComplete, + @NonNull Consumer onError) { + startFaceLivenessSession(sessionId, sessionInformation, FaceLivenessSessionOptions.defaults(), + onSessionStarted, onComplete, onError); + } + + /** + * Starts a Liveness session with the given options. + * @param sessionId ID for the session to start. + * @param sessionInformation Information about the face liveness session. + * @param options The options for this session. + * @param onSessionStarted Called when the face liveness session has been started. + * @param onComplete Called when the session is complete. + * @param onError Called when an error occurs during the session. + */ + @InternalAmplifyApi + public static void startFaceLivenessSession(@NonNull String sessionId, + @NonNull FaceLivenessSessionInformation sessionInformation, + @NonNull FaceLivenessSessionOptions options, + @NonNull Consumer onSessionStarted, + @NonNull Action onComplete, + @NonNull Consumer onError) { + + startFaceLivenessSession(sessionId, sessionInformation, options, null, + onSessionStarted, onComplete, onError); + } + /** * Starts a Liveness session. * @param sessionId ID for the session to start. @@ -322,7 +362,7 @@ public InterpretOperation interpret( @InternalAmplifyApi public static void startFaceLivenessSession(@NonNull String sessionId, @NonNull FaceLivenessSessionInformation sessionInformation, - @NonNull String livenessVersion, + @Nullable String livenessVersion, @NonNull Consumer onSessionStarted, @NonNull Action onComplete, @NonNull Consumer onError) { @@ -344,7 +384,7 @@ public static void startFaceLivenessSession(@NonNull String sessionId, public static void startFaceLivenessSession(@NonNull String sessionId, @NonNull FaceLivenessSessionInformation sessionInformation, @NonNull FaceLivenessSessionOptions options, - @NonNull String livenessVersion, + @Nullable String livenessVersion, @NonNull Consumer onSessionStarted, @NonNull Action onComplete, @NonNull Consumer onError) { diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt index 1383b6c7d6..2009ca60a3 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/http/LivenessWebSocket.kt @@ -67,7 +67,7 @@ internal class LivenessWebSocket( val endpoint: String, val region: String, val sessionInformation: FaceLivenessSessionInformation, - val livenessVersion: String = "", + val livenessVersion: String?, val onSessionInformationReceived: Consumer, val onErrorReceived: Consumer, val onComplete: Action @@ -199,14 +199,14 @@ internal class LivenessWebSocket( } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun getUserAgent(livenessVersion: String = ""): String { + fun getUserAgent(): String { val amplifyVersion = BuildConfig.VERSION_NAME val deviceManufacturer = Build.MANUFACTURER.replace(" ", "_") val deviceName = Build.MODEL.replace(" ", "_") var userAgent = "${UserAgent.string()} os/Android/${Build.VERSION.SDK_INT} md/device/$deviceName " + "md/device-manufacturer/$deviceManufacturer api/rekognitionstreaming/$amplifyVersion" - if (livenessVersion != "") { + if (!livenessVersion.isNullOrBlank()) { userAgent += " api/liveness/$livenessVersion" } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt index ebc3da22ab..b98474d915 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/RunFaceLivenessSession.kt @@ -41,7 +41,7 @@ internal class RunFaceLivenessSession( sessionId: String, sessionInformation: FaceLivenessSessionInformation, val credentialsProvider: CredentialsProvider, - livenessVersion: String, + livenessVersion: String?, onSessionStarted: Consumer, onComplete: Action, onError: Consumer diff --git a/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt b/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt index 393caa90a9..3a2c214385 100644 --- a/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt +++ b/aws-predictions/src/test/java/com/amplifyframework/predictions/aws/http/LivenessWebSocketTest.kt @@ -70,7 +70,6 @@ import org.robolectric.RobolectricTestRunner internal class LivenessWebSocketTest { private val json = Json { encodeDefaults = true } - private lateinit var livenessWebSocket: LivenessWebSocket private lateinit var server: MockWebServer private val onComplete = mockk(relaxed = true) @@ -79,7 +78,11 @@ internal class LivenessWebSocketTest { private val credentialsProvider = object : CredentialsProvider { override suspend fun resolve(attributes: Attributes): Credentials { return Credentials( - "", "", "", null, "" + "", + "", + "", + null, + "" ) } } @@ -88,19 +91,7 @@ internal class LivenessWebSocketTest { @Before fun setUp() { Dispatchers.setMain(Dispatchers.Unconfined) - server = MockWebServer() - - livenessWebSocket = LivenessWebSocket( - credentialsProvider, - server.url("/").toString(), - "", - sessionInformation, - "", - onSessionInformationReceived, - onErrorReceived, - onComplete - ) } @After @@ -112,6 +103,7 @@ internal class LivenessWebSocketTest { @Test fun `onClosing informs webSocket`() { val webSocket = mockk(relaxed = true) + val livenessWebSocket = createLivenessWebSocket() livenessWebSocket.webSocket = webSocket livenessWebSocket.webSocketListener.onClosing(webSocket, 4, "closing") @@ -121,6 +113,7 @@ internal class LivenessWebSocketTest { @Test fun `normal status onClosed calls onComplete`() { + val livenessWebSocket = createLivenessWebSocket() livenessWebSocket.webSocketListener.onClosed(mockk(), 1000, "closing") verify { onComplete.call() } @@ -128,6 +121,7 @@ internal class LivenessWebSocketTest { @Test fun `bad status onClosed calls onError`() { + val livenessWebSocket = createLivenessWebSocket() livenessWebSocket.webSocketListener.onClosed(mockk(), 5000, "closing") verify { onErrorReceived.accept(any()) } @@ -135,6 +129,7 @@ internal class LivenessWebSocketTest { @Test fun `onClosed does not call onError if client stopped`() { + val livenessWebSocket = createLivenessWebSocket() livenessWebSocket.clientStoppedSession = true livenessWebSocket.webSocketListener.onClosed(mockk(), 5000, "closing") @@ -144,6 +139,7 @@ internal class LivenessWebSocketTest { @Test fun `onFailure calls onError`() { + val livenessWebSocket = createLivenessWebSocket() // Response does noted like to be mockk val response = Response.Builder() .code(200) @@ -159,6 +155,7 @@ internal class LivenessWebSocketTest { @Test fun `onFailure does not call onError if client stopped`() { + val livenessWebSocket = createLivenessWebSocket() livenessWebSocket.clientStoppedSession = true // Response does noted like to be mockk val response = Response.Builder() @@ -175,6 +172,7 @@ internal class LivenessWebSocketTest { @Test fun `web socket assigned on open`() { + val livenessWebSocket = createLivenessWebSocket() val openLatch = CountDownLatch(1) val latchingListener = LatchingWebSocketResponseListener( livenessWebSocket.webSocketListener, @@ -203,6 +201,7 @@ internal class LivenessWebSocketTest { @Test fun `server session event tracked`() { + val livenessWebSocket = createLivenessWebSocket() val event = ServerSessionInformationEvent( sessionInformation = SessionInformation( challenge = ServerChallenge( @@ -244,6 +243,7 @@ internal class LivenessWebSocketTest { @Test fun `disconnect event stops websocket`() { + val livenessWebSocket = createLivenessWebSocket() livenessWebSocket.webSocket = mockk() val event = DisconnectionEvent(1) val headers = mapOf( @@ -262,6 +262,7 @@ internal class LivenessWebSocketTest { @Test fun `web socket error closes websocket`() { + val livenessWebSocket = createLivenessWebSocket() livenessWebSocket.webSocket = mockk() val event = ValidationException("ValidationException") val headers = mapOf( @@ -279,7 +280,20 @@ internal class LivenessWebSocketTest { } @Test - fun `web socket user agent base`() { + fun `web socket user agent with null UI version`() { + val livenessWebSocket = createLivenessWebSocket(livenessVersion = null) + livenessWebSocket.webSocket = mockk() + + val version = BuildConfig.VERSION_NAME + val os = Build.VERSION.SDK_INT + val baseline = "amplify-android:$version md/unknown/robolectric md/locale/en_UNKNOWN os/Android/$os " + + "md/device/robolectric md/device-manufacturer/unknown api/rekognitionstreaming/$version" + assertEquals(livenessWebSocket.getUserAgent(), baseline) + } + + @Test + fun `web socket user agent with blank UI version`() { + val livenessWebSocket = createLivenessWebSocket(livenessVersion = " ") livenessWebSocket.webSocket = mockk() val version = BuildConfig.VERSION_NAME @@ -291,6 +305,7 @@ internal class LivenessWebSocketTest { @Test fun `web socket user agent includes added UI version`() { + val livenessWebSocket = createLivenessWebSocket(livenessVersion = "1.1.1") livenessWebSocket.webSocket = mockk() val version = BuildConfig.VERSION_NAME @@ -298,7 +313,7 @@ internal class LivenessWebSocketTest { val baseline = "amplify-android:$version md/unknown/robolectric md/locale/en_UNKNOWN os/Android/$os " + "md/device/robolectric md/device-manufacturer/unknown api/rekognitionstreaming/$version" val additional = "api/liveness/1.1.1" - assertEquals(livenessWebSocket.getUserAgent("1.1.1"), "$baseline $additional") + assertEquals(livenessWebSocket.getUserAgent(), "$baseline $additional") } @Test @@ -325,6 +340,19 @@ internal class LivenessWebSocketTest { @Ignore("Need to work on parsing the onMessage byteString from ServerWebSocketListener") fun `sendVideoEvent test`() { } + + private fun createLivenessWebSocket( + livenessVersion: String? = null + ) = LivenessWebSocket( + credentialsProvider, + server.url("/").toString(), + "", + sessionInformation, + livenessVersion, + onSessionInformationReceived, + onErrorReceived, + onComplete + ) } class LatchingWebSocketResponseListener(