diff --git a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt index 56beb1db79..5b01db9e90 100644 --- a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt +++ b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt @@ -17,6 +17,7 @@ package com.amplifyframework.analytics.pinpoint import android.content.Context import aws.sdk.kotlin.services.pinpoint.PinpointClient import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider +import com.amplifyframework.core.store.EncryptedKeyValueRepository import com.amplifyframework.pinpoint.core.AnalyticsClient import com.amplifyframework.pinpoint.core.TargetingClient import com.amplifyframework.pinpoint.core.data.AndroidAppDetails @@ -61,11 +62,17 @@ internal class PinpointManager constructor( Context.MODE_PRIVATE ) + val encryptedStore = EncryptedKeyValueRepository( + context, + "${awsPinpointConfiguration.appId}$PINPOINT_SHARED_PREFS_SUFFIX" + ) + val androidAppDetails = AndroidAppDetails(context, awsPinpointConfiguration.appId) val androidDeviceDetails = AndroidDeviceDetails(context) targetingClient = TargetingClient( context, pinpointClient, + encryptedStore, sharedPrefs, androidAppDetails, androidDeviceDetails, diff --git a/aws-analytics-pinpoint/src/test/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPluginBehaviorTest.kt b/aws-analytics-pinpoint/src/test/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPluginBehaviorTest.kt index b5f352cba1..d2b17cb2cc 100644 --- a/aws-analytics-pinpoint/src/test/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPluginBehaviorTest.kt +++ b/aws-analytics-pinpoint/src/test/java/com/amplifyframework/analytics/pinpoint/AWSPinpointAnalyticsPluginBehaviorTest.kt @@ -150,7 +150,8 @@ class AWSPinpointAnalyticsPluginBehaviorTest { sharedPrefs.getUniqueId(), androidAppDetails, androidDeviceDetails, - ApplicationProvider.getApplicationContext() + ApplicationProvider.getApplicationContext(), + mockk() ) } val actualEndpoint = slot() diff --git a/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/EventRecorder.kt b/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/EventRecorder.kt index 99d110d984..519d223e36 100644 --- a/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/EventRecorder.kt +++ b/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/EventRecorder.kt @@ -18,6 +18,7 @@ import android.content.Context import android.database.Cursor import android.net.Uri import aws.sdk.kotlin.services.pinpoint.PinpointClient +import aws.sdk.kotlin.services.pinpoint.model.ChannelType import aws.sdk.kotlin.services.pinpoint.model.EndpointDemographic import aws.sdk.kotlin.services.pinpoint.model.EndpointItemResponse import aws.sdk.kotlin.services.pinpoint.model.EndpointLocation @@ -286,12 +287,10 @@ class EventRecorder( demographic = endpointDemographic effectiveDate = endpointProfile.effectiveDate.millisToIsoDate() - if (endpointProfile.address != "" && endpointProfile.channelType != null) { + if (endpointProfile.address != "" && endpointProfile.channelType == ChannelType.Gcm) { optOut = "NONE" // no opt out, send notifications address = endpointProfile.address channelType = endpointProfile.channelType - } else { - optOut = "ALL" // opt out from all notifications } attributes = endpointProfile.allAttributes diff --git a/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/TargetingClient.kt b/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/TargetingClient.kt index 5e1143998d..ae86d33932 100644 --- a/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/TargetingClient.kt +++ b/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/TargetingClient.kt @@ -17,6 +17,7 @@ package com.amplifyframework.pinpoint.core import android.content.Context import android.content.SharedPreferences import aws.sdk.kotlin.services.pinpoint.PinpointClient +import aws.sdk.kotlin.services.pinpoint.model.ChannelType import aws.sdk.kotlin.services.pinpoint.model.EndpointDemographic import aws.sdk.kotlin.services.pinpoint.model.EndpointLocation import aws.sdk.kotlin.services.pinpoint.model.EndpointRequest @@ -31,6 +32,7 @@ import com.amplifyframework.analytics.UserProfile import com.amplifyframework.annotations.InternalAmplifyApi import com.amplifyframework.core.Amplify import com.amplifyframework.core.category.CategoryType +import com.amplifyframework.core.store.KeyValueRepository import com.amplifyframework.pinpoint.core.data.AndroidAppDetails import com.amplifyframework.pinpoint.core.data.AndroidDeviceDetails import com.amplifyframework.pinpoint.core.endpointProfile.EndpointProfile @@ -52,12 +54,13 @@ import org.json.JSONObject class TargetingClient( context: Context, private val pinpointClient: PinpointClient, + store: KeyValueRepository, private val prefs: SharedPreferences, appDetails: AndroidAppDetails, deviceDetails: AndroidDeviceDetails, - coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default + coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default, ) { - private val endpointProfile = EndpointProfile(prefs.getUniqueId(), appDetails, deviceDetails, context) + private val endpointProfile = EndpointProfile(prefs.getUniqueId(), appDetails, deviceDetails, context, store) private val globalAttributes: MutableMap> private val globalMetrics: MutableMap private val coroutineScope = CoroutineScope(coroutineDispatcher) @@ -211,12 +214,10 @@ class TargetingClient( this.location = location this.demographic = demographic effectiveDate = endpointProfile.effectiveDate.millisToIsoDate() - if (endpointProfile.address != "" && endpointProfile.channelType != null) { + if (endpointProfile.address != "" && endpointProfile.channelType == ChannelType.Gcm) { optOut = "NONE" // no opt out, send notifications address = endpointProfile.address channelType = endpointProfile.channelType - } else { - optOut = "ALL" // opt out from all notifications } attributes = endpointProfile.allAttributes @@ -371,6 +372,9 @@ class TargetingClient( } companion object { + @InternalAmplifyApi + const val AWS_PINPOINT_PUSHNOTIFICATIONS_DEVICE_TOKEN_KEY = "FCMDeviceToken" + private val LOG = Amplify.Logging.logger(CategoryType.ANALYTICS, "amplify:aws-analytics-pinpoint") private const val CUSTOM_ATTRIBUTES_KEY = "ENDPOINT_PROFILE_CUSTOM_ATTRIBUTES" private const val CUSTOM_METRICS_KEY = "ENDPOINT_PROFILE_CUSTOM_METRICS" diff --git a/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/endpointProfile/EndpointProfile.kt b/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/endpointProfile/EndpointProfile.kt index f2782dbf81..9799d5daa5 100644 --- a/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/endpointProfile/EndpointProfile.kt +++ b/aws-pinpoint-core/src/main/java/com/amplifyframework/pinpoint/core/endpointProfile/EndpointProfile.kt @@ -20,6 +20,8 @@ import aws.sdk.kotlin.services.pinpoint.model.ChannelType import com.amplifyframework.annotations.InternalAmplifyApi import com.amplifyframework.core.Amplify import com.amplifyframework.core.category.CategoryType +import com.amplifyframework.core.store.KeyValueRepository +import com.amplifyframework.pinpoint.core.TargetingClient import com.amplifyframework.pinpoint.core.data.AndroidAppDetails import com.amplifyframework.pinpoint.core.data.AndroidDeviceDetails import com.amplifyframework.pinpoint.core.util.millisToIsoDate @@ -39,14 +41,17 @@ class EndpointProfile( uniqueId: String, appDetails: AndroidAppDetails, deviceDetails: AndroidDeviceDetails, - applicationContext: Context + applicationContext: Context, + private val store: KeyValueRepository ) { private val attributes: MutableMap> = ConcurrentHashMap() private val metrics: MutableMap = ConcurrentHashMap() private val currentNumOfAttributesAndMetrics = AtomicInteger(0) var channelType: ChannelType? = null - var address: String = "" + val address: String get() { + return store.get(TargetingClient.AWS_PINPOINT_PUSHNOTIFICATIONS_DEVICE_TOKEN_KEY) ?: "" + } private val country: String = try { applicationContext.resources.configuration.locales[0].isO3Country diff --git a/aws-pinpoint-core/src/test/java/com/amplifyframework/pinpoint/core/ConstructTargetingClasses.kt b/aws-pinpoint-core/src/test/java/com/amplifyframework/pinpoint/core/ConstructTargetingClasses.kt index a54670933d..4f9d3ae0fb 100644 --- a/aws-pinpoint-core/src/test/java/com/amplifyframework/pinpoint/core/ConstructTargetingClasses.kt +++ b/aws-pinpoint-core/src/test/java/com/amplifyframework/pinpoint/core/ConstructTargetingClasses.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.SharedPreferences import androidx.test.core.app.ApplicationProvider import aws.sdk.kotlin.services.pinpoint.PinpointClient +import com.amplifyframework.core.store.KeyValueRepository import com.amplifyframework.pinpoint.core.data.AndroidAppDetails import com.amplifyframework.pinpoint.core.data.AndroidDeviceDetails import com.amplifyframework.pinpoint.core.endpointProfile.EndpointProfile @@ -48,6 +49,7 @@ internal val country = "en_US" internal val effectiveDate = 0L internal val preferences = mockk() +internal val store = mockk() internal val appDetails = AndroidAppDetails(appID, appTitle, packageName, versionCode, versionName) internal val deviceDetails = AndroidDeviceDetails(carrier = carrier, locale = locale) internal val applicationContext = mockk() @@ -55,6 +57,7 @@ internal val applicationContext = mockk() internal fun setup() { mockkStatic("com.amplifyframework.pinpoint.core.util.SharedPreferencesUtilKt") every { preferences.getUniqueId() }.returns(uniqueID) + every { store.get(TargetingClient.AWS_PINPOINT_PUSHNOTIFICATIONS_DEVICE_TOKEN_KEY) } returns "" every { applicationContext.resources.configuration.locales[0].isO3Country } .returns(country) } @@ -65,7 +68,8 @@ internal fun constructEndpointProfile(): EndpointProfile { preferences.getUniqueId(), appDetails, deviceDetails, - applicationContext + applicationContext, + store ) endpointProfile.effectiveDate = effectiveDate return endpointProfile @@ -83,8 +87,9 @@ internal fun constructTargetingClient(): TargetingClient { return TargetingClient( applicationContext, pinpointClient, + store, prefs, appDetails, - deviceDetails + deviceDetails, ) } diff --git a/aws-pinpoint-core/src/test/java/com/amplifyframework/pinpoint/core/TargetingClientTest.kt b/aws-pinpoint-core/src/test/java/com/amplifyframework/pinpoint/core/TargetingClientTest.kt index 18cde9f160..0c48e88a88 100644 --- a/aws-pinpoint-core/src/test/java/com/amplifyframework/pinpoint/core/TargetingClientTest.kt +++ b/aws-pinpoint-core/src/test/java/com/amplifyframework/pinpoint/core/TargetingClientTest.kt @@ -18,11 +18,13 @@ package com.amplifyframework.pinpoint.core import android.os.Build import aws.sdk.kotlin.services.pinpoint.PinpointClient +import aws.sdk.kotlin.services.pinpoint.model.ChannelType import aws.sdk.kotlin.services.pinpoint.model.EndpointRequest import aws.sdk.kotlin.services.pinpoint.model.UpdateEndpointRequest import aws.sdk.kotlin.services.pinpoint.model.UpdateEndpointResponse import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -47,21 +49,63 @@ class TargetingClientTest { } @Test - fun testCurrentEndpoint() { - targetingClient.addAttribute("attribute", listOf("a", "b", "c")) - targetingClient.addMetric("metric", 2.0) - val endpoint = targetingClient.currentEndpoint() - assertEquals(endpoint.getAttribute("attribute"), listOf("a", "b", "c")) - assertEquals(endpoint.getMetric("metric"), 2.0) + fun testUpdateEndpointProfile() = runTest { + setup() + targetingClient = constructTargetingClient() + + val expectedToken = "token123" + every { store.get(TargetingClient.AWS_PINPOINT_PUSHNOTIFICATIONS_DEVICE_TOKEN_KEY) } returns expectedToken + + val updateEndpointResponse = UpdateEndpointResponse.invoke {} + coEvery { pinpointClient.updateEndpoint(ofType(UpdateEndpointRequest::class)) }.returns(updateEndpointResponse) + targetingClient.updateEndpointProfile() + + coVerify { + pinpointClient.updateEndpoint( + coWithArg { + assertNotNull(it.endpointRequest) + val request: EndpointRequest = it.endpointRequest!! + assertEquals("app id", it.applicationId) + assertEquals(expectedToken, request.address) + } + ) + } } @Test - fun testUpdateEndpointProfile() = runTest { + fun testUpdateEndpointProfileOptsIn() = runTest { + setup() + targetingClient = constructTargetingClient() + targetingClient.currentEndpoint().channelType = ChannelType.Gcm + + val expectedToken = "token123" + every { store.get(TargetingClient.AWS_PINPOINT_PUSHNOTIFICATIONS_DEVICE_TOKEN_KEY) } returns expectedToken + + val updateEndpointResponse = UpdateEndpointResponse.invoke {} + coEvery { pinpointClient.updateEndpoint(ofType(UpdateEndpointRequest::class)) }.returns(updateEndpointResponse) + targetingClient.updateEndpointProfile() + + coVerify { + pinpointClient.updateEndpoint( + coWithArg { + assertNotNull(it.endpointRequest) + val request: EndpointRequest = it.endpointRequest!! + assertEquals("app id", it.applicationId) + assertEquals(expectedToken, request.address) + assertEquals("NONE", request.optOut) + } + ) + } + } + + @Test + fun testUpdateEndpointProfileOptOutNotTouched() = runTest { setup() targetingClient = constructTargetingClient() + targetingClient.currentEndpoint().channelType = null - targetingClient.addAttribute("attribute", listOf("a1", "a2")) - targetingClient.addMetric("metric", 1.0) + val expectedToken = "" + every { store.get(TargetingClient.AWS_PINPOINT_PUSHNOTIFICATIONS_DEVICE_TOKEN_KEY) } returns expectedToken val updateEndpointResponse = UpdateEndpointResponse.invoke {} coEvery { pinpointClient.updateEndpoint(ofType(UpdateEndpointRequest::class)) }.returns(updateEndpointResponse) @@ -69,12 +113,11 @@ class TargetingClientTest { coVerify { pinpointClient.updateEndpoint( - coWithArg { + coWithArg { assertNotNull(it.endpointRequest) val request: EndpointRequest = it.endpointRequest!! assertEquals("app id", it.applicationId) - assertEquals(listOf("a1", "a2"), request.attributes?.get("attribute") ?: listOf("wrong")) - assertEquals(1.0, request.metrics?.get("metric") ?: -1.0, 0.01) + assertEquals(expectedToken, request.address) } ) } diff --git a/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt b/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt index a907986499..cdd7a60d5c 100644 --- a/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt +++ b/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt @@ -57,7 +57,6 @@ class AWSPinpointPushNotificationsPlugin : PushNotificationsPlugin) { try { - store.put(AWS_PINPOINT_PUSHNOTIFICATIONS_DEVICE_TOKEN_KEY, token) + store.put(TargetingClient.AWS_PINPOINT_PUSHNOTIFICATIONS_DEVICE_TOKEN_KEY, token) // targetingClient needs to send the address, optOut etc. to Pinpoint so we can receive campaigns/journeys val endpointProfile = targetingClient.currentEndpoint().apply { channelType = ChannelType.Gcm - address = token } targetingClient.updateEndpointProfile(endpointProfile)