From 49868eb84d8d7bb5d8100edfb91c6f716504c72c Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Mon, 15 May 2023 17:06:21 -0700 Subject: [PATCH 01/20] Fix edge case that may cause a stale appId override --- .../configuration/ConfigurationExtension.kt | 34 ++++++++++++++++++- .../ConfigurationExtensionTest.kt | 11 ++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt b/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt index c9013d04f..1ddfea2b7 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt @@ -50,6 +50,8 @@ internal class ConfigurationExtension : Extension { internal const val CONFIGURATION_REQUEST_CONTENT_RETRIEVE_CONFIG = "config.getData" internal const val CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT = "config.isinternalevent" + internal const val CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT = + "config.isinitalloadevent" internal const val DATASTORE_KEY = "AdobeMobile_ConfigState" internal const val RULES_CONFIG_URL = "rules.url" internal const val CONFIG_DOWNLOAD_RETRY_ATTEMPT_DELAY_MS = 5000L @@ -131,7 +133,8 @@ internal class ConfigurationExtension : Extension { val eventData: MutableMap = mutableMapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to appId, - CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, + CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to true ) dispatchConfigurationRequest(eventData) } @@ -249,6 +252,13 @@ internal class ConfigurationExtension : Extension { return } + // Check if this is an initial request + if (isStaleAppIdUpdateRequest(appId, event)) { + Log.trace(TAG, TAG, "An explicit configure with AppId request has preceded this internal event.") + sharedStateResolver?.resolve(configurationStateManager.environmentAwareConfiguration) + return + } + // Stop all event processing for the extension until new configuration download is attempted api.stopEvents() @@ -540,4 +550,26 @@ internal class ConfigurationExtension : Extension { } } } + + private fun isStaleAppIdUpdateRequest(newAppId: String, configureWithAppIdEvent: Event): Boolean { + val isInternalEvent = DataReader.optBoolean( + configureWithAppIdEvent.eventData, + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT, + false + ) + val isInitialEvent = DataReader.optBoolean( + configureWithAppIdEvent.eventData, + CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT, + false + ) + + // Because events are dispatched and processed serially, external config with app id events + // cannot be stale + if (!isInitialEvent || !isInternalEvent) return false + + // Load the currently persisted app id for validation + val currentAppId = appIdManager.loadAppId() + + return !currentAppId.isNullOrBlank() && newAppId != currentAppId + } } diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTest.kt index 0c7d3d55c..d9b9ca559 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTest.kt @@ -18,6 +18,8 @@ import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.ExtensionHelper import com.adobe.marketing.mobile.SharedStateResolver import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG +import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT +import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH @@ -141,7 +143,8 @@ class ConfigurationExtensionTest { EventSource.REQUEST_CONTENT, mapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to "SampleAppID", - ConfigurationExtension.CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, + CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to true ), null ) @@ -234,7 +237,8 @@ class ConfigurationExtensionTest { EventSource.REQUEST_CONTENT, mapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to "SampleAppID", - "config.isinternalevent" to true + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, + CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to true ), null ) @@ -443,7 +447,8 @@ class ConfigurationExtensionTest { EventSource.REQUEST_CONTENT, mapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to "SampleAppID", - "config.isinternalevent" to true + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, + CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to true ), null ) From d92c08ca7b44b32126e0c38ad895cf42aa4a83f5 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Mon, 12 Jun 2023 16:14:49 -0700 Subject: [PATCH 02/20] Add tests and fix docs --- .../configuration/ConfigurationExtension.kt | 9 ++++ .../com/adobe/marketing/mobile/SDKHelper.kt | 5 ++- .../core/ConfigurationIntegrationTests.kt | 42 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt b/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt index 992187123..9c2127b1b 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt @@ -553,6 +553,15 @@ internal class ConfigurationExtension : Extension { } } + /** + * Determines if the current AppID update request is stale. + * A request is considered stale if it is an initial configuration request sent internally + * and there is a newer request that has been sent externally via {@link MobileCore#configureWithAppId(String)} + * + * @param newAppId the new app ID with which the configuration update is being requested + * @param isInitialLoadEvent whether the current request is an initial configuration request + * @return true if the current request is stale, false otherwise + */ private fun isStaleAppIdUpdateRequest(newAppId: String, isInitialLoadEvent: Boolean): Boolean { // Because events are dispatched and processed serially, external config with app id events // cannot be stale. However, checking if this is an internal event is not sufficient due diff --git a/code/integration-tests/src/androidTest/java/com/adobe/marketing/mobile/SDKHelper.kt b/code/integration-tests/src/androidTest/java/com/adobe/marketing/mobile/SDKHelper.kt index e3683a308..d4652f36a 100644 --- a/code/integration-tests/src/androidTest/java/com/adobe/marketing/mobile/SDKHelper.kt +++ b/code/integration-tests/src/androidTest/java/com/adobe/marketing/mobile/SDKHelper.kt @@ -40,7 +40,7 @@ object SDKHelper { private const val LIFECYCLE_DATA_STORE = "AdobeMobile_Lifecycle" - private fun setupNetworkService( + fun setupNetworkService( configURL: String, mockConfigResponse: Map, rulesURL: String?, @@ -72,6 +72,9 @@ object SDKHelper { HttpURLConnection.HTTP_OK, "OK", emptyMap(), rulesStream, urlMonitor ) } + else -> { + connection = MockNetworkResponse(HttpURLConnection.HTTP_NOT_FOUND, "NOT FOUND", emptyMap(), "".byteInputStream(), urlMonitor) + } } if (callback != null && connection != null) { diff --git a/code/integration-tests/src/androidTest/java/com/adobe/marketing/mobile/integration/core/ConfigurationIntegrationTests.kt b/code/integration-tests/src/androidTest/java/com/adobe/marketing/mobile/integration/core/ConfigurationIntegrationTests.kt index 25c272beb..393fcf309 100644 --- a/code/integration-tests/src/androidTest/java/com/adobe/marketing/mobile/integration/core/ConfigurationIntegrationTests.kt +++ b/code/integration-tests/src/androidTest/java/com/adobe/marketing/mobile/integration/core/ConfigurationIntegrationTests.kt @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.integration.core +import android.content.Context +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.adobe.marketing.mobile.MobileCore import com.adobe.marketing.mobile.MobilePrivacyStatus @@ -28,6 +30,7 @@ import org.junit.runner.RunWith class ConfigurationIntegrationTests { companion object { const val TEST_APP_ID = "appId" + const val CONFIGURATION_STATE_PREF = "AdobeMobile_ConfigState" const val TEST_RULES_RESOURCE = "rules_configuration_tests.zip" const val WAIT_TIME_MILLIS = 5000L } @@ -112,6 +115,45 @@ class ConfigurationIntegrationTests { validatePrivacyStatus(MobilePrivacyStatus.OPT_IN) } + @Test + fun testConfigurationWithAppIDIsNotOverwrittenByCache() { + SDKHelper.setupConfiguration(TEST_APP_ID, emptyMap(), TEST_RULES_RESOURCE) + + val context = ApplicationProvider.getApplicationContext() + // get config state + val configSharedPreference = context.getSharedPreferences(CONFIGURATION_STATE_PREF, 0) + val persistedAppId = configSharedPreference.getString("config.appID", null) + Assert.assertEquals(TEST_APP_ID, persistedAppId) + + //Simulate shut down and initialize SDK again. Ensure that the cached configuration is retained + SDKHelper.resetSDK(false) + + // === Custom initialization with new app id=== + val newAppID = "NEW_APP_ID" + val newConfigURL = "https://assets.adobedtm.com/$newAppID.json" + val newRulesURL = "https://assets.adobedtm.com/$newAppID-rules.zip" + + // Configure with new app id before registering extensions + MobileCore.configureWithAppID("NEW_APP_ID") + SDKHelper.initializeSDK(listOf(Signal.EXTENSION)) + + val newConfigUrlValidationLatch = CountDownLatch(1) + val newRulesUrlValidationLatch = CountDownLatch(1) + SDKHelper.setupNetworkService(newConfigURL, emptyMap(), newRulesURL, TEST_RULES_RESOURCE) { + when(it) { + newConfigURL -> newConfigUrlValidationLatch.countDown() + newRulesURL -> newRulesUrlValidationLatch.countDown() + } + } + newConfigUrlValidationLatch.await(WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS) + newRulesUrlValidationLatch.await(WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS) + + // Verify configuration is updated and cached appId does not overwrite the new configuration + val newConfigSharedPreference = context.getSharedPreferences(CONFIGURATION_STATE_PREF, 0) + val newPersistedAppId = newConfigSharedPreference.getString("config.appID", null) + Assert.assertEquals(newAppID, newPersistedAppId) + } + @Test fun testClearUpdatedConfiguration() { SDKHelper.setupConfiguration(TEST_APP_ID, mapOf("global.privacy" to "optedin"), TEST_RULES_RESOURCE) From d3f572ecb03160d696251f1b87902c16b5b24e95 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Mon, 19 Jun 2023 17:14:41 -0700 Subject: [PATCH 03/20] do dismiss tracking and back press tracking if a back press occurred --- .../mobile/services/ui/AEPMessage.java | 4 +- .../mobile/services/ui/AEPMessageTests.java | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java index 8b8d96444..720328d17 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java @@ -385,9 +385,9 @@ void cleanup(final boolean dismissedWithBackTouch) { if (dismissedWithBackTouch) { listener.onBackPressed(this); - } else { - listener.onDismiss(this); } + listener.onDismiss(this); + webViewFrame.setOnTouchListener(null); webView.setOnTouchListener(null); if (dismissAnimation != null) { diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/AEPMessageTests.java b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/AEPMessageTests.java index 15dcb258a..8d6a0bfdf 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/AEPMessageTests.java +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/AEPMessageTests.java @@ -686,6 +686,54 @@ public void aepMessageIsDismissed_When_MessagingDelegateSet_And_MessageDismissed .startAnimation(ArgumentMatchers.any(Animation.class)); } + @Test + public void + aepMessageIsDismissed_When_MessagingDelegateSet_And_MessageDismissedWithBackButton() { + // setup + Mockito.when(mockAEPMessageSettings.getDismissAnimation()) + .thenReturn(MessageAnimation.BOTTOM); + Mockito.when(mockMessageMonitor.isDisplayed()).thenReturn(true); + Mockito.when(mockMessageMonitor.dismiss()).thenReturn(true); + Mockito.when( + mockMessagingDelegate.shouldShowMessage( + ArgumentMatchers.any(AEPMessage.class))) + .thenReturn(true); + setupFragmentTransactionMocks(); + + try { + message = + new AEPMessage( + "html", + mockFullscreenMessageDelegate, + false, + mockMessageMonitor, + mockAEPMessageSettings, + mockExecutor); + } catch (MessageCreationException ex) { + Assert.fail(ex.getMessage()); + } + + Mockito.when(mockMessageFragment.isDismissedWithGesture()).thenReturn(false); + message.setMessageFragment(mockMessageFragment); + message.setWebView(mockWebView); + message.setWebViewFrame(mockCardView); + Mockito.when(mockViewGroup.getMeasuredWidth()).thenReturn(1000); + Mockito.when(mockViewGroup.getMeasuredHeight()).thenReturn(1000); + // test + message.dismiss(true); + message.getAnimationListener().onAnimationEnd(mockAnimation); + // verify listeners are called for a message dismiss and device back button press + Mockito.verify(mockMessageMonitor, Mockito.times(1)).dismiss(); + Mockito.verify(mockMessagingDelegate, Mockito.times(1)) + .onDismiss(any(FullscreenMessage.class)); + Mockito.verify(mockFullscreenMessageDelegate, Mockito.times(1)) + .onBackPressed(any(FullscreenMessage.class)); + Mockito.verify(mockFullscreenMessageDelegate, Mockito.times(1)) + .onDismiss(any(FullscreenMessage.class)); + Mockito.verify(mockCardView, Mockito.times(1)) + .startAnimation(ArgumentMatchers.any(Animation.class)); + } + // mock fragment setup helper void setupFragmentTransactionMocks() { Mockito.when(mockActivity.getFragmentManager()).thenReturn(mockFragmentManager); From 469a49e35c6ddc526f0ae91b20f9841b692a43bd Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 20 Jun 2023 10:18:40 -0700 Subject: [PATCH 04/20] Use internal flag and persisted id for disambiguation --- .../internal/configuration/AppIdManager.kt | 2 +- .../configuration/ConfigurationExtension.kt | 41 +++++++------------ .../ConfigurationExtensionTests.kt | 10 ++--- 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/AppIdManager.kt b/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/AppIdManager.kt index 430151512..54fe1cf1e 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/AppIdManager.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/AppIdManager.kt @@ -84,7 +84,7 @@ internal class AppIdManager { * @return the existing appId stored in shared preferences if it exists, * null otherwise. */ - private fun getAppIDFromPersistence(): String? { + internal fun getAppIDFromPersistence(): String? { return configStateStoreCollection?.getString( ConfigurationStateManager.PERSISTED_APPID, null diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt b/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt index 9c2127b1b..dc7e34f58 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtension.kt @@ -50,8 +50,6 @@ internal class ConfigurationExtension : Extension { internal const val CONFIGURATION_REQUEST_CONTENT_RETRIEVE_CONFIG = "config.getData" internal const val CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT = "config.isinternalevent" - internal const val CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT = - "config.isinitalloadevent" internal const val DATASTORE_KEY = "AdobeMobile_ConfigState" internal const val RULES_CONFIG_URL = "rules.url" internal const val CONFIG_DOWNLOAD_RETRY_ATTEMPT_DELAY_MS = 5000L @@ -133,8 +131,7 @@ internal class ConfigurationExtension : Extension { val eventData: MutableMap = mutableMapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to appId, - CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, - CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to true + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true ) dispatchConfigurationRequest(eventData) } @@ -252,9 +249,9 @@ internal class ConfigurationExtension : Extension { return } - // Check if this is an initial request - val isInitialEvent = DataReader.optBoolean(event.eventData, CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT, false) - if (isStaleAppIdUpdateRequest(appId, isInitialEvent)) { + // Check if this is an internal request ovewriting explicit configure with appId request. + val isInternalEvent = DataReader.optBoolean(event.eventData, CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT, false) + if (isStaleAppIdUpdateRequest(appId, isInternalEvent)) { Log.trace(TAG, TAG, "An explicit configure with AppId request has preceded this internal event.") sharedStateResolver?.resolve(configurationStateManager.environmentAwareConfiguration) return @@ -277,7 +274,7 @@ internal class ConfigurationExtension : Extension { // If the configuration download fails, publish current configuration and retry download again. sharedStateResolver?.resolve(configurationStateManager.environmentAwareConfiguration) - retryConfigTaskHandle = retryConfigDownload(appId, isInitialEvent) + retryConfigTaskHandle = retryConfigDownload(appId) } // Start event processing again @@ -434,15 +431,14 @@ internal class ConfigurationExtension : Extension { * @param appId the appId for which the config download should be attempted * @return the [Future] associated with the runnable that dispatches the configuration request event */ - private fun retryConfigDownload(appId: String, isInitialLoad: Boolean): Future<*> { + private fun retryConfigDownload(appId: String): Future<*> { val retryDelay = ++retryConfigurationCounter * CONFIG_DOWNLOAD_RETRY_ATTEMPT_DELAY_MS return retryWorker.schedule( { dispatchConfigurationRequest( mutableMapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to appId, - CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, - CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to isInitialLoad + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true ) ) }, @@ -555,30 +551,21 @@ internal class ConfigurationExtension : Extension { /** * Determines if the current AppID update request is stale. - * A request is considered stale if it is an initial configuration request sent internally + * A request is considered stale if it is a configuration request sent internally * and there is a newer request that has been sent externally via {@link MobileCore#configureWithAppId(String)} * * @param newAppId the new app ID with which the configuration update is being requested - * @param isInitialLoadEvent whether the current request is an initial configuration request + * @param isInternalEvent whether the current request is an initial configuration request * @return true if the current request is stale, false otherwise */ - private fun isStaleAppIdUpdateRequest(newAppId: String, isInitialLoadEvent: Boolean): Boolean { + private fun isStaleAppIdUpdateRequest(newAppId: String, isInternalEvent: Boolean): Boolean { // Because events are dispatched and processed serially, external config with app id events - // cannot be stale. However, checking if this is an internal event is not sufficient due - // to the following scenario: - // - An implementer invokes configureWithAppId before the registerExtensions() callback, - // and the download fails, retryConfigDownload() will schedule the dispatch a new internal - // event. - // - Just during that time, an internal configureWithAppId event with a cached id may be - // processed due to Configuration extension initialization. - // - Now, there is no way to disambiguate between the retry and the incorrect override with - // cache. - // Checking the flag for whether this is an initial load event will allow disambiguation. - if (!isInitialLoadEvent) return false + // cannot be stale. + if (!isInternalEvent) return false // Load the currently persisted app id for validation - val currentAppId = appIdManager.loadAppId() + val persistedAppId = appIdManager.getAppIDFromPersistence() - return !currentAppId.isNullOrBlank() && newAppId != currentAppId + return !persistedAppId.isNullOrBlank() && newAppId != persistedAppId } } diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTests.kt b/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTests.kt index 46895da25..1ef52476a 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTests.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTests.kt @@ -18,7 +18,6 @@ import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.ExtensionHelper import com.adobe.marketing.mobile.SharedStateResolver import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG -import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension.Companion.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE @@ -161,8 +160,7 @@ class ConfigurationExtensionTests { EventSource.REQUEST_CONTENT, mapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to "SampleAppID", - CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, - CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to true + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true ), null ) @@ -255,8 +253,7 @@ class ConfigurationExtensionTests { EventSource.REQUEST_CONTENT, mapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to "SampleAppID", - CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, - CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to true + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true ), null ) @@ -465,8 +462,7 @@ class ConfigurationExtensionTests { EventSource.REQUEST_CONTENT, mapOf( CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID to "SampleAppID", - CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true, - CONFIGURATION_REQUEST_CONTENT_IS_INITIAL_LOAD_EVENT to true + CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT to true ), null ) From 3cec20e3b49d5b9c1c7c938f35aa998a47baf402 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Thu, 22 Jun 2023 15:42:31 -0700 Subject: [PATCH 05/20] fix message placement and rounded corners on api 22 and below --- .../mobile/services/ui/AEPMessage.java | 16 +++++----- .../mobile/services/ui/MessageFragment.java | 15 ++-------- .../services/ui/MessageWebViewUtil.java | 22 ++++++++++++-- .../services/ui/MessageWebViewUtilTests.java | 30 +++++++++---------- 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java index 720328d17..a9da5643e 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java @@ -18,13 +18,13 @@ import android.content.Intent; import android.graphics.Color; import android.view.View; -import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.TranslateAnimation; import android.webkit.WebSettings; import android.webkit.WebView; +import android.widget.FrameLayout; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.cardview.widget.CardView; @@ -68,7 +68,7 @@ class AEPMessage implements FullscreenMessage { // private vars private WebView webView; private CardView webViewFrame; - private ViewGroup.LayoutParams params; + private FrameLayout.LayoutParams params; private final String html; private MessageSettings settings; private Animation dismissAnimation; @@ -143,20 +143,20 @@ void setWebView(final WebView webView) { } /** - * Returns the {@link ViewGroup.LayoutParams} created for this message. + * Returns the {@link FrameLayout.LayoutParams} created for this message. * - * @return the created {@code ViewGroup.LayoutParams} + * @return the created {@code FrameLayout.LayoutParams} */ - ViewGroup.LayoutParams getParams() { + FrameLayout.LayoutParams getParams() { return params; } /** - * Sets the {@link ViewGroup.LayoutParams} for this message. + * Sets the {@link FrameLayout.LayoutParams} for this message. * - * @param params the {@code ViewGroup.LayoutParams} to be set + * @param params the {@code FrameLayout.LayoutParams} to be set */ - void setParams(final ViewGroup.LayoutParams params) { + void setParams(final FrameLayout.LayoutParams params) { this.params = params; } diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageFragment.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageFragment.java index 7adb50d47..a29bc8b06 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageFragment.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageFragment.java @@ -17,15 +17,14 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; import android.os.Bundle; -import android.util.TypedValue; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; +import android.widget.FrameLayout; import androidx.annotation.VisibleForTesting; import androidx.cardview.widget.CardView; import androidx.fragment.app.DialogFragment; @@ -381,7 +380,7 @@ private void applyBackdropColor() { /** Add the IAM WebView as the {@link MessageFragment} Dialog's content view. */ private void updateDialogView() { final Dialog dialog = getDialog(); - final ViewGroup.LayoutParams params = message.getParams(); + final FrameLayout.LayoutParams params = message.getParams(); final CardView webViewFrame = message.getWebViewFrame(); if (dialog == null || webViewFrame == null || params == null) { @@ -393,16 +392,6 @@ private void updateDialogView() { return; } - // use a gradient drawable to set the rounded corners on the message - final GradientDrawable roundedDrawable = new GradientDrawable(); - final float calculatedRadius = - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - message.getMessageSettings().getCornerRadius(), - dialog.getContext().getResources().getDisplayMetrics()); - roundedDrawable.setCornerRadius(calculatedRadius); - webViewFrame.setBackground(roundedDrawable); - dialog.setContentView(webViewFrame, params); webViewFrame.setOnTouchListener(this); } diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java index 637c77566..37d408ad4 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java @@ -13,6 +13,9 @@ import android.content.Context; import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.util.TypedValue; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.AlphaAnimation; @@ -21,6 +24,7 @@ import android.view.animation.TranslateAnimation; import android.webkit.WebSettings; import android.webkit.WebView; +import android.widget.FrameLayout; import androidx.cardview.widget.CardView; import com.adobe.marketing.mobile.services.Log; import com.adobe.marketing.mobile.services.ServiceConstants; @@ -152,6 +156,21 @@ void show(final AEPMessage message) { webviewSettings.setUseWideViewPort(true); } + // use a gradient drawable to set the rounded corners on the message + final GradientDrawable roundedDrawable = new GradientDrawable(); + final float calculatedRadius = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + message.getMessageSettings().getCornerRadius(), + context.getResources().getDisplayMetrics()); + roundedDrawable.setCornerRadius(calculatedRadius); + webViewFrame.setBackground(roundedDrawable); + + // if API < 22 then set webview alpha to 99% to fix rounded corners on messages + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { + webView.setAlpha(0.99f); + } + webViewFrame.addView(webView); // add the created cardview containing the webview to the message object @@ -233,8 +252,7 @@ private Animation setupDisplayAnimation(final AEPMessage message, final WebView * @param message {@link AEPMessage} containing the in-app message payload */ private void setMessageLayoutParameters(final AEPMessage message) { - ViewGroup.MarginLayoutParams params = - new ViewGroup.MarginLayoutParams(messageWidth, messageHeight); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(messageWidth, messageHeight); params.topMargin = originY; params.leftMargin = originX; message.setParams(params); diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtilTests.java b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtilTests.java index f788f2dc2..6c831fcf3 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtilTests.java +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtilTests.java @@ -18,9 +18,9 @@ import android.content.Context; import android.content.res.Resources; import android.util.DisplayMetrics; -import android.view.ViewGroup; import android.webkit.WebSettings; import android.webkit.WebView; +import android.widget.FrameLayout; import androidx.cardview.widget.CardView; import com.adobe.marketing.mobile.services.AppContextService; import com.adobe.marketing.mobile.services.ServiceProviderModifier; @@ -107,7 +107,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -128,7 +128,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -149,7 +149,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -170,7 +170,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -191,7 +191,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -212,7 +212,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -233,7 +233,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -254,7 +254,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -275,7 +275,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -296,7 +296,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -317,7 +317,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -338,7 +338,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -357,7 +357,7 @@ public void testRunnable_WithValidAEPMessage_ThenWebviewLoadDataCalled() { Mockito.verify(mockWebview, Mockito.times(1)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(1)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } @@ -376,7 +376,7 @@ public void testRunnable_WithInvalidAEPMessage_ThenWebviewLoadDataNotCalled() { Mockito.verify(mockWebview, Mockito.times(0)) .setOnTouchListener(any(MessageFragment.class)); Mockito.verify(mockAEPMessage, Mockito.times(0)) - .setParams(any(ViewGroup.MarginLayoutParams.class)); + .setParams(any(FrameLayout.LayoutParams.class)); } } } From e9492b5db92aa14fece9499c7bc92add98613f27 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Thu, 22 Jun 2023 15:44:00 -0700 Subject: [PATCH 06/20] fix style error --- .../adobe/marketing/mobile/services/ui/MessageWebViewUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java index 37d408ad4..1ce336ec0 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java @@ -42,6 +42,7 @@ class MessageWebViewUtil { private static final String UNEXPECTED_NULL_VALUE = "Unexpected Null Value"; private static final int FULLSCREEN_PERCENTAGE = 100; private static final int ANIMATION_DURATION = 300; + private static final float WORKAROUND_ALPHA_VALUE = 0.99f; private static final String BASE_URL = "file:///android_asset/"; private static final String MIME_TYPE = "text/html"; @@ -168,7 +169,7 @@ void show(final AEPMessage message) { // if API < 22 then set webview alpha to 99% to fix rounded corners on messages if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { - webView.setAlpha(0.99f); + webView.setAlpha(WORKAROUND_ALPHA_VALUE); } webViewFrame.addView(webView); From 3fb7de27ce0d8099185abef9657a4384a28d7e0a Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Tue, 27 Jun 2023 13:58:00 -0700 Subject: [PATCH 07/20] check if objects used for displaying an iam are null before cleaning them --- .../marketing/mobile/services/ui/AEPMessage.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java index 8b8d96444..acd0de5a5 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java @@ -295,7 +295,9 @@ public void onAnimationEnd(final Animation animation) { public void onAnimationRepeat(final Animation animation) {} }; dismissAnimation.setAnimationListener(animationListener); - webViewFrame.startAnimation(dismissAnimation); + if (webViewFrame != null) { + webViewFrame.startAnimation(dismissAnimation); + } return; } @@ -388,8 +390,15 @@ void cleanup(final boolean dismissedWithBackTouch) { } else { listener.onDismiss(this); } - webViewFrame.setOnTouchListener(null); - webView.setOnTouchListener(null); + + if (webViewFrame != null) { + webViewFrame.setOnTouchListener(null); + } + + if (webView != null) { + webView.setOnTouchListener(null); + } + if (dismissAnimation != null) { dismissAnimation.setAnimationListener(null); dismissAnimation = null; From c84aa8662a268513f99170bfcae725f1d50cc6ca Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Tue, 27 Jun 2023 16:14:51 -0700 Subject: [PATCH 08/20] restore inadvertent removal of listener.onDismiss --- .../java/com/adobe/marketing/mobile/services/ui/AEPMessage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java index b30268015..0e45a4ad6 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java @@ -388,6 +388,7 @@ void cleanup(final boolean dismissedWithBackTouch) { if (dismissedWithBackTouch) { listener.onBackPressed(this); } + listener.onDismiss(this); if (webViewFrame != null) { webViewFrame.setOnTouchListener(null); From a68ae4051d6ed7c50e1338b1ea1f66570622794f Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Wed, 28 Jun 2023 08:58:20 -0700 Subject: [PATCH 09/20] ensure views are cleaned even if the dismiss animation is not started --- .../com/adobe/marketing/mobile/services/ui/AEPMessage.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java index 0e45a4ad6..0a0d6b772 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java @@ -295,9 +295,10 @@ public void onAnimationEnd(final Animation animation) { public void onAnimationRepeat(final Animation animation) {} }; dismissAnimation.setAnimationListener(animationListener); - if (webViewFrame != null) { - webViewFrame.startAnimation(dismissAnimation); + if (webViewFrame == null) { + cleanup(dismissedWithBackTouch); } + webViewFrame.startAnimation(dismissAnimation); return; } From f3dec12f151da9568066d4573e27305f290f37fc Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Wed, 28 Jun 2023 12:24:48 -0700 Subject: [PATCH 10/20] fix double back press event, cleanup dismiss --- .../mobile/services/ui/AEPMessage.java | 46 +++++++++---------- .../mobile/services/ui/MessageFragment.java | 4 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java index 0a0d6b772..3fa42a8b9 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java @@ -277,33 +277,31 @@ public void dismiss(final boolean dismissedWithBackTouch) { if (!messagesMonitor.dismiss()) { return; } - // add a dismiss animation if the webview wasn't previously removed via a swipe gesture - if (!messageFragment.isDismissedWithGesture()) { - dismissAnimation = setupDismissAnimation(); - animationListener = - new Animation.AnimationListener() { - @Override - public void onAnimationStart(final Animation animation) {} - - @Override - public void onAnimationEnd(final Animation animation) { - // wait for the animation to end then clean the views - cleanup(dismissedWithBackTouch); - } - - @Override - public void onAnimationRepeat(final Animation animation) {} - }; - dismissAnimation.setAnimationListener(animationListener); - if (webViewFrame == null) { - cleanup(dismissedWithBackTouch); - } - webViewFrame.startAnimation(dismissAnimation); + + if (messageFragment.isDismissedWithGesture() || webViewFrame == null) { + // just clean the views + cleanup(dismissedWithBackTouch); return; } - // otherwise, just clean the views - cleanup(dismissedWithBackTouch); + // add a dismiss animation if the webview wasn't previously removed via a swipe gesture + dismissAnimation = setupDismissAnimation(); + animationListener = + new Animation.AnimationListener() { + @Override + public void onAnimationStart(final Animation animation) {} + + @Override + public void onAnimationEnd(final Animation animation) { + // wait for the animation to end then clean the views + cleanup(dismissedWithBackTouch); + } + + @Override + public void onAnimationRepeat(final Animation animation) {} + }; + dismissAnimation.setAnimationListener(animationListener); + webViewFrame.startAnimation(dismissAnimation); } /** diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageFragment.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageFragment.java index a29bc8b06..0a5abeee4 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageFragment.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageFragment.java @@ -321,7 +321,9 @@ private void addListeners() { // handle on back pressed to dismiss the message dialog.setOnKeyListener( (dialogInterface, keyCode, event) -> { - if (message != null && keyCode == KeyEvent.KEYCODE_BACK) { + if (message != null + && keyCode == KeyEvent.KEYCODE_BACK + && event.getAction() == KeyEvent.ACTION_UP) { message.dismiss(true); } return false; From 4bf8b0fece4b09c8cbd2c5ab2342bfb13ef57ab3 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Tue, 11 Jul 2023 13:05:24 -0700 Subject: [PATCH 11/20] remove version check when setting the webview alpha setting the webview alpha to 99% does not cause any visual issues when testing on API 23 and above. --- .../marketing/mobile/services/ui/MessageWebViewUtil.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java index 1ce336ec0..6a8b77867 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java @@ -167,14 +167,11 @@ void show(final AEPMessage message) { roundedDrawable.setCornerRadius(calculatedRadius); webViewFrame.setBackground(roundedDrawable); - // if API < 22 then set webview alpha to 99% to fix rounded corners on messages - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { - webView.setAlpha(WORKAROUND_ALPHA_VALUE); - } - - webViewFrame.addView(webView); + // set webview alpha to 99% to allow rounded corners to be applied on messages on API22 and below + webView.setAlpha(WORKAROUND_ALPHA_VALUE); // add the created cardview containing the webview to the message object + webViewFrame.addView(webView); message.setWebViewFrame(webViewFrame); setMessageLayoutParameters(message); From 351b408fce6f6c6d89c0c2f9603062813e1348b8 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Tue, 11 Jul 2023 13:22:23 -0700 Subject: [PATCH 12/20] fix code formatting, remove unused import --- .../marketing/mobile/services/ui/MessageWebViewUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java index 6a8b77867..7f0849a18 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewUtil.java @@ -14,7 +14,6 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; -import android.os.Build; import android.util.TypedValue; import android.view.ViewGroup; import android.view.ViewParent; @@ -167,7 +166,8 @@ void show(final AEPMessage message) { roundedDrawable.setCornerRadius(calculatedRadius); webViewFrame.setBackground(roundedDrawable); - // set webview alpha to 99% to allow rounded corners to be applied on messages on API22 and below + // set webview alpha to 99% to allow rounded corners to be applied on messages on API22 + // and below webView.setAlpha(WORKAROUND_ALPHA_VALUE); // add the created cardview containing the webview to the message object From ea5e98b9aa4a628c25df29563c2ad82ae8d27210 Mon Sep 17 00:00:00 2001 From: praveek Date: Wed, 19 Jul 2023 10:53:15 -0700 Subject: [PATCH 13/20] Update services to query system locale --- code/core/api/core.api | 1 + .../mobile/services/DeviceInfoServiceTests.kt | 6 ++++ .../mobile/services/DeviceInforming.java | 10 +++++- .../mobile/services/DeviceInfoService.java | 31 +++++++++++++++++-- .../testapp/PlatformServicesFragment.java | 1 + 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/code/core/api/core.api b/code/core/api/core.api index 5cb483abc..3983609a2 100644 --- a/code/core/api/core.api +++ b/code/core/api/core.api @@ -610,6 +610,7 @@ public abstract interface class com/adobe/marketing/mobile/services/DeviceInform public abstract fun getOperatingSystemVersion ()Ljava/lang/String; public abstract fun getPropertyFromManifest (Ljava/lang/String;)Ljava/lang/String; public abstract fun getRunMode ()Ljava/lang/String; + public abstract fun getSystemLocale ()Ljava/util/Locale; public abstract fun registerOneTimeNetworkConnectionActiveListener (Lcom/adobe/marketing/mobile/services/DeviceInforming$NetworkConnectionActiveListener;)Z } diff --git a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/DeviceInfoServiceTests.kt b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/DeviceInfoServiceTests.kt index 50a10daea..4261bbe4f 100644 --- a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/DeviceInfoServiceTests.kt +++ b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/DeviceInfoServiceTests.kt @@ -39,6 +39,7 @@ class DeviceInfoServiceTests { ServiceProviderModifier.setAppContextService(MockAppContextService()) deviceInfoService = ServiceProvider.getInstance().deviceInfoService assertNull(deviceInfoService.activeLocale) + assertTrue(deviceInfoService.systemLocale.displayLanguage.isNotEmpty()) assertNull(deviceInfoService.displayInformation) assertEquals( DeviceInforming.DeviceType.UNKNOWN, @@ -73,6 +74,11 @@ class DeviceInfoServiceTests { assertTrue(deviceInfoService.activeLocale.displayLanguage.isNotEmpty()) } + @Test + fun testGetSystemLocale() { + assertTrue(deviceInfoService.systemLocale.displayLanguage.isNotEmpty()) + } + @Test fun testGetCurrentOrientation() { assertTrue(deviceInfoService.currentOrientation >= 0) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java b/code/core/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java index d665682f0..249b240fe 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java +++ b/code/core/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java @@ -142,12 +142,20 @@ interface DisplayInformation { String getApplicationVersionCode(); /** - * Returns the currently selected / active locale value (as set by the user on the system). + * Returns the currently selected / active locale value with respect to the application context. * * @return A {@link Locale} value, if available, null otherwise */ Locale getActiveLocale(); + /** + * Returns the currently selected / active locale value on the device settings as set by the + * user. + * + * @return A {@link Locale} value, if available, null otherwise + */ + Locale getSystemLocale(); + /** * Returns information about the display hardware, as returned by the underlying OS. * diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java index 7293cc64d..3730a486a 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java @@ -45,9 +45,9 @@ class DeviceInfoService implements DeviceInforming { DeviceInfoService() {} /** - * Returns the currently selected / active locale value (as set by the user on the system). + * Returns the currently selected / active locale value with respect to the application context. * - * @return A {@link Locale} value, if available, null otherwise. + * @return A {@link Locale} value, if available, null otherwise */ public Locale getActiveLocale() { final Context context = getApplicationContext(); @@ -75,6 +75,33 @@ public Locale getActiveLocale() { } } + /** + * Returns the currently selected / active locale value on the device settings as set by the + * user. + * + * @return A {@link Locale} value, if available, null otherwise + */ + @Override + public Locale getSystemLocale() { + final Resources resources = Resources.getSystem(); + + if (resources == null) { + return null; + } + + final Configuration configuration = resources.getConfiguration(); + + if (configuration == null) { + return null; + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return configuration.locale; + } else { + return configuration.getLocales().get(0); + } + } + @Override public DisplayInformation getDisplayInformation() { final Context context = getApplicationContext(); diff --git a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java index 0ddf3728e..5a011b76b 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java @@ -57,6 +57,7 @@ public void onClick(View view) { stringBuffer.append("\ngetApplicationBaseDir() - " + deviceInforming.getApplicationBaseDir()); stringBuffer.append("\ngetApplicationCacheDir() - " + deviceInforming.getApplicationCacheDir()); stringBuffer.append("\ngetActiveLocale() - " + deviceInforming.getActiveLocale()); + stringBuffer.append("\ngetSystemLocale() - " + deviceInforming.getSystemLocale()); stringBuffer.append("\ngetCanonicalPlatformName() - " + deviceInforming.getCanonicalPlatformName()); stringBuffer.append("\ngetDefaultUserAgent() - " + deviceInforming.getDefaultUserAgent()); stringBuffer.append("\ngetDeviceBuildId() - " + deviceInforming.getDeviceBuildId()); From 5d057700f148b87e9b29b7153c3815eced28e7a8 Mon Sep 17 00:00:00 2001 From: praveek Date: Wed, 19 Jul 2023 11:01:41 -0700 Subject: [PATCH 14/20] Lifecycle updates for system locale --- .../lifecycle/LifecycleFunctionalTest.java | 10 +++- .../lifecycle/LifecycleV2FunctionalTest.java | 34 ++++++++++-- .../lifecycle/MockDeviceInfoService.java | 7 +++ .../mobile/lifecycle/LifecycleConstants.java | 1 + .../lifecycle/LifecycleMetricsBuilder.java | 6 +++ .../lifecycle/LifecycleV2MetricsBuilder.java | 4 +- .../mobile/lifecycle/XDMLanguage.java | 53 +++++++++++++++++++ .../lifecycle/XDMLifecycleApplication.java | 37 +++++++++++++ .../lifecycle/XDMLifecycleEnvironment.java | 46 +++++----------- .../mobile/lifecycle/LifecycleTestHelper.java | 1 + .../LifecycleV2MetricsBuilderTest.java | 23 +++++++- 11 files changed, 183 insertions(+), 39 deletions(-) create mode 100644 code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java diff --git a/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/LifecycleFunctionalTest.java b/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/LifecycleFunctionalTest.java index e5bf1bef6..36bd9e9a3 100644 --- a/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/LifecycleFunctionalTest.java +++ b/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/LifecycleFunctionalTest.java @@ -76,6 +76,8 @@ public class LifecycleFunctionalTest { private static final String RUN_MODE = "runmode"; private static final String SESSION_EVENT = "sessionevent"; private static final String SESSION_START_TIMESTAMP = "starttimestampmillis"; + + private static final String SYSTEM_LOCALE = "systemlocale"; private static final String UPGRADE_EVENT = "upgradeevent"; private static final String DATA_STORE_NAME = "AdobeMobile_Lifecycle"; private static final String LIFECYCLE_CONFIG_SESSION_TIMEOUT = "lifecycle.sessionTimeout"; @@ -141,7 +143,8 @@ public int getDensityDpi() { mockDeviceInfoService.operatingSystemName = "TEST_OS"; mockDeviceInfoService.operatingSystemVersion = "5.55"; mockDeviceInfoService.mobileCarrierName = "TEST_CARRIER"; - mockDeviceInfoService.activeLocale = new Locale("en", "US"); + mockDeviceInfoService.activeLocale = Locale.US; + mockDeviceInfoService.systemLocale = Locale.FRANCE; mockDeviceInfoService.runMode = "APPLICATION"; } @@ -192,6 +195,7 @@ private String getDayOfWeek(long timestampMillis) { put(LAUNCHES, "1"); put(OPERATING_SYSTEM, "TEST_OS 5.55"); put(LOCALE, "en-US"); + put(SYSTEM_LOCALE, "fr-FR"); put(DEVICE_RESOLUTION, "100x100"); put(CARRIER_NAME, "TEST_CARRIER"); put(DEVICE_NAME, "deviceName"); @@ -331,6 +335,7 @@ public void testLifecycle__When__SecondLaunch_BeforeSessionTimeout__Then__GetNoL put(PREVIOUS_OS, "TEST_OS 5.55"); put(OPERATING_SYSTEM, "TEST_OS 5.55"); put(LOCALE, "en-US"); + put(SYSTEM_LOCALE, "fr-FR"); put(DEVICE_RESOLUTION, "100x100"); put(CARRIER_NAME, "TEST_CARRIER"); put(DEVICE_NAME, "deviceName"); @@ -393,6 +398,7 @@ public void testLifecycle__When__SecondLaunch_AfterSessionTimeout__Then__GetLaun put(LAUNCHES, "2"); put(OPERATING_SYSTEM, "TEST_OS 5.55"); put(LOCALE, "en-US"); + put(SYSTEM_LOCALE, "fr-FR"); put(DEVICE_RESOLUTION, "100x100"); put(CARRIER_NAME, "TEST_CARRIER"); put(DEVICE_NAME, "deviceName"); @@ -559,6 +565,7 @@ public void testLifecycle__When__SecondLaunch_VersionNumberChanged__Then__GetUpg put(LAUNCHES, "2"); put(OPERATING_SYSTEM, "TEST_OS 5.55"); put(LOCALE, "en-US"); + put(SYSTEM_LOCALE, "fr-FR"); put(DEVICE_RESOLUTION, "100x100"); put(CARRIER_NAME, "TEST_CARRIER"); put(DEVICE_NAME, "deviceName"); @@ -798,6 +805,7 @@ public void testLifecycle__When__ThreeDaysAfterUpgrade__Then__DaysSinceLastUpgra put(LAUNCHES, "1"); put(OPERATING_SYSTEM, "TEST_OS 5.55"); put(LOCALE, "en-US"); + put(SYSTEM_LOCALE, "fr-FR"); put(DEVICE_RESOLUTION, "100x100"); put(CARRIER_NAME, "TEST_CARRIER"); put(DEVICE_NAME, "deviceName"); diff --git a/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2FunctionalTest.java b/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2FunctionalTest.java index 994d96b15..31a87d704 100644 --- a/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2FunctionalTest.java +++ b/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2FunctionalTest.java @@ -89,9 +89,13 @@ public void beforeEach() { expectedEnvironmentInfo.put("operatingSystemVersion", "5.55"); expectedEnvironmentInfo.put("operatingSystem", "TEST_OS"); expectedEnvironmentInfo.put("type", "application"); - Map localeMap = new HashMap<>(); - localeMap.put("language", "en-US"); - expectedEnvironmentInfo.put("_dc", localeMap); + expectedEnvironmentInfo.put( + "_dc", + new HashMap() { + { + put("language", "fr-FR"); + } + }); expectedDeviceInfo.put("manufacturer", "Android"); expectedDeviceInfo.put("model", "deviceName"); @@ -128,7 +132,8 @@ public int getDensityDpi() { mockDeviceInfoService.operatingSystemName = "TEST_OS"; mockDeviceInfoService.operatingSystemVersion = "5.55"; mockDeviceInfoService.mobileCarrierName = "TEST_CARRIER"; - mockDeviceInfoService.activeLocale = new Locale("en", "US"); + mockDeviceInfoService.activeLocale = Locale.US; + mockDeviceInfoService.systemLocale = Locale.FRANCE; mockDeviceInfoService.runMode = "APPLICATION"; mockDeviceInfoService.deviceManufacturer = "Android"; mockDeviceInfoService.applicationPackageName = "TEST_PACKAGE_NAME"; @@ -164,6 +169,13 @@ private void initTimestamps() { expectedApplicationInfo.put("isInstall", true); expectedApplicationInfo.put("isLaunch", true); expectedApplicationInfo.put("id", "TEST_PACKAGE_NAME"); + expectedApplicationInfo.put( + "_dc", + new HashMap() { + { + put("language", "en-US"); + } + }); Map expectedXDMData = new HashMap() { @@ -287,6 +299,13 @@ public void testLifecycleV2__When__Pause__Then__DispatchLifecycleApplicationClos expectedApplicationInfo.put("isUpgrade", true); expectedApplicationInfo.put("isLaunch", true); expectedApplicationInfo.put("id", "TEST_PACKAGE_NAME"); + expectedApplicationInfo.put( + "_dc", + new HashMap() { + { + put("language", "en-US"); + } + }); Map expectedXDMData = new HashMap() { @@ -353,6 +372,13 @@ public void testLifecycleV2__When__Pause__Then__DispatchLifecycleApplicationClos expectedApplicationInfo.put("version", "1.1 (12345)"); expectedApplicationInfo.put("isLaunch", true); expectedApplicationInfo.put("id", "TEST_PACKAGE_NAME"); + expectedApplicationInfo.put( + "_dc", + new HashMap() { + { + put("language", "en-US"); + } + }); Map expectedXDMData = new HashMap() { diff --git a/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/MockDeviceInfoService.java b/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/MockDeviceInfoService.java index 139506169..0e6ea5f29 100644 --- a/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/MockDeviceInfoService.java +++ b/code/lifecycle/src/androidTest/java/com/adobe/marketing/mobile/lifecycle/MockDeviceInfoService.java @@ -90,6 +90,13 @@ public Locale getActiveLocale() { return activeLocale; } + public Locale systemLocale = Locale.FRANCE; + + @Override + public Locale getSystemLocale() { + return systemLocale; + } + public DeviceInforming.DisplayInformation displayInformation; @Override diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleConstants.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleConstants.java index 90492ea63..1103754e7 100644 --- a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleConstants.java +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleConstants.java @@ -118,6 +118,7 @@ static final class Lifecycle { static final String RUN_MODE = "runmode"; static final String SESSION_EVENT = "sessionevent"; static final String SESSION_START_TIMESTAMP = "starttimestampmillis"; + static final String SYSTEM_LOCALE = "systemlocale"; static final String UPGRADE_EVENT = "upgradeevent"; private Lifecycle() {} diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleMetricsBuilder.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleMetricsBuilder.java index 77b70192d..21b0ea49a 100644 --- a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleMetricsBuilder.java +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleMetricsBuilder.java @@ -322,6 +322,12 @@ LifecycleMetricsBuilder addCoreData() { lifecycleData.put(LifecycleConstants.EventDataKeys.Lifecycle.LOCALE, locale); } + final String systemLocale = LifecycleUtil.formatLocale(deviceInfoService.getSystemLocale()); + if (!StringUtils.isNullOrEmpty(systemLocale)) { + lifecycleData.put( + LifecycleConstants.EventDataKeys.Lifecycle.SYSTEM_LOCALE, systemLocale); + } + final String runMode = deviceInfoService.getRunMode(); if (!StringUtils.isNullOrEmpty(runMode)) { diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2MetricsBuilder.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2MetricsBuilder.java index aaa85986c..78498b349 100644 --- a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2MetricsBuilder.java +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2MetricsBuilder.java @@ -135,6 +135,8 @@ private XDMLifecycleApplication computeAppLaunchData( xdmApplicationInfoLaunch.setName(deviceInfoService.getApplicationName()); xdmApplicationInfoLaunch.setId(deviceInfoService.getApplicationPackageName()); xdmApplicationInfoLaunch.setVersion(getAppVersion()); + xdmApplicationInfoLaunch.setLanguage( + LifecycleUtil.formatLocaleXDM(deviceInfoService.getActiveLocale())); return xdmApplicationInfoLaunch; } @@ -194,7 +196,7 @@ private XDMLifecycleEnvironment computeEnvironmentData() { xdmEnvironmentInfo.setOperatingSystem(deviceInfoService.getOperatingSystemName()); xdmEnvironmentInfo.setOperatingSystemVersion(deviceInfoService.getOperatingSystemVersion()); xdmEnvironmentInfo.setLanguage( - LifecycleUtil.formatLocaleXDM(deviceInfoService.getActiveLocale())); + LifecycleUtil.formatLocaleXDM(deviceInfoService.getSystemLocale())); return xdmEnvironmentInfo; } diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java new file mode 100644 index 000000000..0d4d5a0d4 --- /dev/null +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java @@ -0,0 +1,53 @@ +/* + Copyright 2023 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.lifecycle; + +import androidx.annotation.NonNull; +import com.adobe.marketing.mobile.util.StringUtils; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class XDMLanguage { + private final String languageRegex = + "^(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$"; + private final Pattern languagePattern = Pattern.compile(languageRegex); + private final String language; + + XDMLanguage(final String language) { + if (StringUtils.isNullOrEmpty(language) || !isValidLanguageTag(language)) { + throw new IllegalArgumentException("Language tag failed validation"); + } + + this.language = language; + } + + String getLanguage() { + return this.language; + } + + Map serializeToXdm() { + Map map = new HashMap(); + map.put("language", this.language); + return map; + } + + /** + * Validate the language tag is formatted per the XDM Environment Schema required pattern. + * + * @param tag the language tag to validate + * @return true if the language tag matches the pattern. + */ + private boolean isValidLanguageTag(@NonNull final String tag) { + return languagePattern.matcher(tag).matches(); + } +} diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleApplication.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleApplication.java index c97b3daec..bb5827275 100644 --- a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleApplication.java +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleApplication.java @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.lifecycle; +import com.adobe.marketing.mobile.services.Log; import java.util.HashMap; import java.util.Map; @@ -18,12 +19,14 @@ @SuppressWarnings("unused") class XDMLifecycleApplication { + private final String LOG_SOURCE = "XDMLifecycleApplication"; private XDMLifecycleCloseTypeEnum closeType; private String id; private boolean isClose; private boolean isInstall; private boolean isLaunch; private boolean isUpgrade; + private XDMLanguage language; private String name; private int sessionLength; private String version; @@ -69,6 +72,10 @@ Map serializeToXdm() { map.put("sessionLength", this.sessionLength); } + if (this.language != null) { + map.put("_dc", this.language.serializeToXdm()); + } + return map; } @@ -186,6 +193,36 @@ void setIsUpgrade(final boolean newValue) { this.isUpgrade = newValue; } + /** + * Returns the Language property The language of the environment to represent the user's + * linguistic, geographical, or cultural preferences for data presentation. + * + * @return {@link String} value or null if the property is not set + */ + String getLanguage() { + return this.language.getLanguage(); + } + + /** + * Sets the Language property The language of the environment to represent the user's + * linguistic, geographical, or cultural preferences for data presentation (according to IETF + * RFC 3066). + * + * @param newValue the new Language value + */ + void setLanguage(final String newValue) { + try { + this.language = new XDMLanguage(newValue); + } catch (IllegalArgumentException ex) { + Log.warning( + LifecycleConstants.LOG_TAG, + LOG_SOURCE, + "Language tag '%s' failed validation and will be dropped. Values for XDM" + + " field 'application._dc.language' must conform to BCP 47.", + newValue); + } + } + /** * Returns the Name property Name of the application. * diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleEnvironment.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleEnvironment.java index 8f4db8d4b..1c6a9a8df 100644 --- a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleEnvironment.java +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleEnvironment.java @@ -11,12 +11,9 @@ package com.adobe.marketing.mobile.lifecycle; -import androidx.annotation.NonNull; import com.adobe.marketing.mobile.services.Log; -import com.adobe.marketing.mobile.util.StringUtils; import java.util.HashMap; import java.util.Map; -import java.util.regex.Pattern; /** * Class {@code Environment} representing a subset of the XDM Environment data type fields. @@ -27,13 +24,10 @@ class XDMLifecycleEnvironment { private final String LOG_SOURCE = "XDMLifecycleEnvironment"; private String carrier; - private String language; + private XDMLanguage language; private String operatingSystem; private String operatingSystemVersion; private XDMLifecycleEnvironmentTypeEnum type; - private final String languageRegex = - "^(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$"; - private final Pattern languagePattern = Pattern.compile(languageRegex); XDMLifecycleEnvironment() {} @@ -44,19 +38,8 @@ Map serializeToXdm() { map.put("carrier", this.carrier); } - if (!StringUtils.isNullOrEmpty(this.language)) { - if (isValidLanguageTag(this.language)) { - Map dublinCoreLanguage = new HashMap(); - dublinCoreLanguage.put("language", this.language); - map.put("_dc", dublinCoreLanguage); - } else { - Log.warning( - LifecycleConstants.LOG_TAG, - LOG_SOURCE, - "Language tag '%s' failed validation and will be dropped. Values for XDM" - + " field 'environment._dc.language' must conform to BCP 47.", - this.language); - } + if (this.language != null) { + map.put("_dc", this.language.serializeToXdm()); } if (this.operatingSystem != null) { @@ -105,7 +88,7 @@ void setCarrier(final String newValue) { * @return {@link String} value or null if the property is not set */ String getLanguage() { - return this.language; + return this.language.getLanguage(); } /** @@ -116,7 +99,16 @@ String getLanguage() { * @param newValue the new Language value */ void setLanguage(final String newValue) { - this.language = newValue; + try { + this.language = new XDMLanguage(newValue); + } catch (IllegalArgumentException ex) { + Log.warning( + LifecycleConstants.LOG_TAG, + LOG_SOURCE, + "Language tag '%s' failed validation and will be dropped. Values for XDM" + + " field 'environment._dc.language' must conform to BCP 47.", + newValue); + } } /** @@ -180,14 +172,4 @@ XDMLifecycleEnvironmentTypeEnum getType() { void setType(final XDMLifecycleEnvironmentTypeEnum newValue) { this.type = newValue; } - - /** - * Validate the language tag is formatted per the XDM Environment Schema required pattern. - * - * @param tag the language tag to validate - * @return true if the language tag matches the pattern. - */ - private boolean isValidLanguageTag(@NonNull final String tag) { - return languagePattern.matcher(tag).matches(); - } } diff --git a/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleTestHelper.java b/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleTestHelper.java index feb72cfaf..e21c232f9 100644 --- a/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleTestHelper.java +++ b/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleTestHelper.java @@ -46,6 +46,7 @@ public int getDensityDpi() { when(deviceInfoService.getOperatingSystemVersion()).thenReturn("5.55"); when(deviceInfoService.getMobileCarrierName()).thenReturn("TEST_CARRIER"); when(deviceInfoService.getActiveLocale()).thenReturn(new Locale("en", "US")); + when(deviceInfoService.getSystemLocale()).thenReturn(new Locale("fr", "FR")); when(deviceInfoService.getRunMode()).thenReturn("APPLICATION"); } } diff --git a/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2MetricsBuilderTest.java b/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2MetricsBuilderTest.java index 4c6b212a2..6263da6d3 100644 --- a/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2MetricsBuilderTest.java +++ b/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleV2MetricsBuilderTest.java @@ -48,7 +48,7 @@ public static void beforeAll() { "_dc", new HashMap() { { - put("language", "en-US"); + put("language", "fr-FR"); } }); } @@ -92,6 +92,13 @@ public void testBuildAppLaunchXDMData_returnsCorrectData_whenIsInstall() { put("version", "1.1 (12345)"); put("isInstall", true); put("isLaunch", true); + put( + "_dc", + new HashMap() { + { + put("language", "en-US"); + } + }); } }); expectedData.put("eventType", "application.launch"); @@ -117,6 +124,13 @@ public void testBuildAppLaunchXDMData_returnsCorrectData_whenIsUpgradeEvent() { put("version", "1.1 (12345)"); put("isUpgrade", true); put("isLaunch", true); + put( + "_dc", + new HashMap() { + { + put("language", "en-US"); + } + }); } }); expectedData.put("eventType", "application.launch"); @@ -164,6 +178,13 @@ public void testBuildAppLaunchXDMData_returnsCorrectData_whenIsLaunch() { put("id", "test.package.name"); put("version", "1.1 (12345)"); put("isLaunch", true); + put( + "_dc", + new HashMap() { + { + put("language", "en-US"); + } + }); } }); expectedData.put("eventType", "application.launch"); From 7952e400c0115bee5a799c8a9ad765f8433c1e93 Mon Sep 17 00:00:00 2001 From: praveek Date: Wed, 19 Jul 2023 14:16:03 -0700 Subject: [PATCH 15/20] Fix review comments --- .../java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java | 2 +- .../marketing/mobile/lifecycle/XDMLifecycleApplication.java | 2 +- .../marketing/mobile/lifecycle/XDMLifecycleEnvironment.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java index 0d4d5a0d4..a12104093 100644 --- a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLanguage.java @@ -17,7 +17,7 @@ import java.util.Map; import java.util.regex.Pattern; -public class XDMLanguage { +class XDMLanguage { private final String languageRegex = "^(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$"; private final Pattern languagePattern = Pattern.compile(languageRegex); diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleApplication.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleApplication.java index bb5827275..7fa485bf8 100644 --- a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleApplication.java +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleApplication.java @@ -200,7 +200,7 @@ void setIsUpgrade(final boolean newValue) { * @return {@link String} value or null if the property is not set */ String getLanguage() { - return this.language.getLanguage(); + return this.language != null ? this.language.getLanguage() : null; } /** diff --git a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleEnvironment.java b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleEnvironment.java index 1c6a9a8df..2c441a0b3 100644 --- a/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleEnvironment.java +++ b/code/lifecycle/src/main/java/com/adobe/marketing/mobile/lifecycle/XDMLifecycleEnvironment.java @@ -88,7 +88,7 @@ void setCarrier(final String newValue) { * @return {@link String} value or null if the property is not set */ String getLanguage() { - return this.language.getLanguage(); + return this.language != null ? this.language.getLanguage() : null; } /** From b6617f1cb56c94509bc42cf6a8abd3c590c945d3 Mon Sep 17 00:00:00 2001 From: praveek Date: Wed, 19 Jul 2023 15:39:39 -0700 Subject: [PATCH 16/20] Refactor logic around fetching locale --- .../mobile/services/DeviceInfoService.java | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java index 3730a486a..f6e596f25 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java @@ -56,23 +56,7 @@ public Locale getActiveLocale() { return null; } - final Resources resources = context.getResources(); - - if (resources == null) { - return null; - } - - final Configuration configuration = resources.getConfiguration(); - - if (configuration == null) { - return null; - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - return configuration.locale; - } else { - return configuration.getLocales().get(0); - } + return getLocaleFromResources(context.getResources()); } /** @@ -83,23 +67,7 @@ public Locale getActiveLocale() { */ @Override public Locale getSystemLocale() { - final Resources resources = Resources.getSystem(); - - if (resources == null) { - return null; - } - - final Configuration configuration = resources.getConfiguration(); - - if (configuration == null) { - return null; - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - return configuration.locale; - } else { - return configuration.getLocales().get(0); - } + return getLocaleFromResources(Resources.getSystem()); } @Override @@ -608,6 +576,29 @@ public String getLocaleString() { return result; } + /** + * Returns the preferred locale value from the Resources object. + * + * @return A {@link Locale} value, if available, null otherwise + */ + private Locale getLocaleFromResources(final Resources resources) { + if (resources == null) { + return null; + } + + final Configuration configuration = resources.getConfiguration(); + + if (configuration == null) { + return null; + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return configuration.locale; + } else { + return configuration.getLocales().get(0); + } + } + /** * Checks if a {@code String} is null, empty or it only contains whitespaces. * From 2685f324a60edac1740e950112d868516d2c62f5 Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 25 Jul 2023 14:46:58 -0700 Subject: [PATCH 17/20] Reset dataqueue if underlying database get corrupted --- .../mobile/services/SqliteDataQueueTests.java | 86 +++++++- .../mobile/services/SQLiteDataQueue.java | 190 +++++++++++------- 2 files changed, 203 insertions(+), 73 deletions(-) diff --git a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java index 625fdb881..6255b5315 100644 --- a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java +++ b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -12,9 +12,11 @@ package com.adobe.marketing.mobile.services; import android.content.Context; +import android.database.sqlite.SQLiteDatabase; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.File; +import java.io.FileOutputStream; import java.util.List; import org.junit.After; import org.junit.Assert; @@ -25,19 +27,19 @@ @RunWith(AndroidJUnit4.class) public class SqliteDataQueueTests { - private File dbFile; private DataQueue dataQueue; private static final String QUEUE_NAME = "test.dataQueue"; @Before public void setUp() { Context context = ApplicationProvider.getApplicationContext(); - dbFile = context.getDatabasePath(QUEUE_NAME); dataQueue = new SQLiteDataQueue(context.getDatabasePath(QUEUE_NAME).getPath()); } @After public void tearDown() { + Context context = ApplicationProvider.getApplicationContext(); + File dbFile = getDatabase(); if (dbFile != null && dbFile.exists()) { dbFile.delete(); } @@ -98,4 +100,84 @@ public void testClear() { List results = dataQueue.peek(4); Assert.assertEquals(0, results.size()); } + + @Test + public void testAddToCorruptedDatabase() { + dataQueue.add(new DataEntity("test_data_1")); + dataQueue.add(new DataEntity("test_data_2")); + Assert.assertEquals(2, dataQueue.count()); + + corruptDatabase(); + Assert.assertTrue(isDatabaseCorrupt()); + + // After detecting db is corrupt, resets the database and then adds the new entry. + Assert.assertTrue(dataQueue.add(new DataEntity("test_data_3"))); + Assert.assertEquals(1, dataQueue.count()); + Assert.assertEquals("test_data_3", dataQueue.peek().getData()); + Assert.assertFalse(isDatabaseCorrupt()); + } + + @Test + public void testRemoveFromCorruptedDatabase() { + dataQueue.add(new DataEntity("test_data_1")); + dataQueue.add(new DataEntity("test_data_2")); + Assert.assertEquals(2, dataQueue.count()); + + corruptDatabase(); + Assert.assertTrue(isDatabaseCorrupt()); + + // After detecting db is corrupt, resets the database. + Assert.assertFalse(dataQueue.remove()); + Assert.assertEquals(0, dataQueue.count()); + Assert.assertFalse(isDatabaseCorrupt()); + } + + @Test + public void testClearCorruptedDatabase() { + dataQueue.add(new DataEntity("test_data_1")); + dataQueue.add(new DataEntity("test_data_2")); + Assert.assertEquals(2, dataQueue.count()); + + corruptDatabase(); + Assert.assertTrue(isDatabaseCorrupt()); + + // After detecting db is corrupt, resets the database. + Assert.assertFalse(dataQueue.clear()); + Assert.assertEquals(0, dataQueue.count()); + Assert.assertFalse(isDatabaseCorrupt()); + } + + private File getDatabase() { + Context context = ApplicationProvider.getApplicationContext(); + return context.getDatabasePath(QUEUE_NAME); + } + + private void corruptDatabase() { + File dbFile = getDatabase(); + if (dbFile == null) { + return; + } + try { + FileOutputStream fos = new FileOutputStream(getDatabase()); + fos.write(new byte[1024]); + fos.close(); + } catch (Exception e) { + + } + } + + private boolean isDatabaseCorrupt() { + File dbFile = getDatabase(); + if (dbFile == null) { + return true; + } + try { + SQLiteDatabase database = + SQLiteDatabase.openDatabase( + dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY); + return database == null || !database.isDatabaseIntegrityOk(); + } catch (Exception e) { + return true; + } + } } diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java index 875bbfa63..e0344bf99 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java @@ -16,7 +16,9 @@ import android.database.DatabaseUtils; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteStatement; +import com.adobe.marketing.mobile.internal.util.FileUtils; import com.adobe.marketing.mobile.internal.util.SQLiteDatabaseHelper; +import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -57,39 +59,15 @@ public boolean add(final DataEntity dataEntity) { return false; } - return SQLiteDatabaseHelper.process( - databasePath, - SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, - database -> { - if (database == null) { - return false; - } - final int INDEX_UUID = 1; - final int INDEX_TIMESTAMP = 2; - final int INDEX_DATA = 3; - try (SQLiteStatement insertStatement = - database.compileStatement( - "INSERT INTO " - + TABLE_NAME - + " (uniqueIdentifier, timestamp, data) VALUES (?," - + " ?, ?)")) { - insertStatement.bindString( - INDEX_UUID, dataEntity.getUniqueIdentifier()); - insertStatement.bindLong( - INDEX_TIMESTAMP, dataEntity.getTimestamp().getTime()); - insertStatement.bindString( - INDEX_DATA, - dataEntity.getData() != null ? dataEntity.getData() : ""); - long rowId = insertStatement.executeInsert(); - return rowId >= 0; - } catch (Exception e) { - Log.debug( - ServiceConstants.LOG_TAG, - LOG_PREFIX, - "add - Returning false: " + e.getLocalizedMessage()); - return false; - } - }); + boolean result = tryAddEntity(dataEntity); + + if (!result) { + resetDatabase(); + // Retry adding the data after resetting the database. + result = tryAddEntity(dataEntity); + } + + return result; } } @@ -226,44 +204,52 @@ public boolean remove(final int n) { return false; } - return SQLiteDatabaseHelper.process( - databasePath, - SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, - database -> { - int deletedRowsCount = -1; - if (database == null) { - return false; - } - String builder = - "DELETE FROM " - + TABLE_NAME - + " WHERE id in (" - + "SELECT id from " - + TABLE_NAME - + " order by id ASC" - + " limit " - + n - + ')'; - try (SQLiteStatement statement = database.compileStatement(builder)) { - deletedRowsCount = statement.executeUpdateDelete(); - Log.trace( - ServiceConstants.LOG_TAG, - LOG_PREFIX, - String.format( - "remove n - Removed %d DataEntities", - deletedRowsCount)); - return deletedRowsCount > -1; - } catch (final SQLiteException e) { - Log.warning( - ServiceConstants.LOG_TAG, - LOG_PREFIX, - String.format( - "removeRows - Error in deleting rows from table(%s)." - + " Returning 0. Error: (%s)", - TABLE_NAME, e.getMessage())); - return false; - } - }); + boolean result = + SQLiteDatabaseHelper.process( + databasePath, + SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, + database -> { + int deletedRowsCount = -1; + if (database == null) { + return false; + } + String builder = + "DELETE FROM " + + TABLE_NAME + + " WHERE id in (" + + "SELECT id from " + + TABLE_NAME + + " order by id ASC" + + " limit " + + n + + ')'; + try (SQLiteStatement statement = + database.compileStatement(builder)) { + deletedRowsCount = statement.executeUpdateDelete(); + Log.trace( + ServiceConstants.LOG_TAG, + LOG_PREFIX, + String.format( + "remove n - Removed %d DataEntities", + deletedRowsCount)); + return deletedRowsCount > -1; + } catch (final SQLiteException e) { + Log.warning( + ServiceConstants.LOG_TAG, + LOG_PREFIX, + String.format( + "removeRows - Error in deleting rows from" + + " table(%s). Returning 0. Error: (%s)", + TABLE_NAME, e.getMessage())); + return false; + } + }); + + if (!result) { + resetDatabase(); + } + + return result; } } @@ -290,6 +276,10 @@ public boolean clear() { String.format( "clear - %s in clearing Table %s", (result ? "Successful" : "Failed"), TABLE_NAME)); + + if (!result) { + resetDatabase(); + } return result; } } @@ -349,4 +339,62 @@ private void createTableIfNotExists() { "createTableIfNotExists - Error creating/accessing table (%s) ", TABLE_NAME)); } + + /** + * Adds a a Table with name {@link #TABLE_NAME}, if not already exists in database at path + * {@link #databasePath}. + */ + private boolean tryAddEntity(final DataEntity dataEntity) { + return SQLiteDatabaseHelper.process( + databasePath, + SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, + database -> { + if (database == null) { + return false; + } + final int INDEX_UUID = 1; + final int INDEX_TIMESTAMP = 2; + final int INDEX_DATA = 3; + try (SQLiteStatement insertStatement = + database.compileStatement( + "INSERT INTO " + + TABLE_NAME + + " (uniqueIdentifier, timestamp, data) VALUES (?," + + " ?, ?)")) { + insertStatement.bindString(INDEX_UUID, dataEntity.getUniqueIdentifier()); + insertStatement.bindLong( + INDEX_TIMESTAMP, dataEntity.getTimestamp().getTime()); + insertStatement.bindString( + INDEX_DATA, + dataEntity.getData() != null ? dataEntity.getData() : ""); + long rowId = insertStatement.executeInsert(); + return rowId >= 0; + } catch (Exception e) { + Log.debug( + ServiceConstants.LOG_TAG, + LOG_PREFIX, + "add - Returning false: " + e.getLocalizedMessage()); + return false; + } + }); + } + + private void resetDatabase() { + Log.warning( + ServiceConstants.LOG_TAG, + LOG_PREFIX, + "resetDatabase - Resetting database (%s) as it is corrupted", + databasePath); + + try { + FileUtils.deleteFile(new File(databasePath), false); + createTableIfNotExists(); + } catch (Exception ex) { + Log.warning( + ServiceConstants.LOG_TAG, + LOG_PREFIX, + "resetDatabase - Error resetting database (%s) ", + databasePath); + } + } } From 9908f4615cc92165d519ce81ea161e7a04c611e7 Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 25 Jul 2023 16:38:33 -0700 Subject: [PATCH 18/20] Fix return type for SQLDataQueue.clear() --- .../adobe/marketing/mobile/services/SqliteDataQueueTests.java | 2 +- .../com/adobe/marketing/mobile/services/SQLiteDataQueue.java | 2 +- .../adobe/marketing/mobile/services/SqliteDataQueueTests.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java index 6255b5315..8feda6073 100644 --- a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java +++ b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -142,7 +142,7 @@ public void testClearCorruptedDatabase() { Assert.assertTrue(isDatabaseCorrupt()); // After detecting db is corrupt, resets the database. - Assert.assertFalse(dataQueue.clear()); + Assert.assertTrue(dataQueue.clear()); Assert.assertEquals(0, dataQueue.count()); Assert.assertFalse(isDatabaseCorrupt()); } diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java index e0344bf99..5e0fa105f 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java @@ -280,7 +280,7 @@ public boolean clear() { if (!result) { resetDatabase(); } - return result; + return true; } } diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/core/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java index 1f900b76e..ba6d9d220 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -215,7 +215,7 @@ public void clearTableWithDatabaseOpenError() { boolean result = dataQueue.clear(); // Assertions - Assert.assertFalse(result); + Assert.assertTrue(result); } } From 1d30a35a93806f15e7dd5b9a71677a756d367e0c Mon Sep 17 00:00:00 2001 From: praveek Date: Wed, 26 Jul 2023 11:43:09 -0700 Subject: [PATCH 19/20] Fix comments --- .../com/adobe/marketing/mobile/services/SQLiteDataQueue.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java index 5e0fa105f..1203fabc7 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java @@ -341,8 +341,8 @@ private void createTableIfNotExists() { } /** - * Adds a a Table with name {@link #TABLE_NAME}, if not already exists in database at path - * {@link #databasePath}. + * Add a new {@link DataEntity} Object to {@link DataQueue}. NOTE: The caller must hold the + * dbMutex. */ private boolean tryAddEntity(final DataEntity dataEntity) { return SQLiteDatabaseHelper.process( @@ -379,6 +379,7 @@ private boolean tryAddEntity(final DataEntity dataEntity) { }); } + /** Resets the database. NOTE: The caller must hold the dbMutex. */ private void resetDatabase() { Log.warning( ServiceConstants.LOG_TAG, From 0785f01196313e390c7f4cc399959952707a6abf Mon Sep 17 00:00:00 2001 From: praveek Date: Wed, 26 Jul 2023 19:01:48 +0000 Subject: [PATCH 20/20] Update versions [Core-2.3.0] [Lifecycle-2.0.4] --- .../java/com/adobe/marketing/mobile/internal/CoreConstants.kt | 2 +- .../test/java/com/adobe/marketing/mobile/MobileCoreTests.kt | 2 +- .../internal/configuration/ConfigurationExtensionTests.kt | 2 +- code/gradle.properties | 4 ++-- .../src/phone/java/com/adobe/marketing/mobile/Lifecycle.java | 2 +- .../java/com/adobe/marketing/mobile/LifecycleAPITests.java | 2 +- .../marketing/mobile/lifecycle/LifecycleExtensionTests.java | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/internal/CoreConstants.kt b/code/core/src/main/java/com/adobe/marketing/mobile/internal/CoreConstants.kt index 445fa530c..0ff66f4d0 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/internal/CoreConstants.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/internal/CoreConstants.kt @@ -13,7 +13,7 @@ package com.adobe.marketing.mobile.internal internal object CoreConstants { const val LOG_TAG = "MobileCore" - const val VERSION = "2.2.3" + const val VERSION = "2.3.0" object EventDataKeys { /** diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt b/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt index c6e7b82ff..84d0da242 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt @@ -35,7 +35,7 @@ import kotlin.test.assertTrue @RunWith(MockitoJUnitRunner.Silent::class) class MobileCoreTests { - private var EXTENSION_VERSION = "2.2.3" + private var EXTENSION_VERSION = "2.3.0" @Mock private lateinit var mockedEventHub: EventHub diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTests.kt b/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTests.kt index 145746793..7eafbbf33 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTests.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/internal/configuration/ConfigurationExtensionTests.kt @@ -60,7 +60,7 @@ import kotlin.test.assertTrue @RunWith(MockitoJUnitRunner.Silent::class) class ConfigurationExtensionTests { - private var EXTENSION_VERSION = "2.2.3" + private var EXTENSION_VERSION = "2.3.0" @Mock private lateinit var mockServiceProvider: ServiceProvider diff --git a/code/gradle.properties b/code/gradle.properties index 88068d1b5..e8144efde 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -6,7 +6,7 @@ android.useAndroidX=true # #Maven artifacts #Core extension -coreExtensionVersion=2.2.3 +coreExtensionVersion=2.3.0 coreExtensionName=core coreExtensionAARName=core-phone-release.aar coreMavenRepoName=AdobeMobileCoreSdk @@ -18,7 +18,7 @@ signalExtensionAARName=signal-phone-release.aar signalMavenRepoName=AdobeMobileSignalSdk signalMavenRepoDescription=Android Signal Extension for Adobe Mobile Marketing #Lifecycle extension -lifecycleExtensionVersion=2.0.3 +lifecycleExtensionVersion=2.0.4 lifecycleExtensionName=lifecycle lifecycleExtensionAARName=lifecycle-phone-release.aar lifecycleMavenRepoName=AdobeMobileLifecycleSdk diff --git a/code/lifecycle/src/phone/java/com/adobe/marketing/mobile/Lifecycle.java b/code/lifecycle/src/phone/java/com/adobe/marketing/mobile/Lifecycle.java index 1bcd845ca..843191832 100644 --- a/code/lifecycle/src/phone/java/com/adobe/marketing/mobile/Lifecycle.java +++ b/code/lifecycle/src/phone/java/com/adobe/marketing/mobile/Lifecycle.java @@ -17,7 +17,7 @@ public class Lifecycle { - private static final String EXTENSION_VERSION = "2.0.3"; + private static final String EXTENSION_VERSION = "2.0.4"; public static final Class EXTENSION = LifecycleExtension.class; diff --git a/code/lifecycle/src/test/java/com/adobe/marketing/mobile/LifecycleAPITests.java b/code/lifecycle/src/test/java/com/adobe/marketing/mobile/LifecycleAPITests.java index 783bcdd81..88bd1bb8d 100644 --- a/code/lifecycle/src/test/java/com/adobe/marketing/mobile/LifecycleAPITests.java +++ b/code/lifecycle/src/test/java/com/adobe/marketing/mobile/LifecycleAPITests.java @@ -25,7 +25,7 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class LifecycleAPITests { - private static final String EXTENSION_VERSION = "2.0.3"; + private static final String EXTENSION_VERSION = "2.0.4"; @Test public void test_extensionVersion() { diff --git a/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleExtensionTests.java b/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleExtensionTests.java index 9c41fc17c..703b8e104 100644 --- a/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleExtensionTests.java +++ b/code/lifecycle/src/test/java/com/adobe/marketing/mobile/lifecycle/LifecycleExtensionTests.java @@ -38,7 +38,7 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class LifecycleExtensionTests { - private static final String EXTENSION_VERSION = "2.0.3"; + private static final String EXTENSION_VERSION = "2.0.4"; @Mock ExtensionApi extensionApi;