From b4d0c204ac28c1e816ba6051fcff31926cefe887 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Mon, 10 Jun 2024 16:15:09 -0700 Subject: [PATCH 01/22] Set webview container background to be transparent The InAppMessage WebView is hosted inside a Card container. By default the Card background color is grey. This is causing unintended results(grey bg being seen) for in-app messages where dimensions are set to fill the entire screen but the content only occupies a portion of it. As a soution, set the container Card's background color to Transperent as with the WebView. --- .../marketing/mobile/services/ui/message/views/MessageFrame.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/message/views/MessageFrame.kt b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/message/views/MessageFrame.kt index b20be3062..e89a74050 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/message/views/MessageFrame.kt +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/message/views/MessageFrame.kt @@ -102,6 +102,7 @@ internal fun MessageFrame( // the WebView message is clipped to the rounded corners for API versions 22 and below. This does not // affect the appearance of the message on API versions 23 and above. Card( + backgroundColor = Color.Transparent, modifier = Modifier .clip(RoundedCornerShape(inAppMessageSettings.cornerRadius.dp)) .alpha(0.99f) From 658223cd551f98b8e82eaa9df6d358c53d634cf3 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Mon, 17 Jun 2024 13:57:12 -0700 Subject: [PATCH 02/22] Detach presentables before re-attaching to top Activity Currently, the ComposeView attached to the Activity when presentables are shown is not removed until the activity is destroyed. This is OK for most use-cases that follow the sigle window mode where the newly resumed activity obscures the previous one, and the presentable redraws on the new activity. However, this approach of waiting until the activity is destroyed may result in the presentables being shown on each activity in a multi-window side-by-side mode. While we do not support multi-window mode, we should ensure that the experience does not deteriorate in such a case. As a solution, change the current detachment logic to to the following: - Track the current activity that the presentable is attached to - When presentable is visble on Activity A and another activity B resumes, use the Android activity co-ordination guarantees to detach presentable from Activity-A and re-attach to Activity B - Reset the reference held to the activity that the presentable is attached to when the activity is destroyed. --- .../services/ui/common/AEPPresentable.kt | 42 ++++++++ .../services/ui/common/AEPPresentableTest.kt | 95 +++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt index e7b7a4910..8de958236 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt @@ -59,6 +59,14 @@ internal abstract class AEPPresentable> : @VisibleForTesting internal val contentIdentifier: Int = Random().nextInt() + /** + * Represents the activity to which the presentable is currently attached. + * This SHOULD always be updated whenever the presentable is attached or detached from an + * activity. For the sake of maintainability, modify this only in [attach] and [detach] and + * limit queries to this field in activity lifecycle methods. + */ + private var attachmentHandle: WeakReference = WeakReference(null) + /** * @param presentation the [Presentation] to be used by this [Presentable] * @param presentationUtilityProvider the [PresentationUtilityProvider] to be used to fetch components needed by this [Presentable] @@ -241,6 +249,22 @@ internal abstract class AEPPresentable> : if (getState() != Presentable.State.VISIBLE) { return@launch } + + // If the activity that has currently resumed is not the same as the one the presentable + // is attached to, then it means that another activity has launched on top of/beside + // current activity. Detach the presentable from the current activity before attaching + // it to the newly resumed activity. + val currentAttachmentHandle = attachmentHandle.get() + if (currentAttachmentHandle != null && currentAttachmentHandle != activity) { + Log.trace( + ServiceConstants.LOG_TAG, + LOG_SOURCE, + "Detaching from $currentAttachmentHandle before attaching to $activity." + ) + + detach(currentAttachmentHandle) + } + attach(activity) } } @@ -327,6 +351,10 @@ internal abstract class AEPPresentable> : composeView.id = contentIdentifier val rootViewGroup = activityToAttach.findViewById(android.R.id.content) rootViewGroup.addView(composeView) + + // Update the attachment handle to the currently attached activity. + attachmentHandle = WeakReference(activityToAttach) + Log.trace( ServiceConstants.LOG_TAG, LOG_SOURCE, @@ -373,6 +401,20 @@ internal abstract class AEPPresentable> : } existingComposeView.removeAllViews() rootViewGroup.removeView(existingComposeView) + + // Clear the attachment handle if the current attachment handle is the same as the activity + // to detach. If not, the handle would have already been cleared when the presentable + // was attached due to another activity being resumed on top of the presentable. + val currentAttachmentHandle = attachmentHandle.get() + if (currentAttachmentHandle == activityToDetach) { + Log.trace( + ServiceConstants.LOG_TAG, + LOG_SOURCE, + "Clearing attachment handle ($activityToDetach)." + ) + attachmentHandle.clear() + } + activityCompatOwnerUtils.detachActivityCompatOwner(activityToDetach) Log.trace(ServiceConstants.LOG_TAG, LOG_SOURCE, "Detached ${contentIdentifier}from $activityToDetach.") } diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt index 8a58f60e0..1d2971457 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt @@ -41,11 +41,16 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import org.mockito.kotlin.KArgumentCaptor import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.never +import org.mockito.kotlin.times import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions import kotlin.test.assertEquals +import kotlin.test.assertTrue internal class AEPPresentableTest { @Mock @@ -809,6 +814,92 @@ internal class AEPPresentableTest { } } + @Test + fun `Test that presentable is detached and resurfaced when an Activity launches over a visible presentable`() { + // setup + val presentationStateManager = PresentationStateManager() + val aepPresentableWithGatedDisplay = TestAEPPresentableGatedDisplay( + mockPresentation, + mockPresentationUtilityProvider, + mockPresentationDelegate, + mockAppLifecycleProvider, + presentationStateManager, + mockActivityCompatLifecycleUtils, + mockMainScope, + mockPresentationObserver, + conflictLogic = { visible -> + visible.any { it is Alert } + } + ) + + // simulate initial detached state + // `when`(mockPresentationStateManager.presentableState).thenReturn(mutableStateOf(Presentable.State.DETACHED)) + // simulate that the presentation delegate allows the presentation to be shown + `when`(mockPresentationDelegate.canShow(aepPresentableWithGatedDisplay)).thenReturn(true) + // simulate a valid activity being present + `when`(mockPresentationUtilityProvider.getCurrentActivity()).thenReturn(mockActivity) + // simulate no existing ComposeView being present + `when`(mockActivity.findViewById(aepPresentableWithGatedDisplay.contentIdentifier)).thenReturn( + null + ) + `when`(mockActivity.findViewById(eq(android.R.id.content))).thenReturn( + mockViewGroup + ) + + `when`(mockPresentationObserver.getVisiblePresentations()).thenReturn( + mutableListOf(mock(FloatingButton::class.java)) + ) + + val mockAnotherActivity = mock(AnotherActivity::class.java) + val anotherViewGroup = mock(ViewGroup::class.java) + `when`(mockAnotherActivity.findViewById(eq(android.R.id.content))).thenReturn( + anotherViewGroup + ) + + runTest { + // Part-1: Simulate presentable shown for the first time + aepPresentableWithGatedDisplay.show() + + // verify that the presentation delegate is called because display is gated + verify(mockPresentationDelegate).canShow(aepPresentableWithGatedDisplay) + + // verify that the lifecycle provider is called to register the listener + verify(mockAppLifecycleProvider).registerListener(aepPresentableWithGatedDisplay) + + // Verify that the compose view is added to the viewgroup + val composeViewCaptor: KArgumentCaptor = argumentCaptor() + verify(mockViewGroup, times(1)).addView(composeViewCaptor.capture()) + + // verify that the listener, delegate, and state manager are notified of content show + verify(mockPresentationDelegate).onShow(aepPresentableWithGatedDisplay) + verify(mockPresentationListener).onShow(aepPresentableWithGatedDisplay) + assertTrue { presentationStateManager.presentableState.value == Presentable.State.VISIBLE } + + // verify that the presentation observer is notified of the new presentation + verify(mockPresentationObserver).onPresentationVisible(aepPresentableWithGatedDisplay.getPresentation()) + + // We already asserted above the presentable has been attached. So return the captured ComposeView + `when`(mockActivity.findViewById(aepPresentableWithGatedDisplay.contentIdentifier)).thenReturn( + composeViewCaptor.firstValue + ) + + // Part-2: Simulate another activity resuming when presentable is already shown + // simulate another activity being the current activity + aepPresentableWithGatedDisplay.onActivityResumed(mockAnotherActivity) + + // verify that the compose view is removed from the previous Activity view/group + verify(mockViewGroup, times(1)).removeView(composeViewCaptor.firstValue) + + // verify that a new compose view is added to the new Activity view/group + verify(anotherViewGroup, times(1)).addView(any()) + assertTrue { presentationStateManager.presentableState.value == Presentable.State.VISIBLE } + + // verify that the listener, delegate are not re-notified because this is implicit + verifyNoMoreInteractions(mockPresentationDelegate) + verifyNoMoreInteractions(mockPresentationListener) + } + } + @Test fun `Test that AEPPresentable#onActivityResumed bails when presentable is HIDDEN`() { // setup @@ -963,4 +1054,8 @@ internal class AEPPresentableTest { return conflictLogic(visiblePresentations) } } + + private class AnotherActivity : Activity() { + // no-op activity + } } From 413233a16102aa777d520e5b323c5811412c3f89 Mon Sep 17 00:00:00 2001 From: yansong Date: Tue, 18 Jun 2024 11:23:14 -0500 Subject: [PATCH 03/22] Not to initialize SDK in Android direct boot mode --- code/core/build.gradle.kts | 1 + .../adobe/marketing/mobile/MobileCore.java | 25 +++++- .../mobile/MobileCoreRobolectricTests.kt | 83 +++++++++++++++++++ code/testapp/src/main/AndroidManifest.xml | 18 +++- .../core/testapp/BootBroadcastReceiver.java | 32 +++++++ .../marketing/mobile/core/testapp/MyApp.kt | 5 +- 6 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt create mode 100644 code/testapp/src/main/java/com/adobe/marketing/mobile/core/testapp/BootBroadcastReceiver.java diff --git a/code/core/build.gradle.kts b/code/core/build.gradle.kts index ec27ce165..9adefb9cc 100644 --- a/code/core/build.gradle.kts +++ b/code/core/build.gradle.kts @@ -52,4 +52,5 @@ dependencies { androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0") //TODO: Consider moving this to the aep-library plugin later androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.3") + testImplementation("org.robolectric:robolectric:4.7") } diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 626cb20fa..51e78f441 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -13,9 +13,13 @@ import android.app.Activity; import android.app.Application; +import android.app.Application.ActivityLifecycleCallbacks; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.core.os.UserManagerCompat; import com.adobe.marketing.mobile.internal.AppResourceStore; import com.adobe.marketing.mobile.internal.CoreConstants; import com.adobe.marketing.mobile.internal.DataMarshaller; @@ -82,8 +86,8 @@ public static void setWrapperType(@NonNull final WrapperType wrapperType) { /** * Set the Android {@link Application}, which enables the SDK get the app {@code Context}, - * register a {@link Application.ActivityLifecycleCallbacks} to monitor the lifecycle of the app - * and get the {@link android.app.Activity} on top of the screen. + * register a {@link ActivityLifecycleCallbacks} to monitor the lifecycle of the app and get the + * {@link Activity} on top of the screen. * *

NOTE: This method should be called right after the app starts, so it gives the SDK all the * contexts it needed. @@ -91,12 +95,26 @@ public static void setWrapperType(@NonNull final WrapperType wrapperType) { * @param application the Android {@link Application} instance. It should not be null. */ public static void setApplication(@NonNull final Application application) { + if (application == null) { Log.error( CoreConstants.LOG_TAG, LOG_TAG, "setApplication failed - application is null"); return; } + // Direct boot mode is supported on Android N and above + if (VERSION.SDK_INT >= VERSION_CODES.N) { + if (UserManagerCompat.isUserUnlocked(application)) { + Log.debug(CoreConstants.LOG_TAG, LOG_TAG, "Device is not in direct boot mode."); + } else { + Log.error( + CoreConstants.LOG_TAG, + LOG_TAG, + "Device is in direct boot mode, SDK will not be initialized."); + return; + } + } + if (sdkInitializedWithContext.getAndSet(true)) { Log.debug( CoreConstants.LOG_TAG, @@ -105,8 +123,7 @@ public static void setApplication(@NonNull final Application application) { return; } - if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O - || android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1) { + if (VERSION.SDK_INT == VERSION_CODES.O || VERSION.SDK_INT == VERSION_CODES.O_MR1) { // AMSDK-8502 // Workaround to prevent a crash happening on Android 8.0/8.1 related to // TimeZoneNamesImpl diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt b/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt new file mode 100644 index 000000000..6fa6a4c64 --- /dev/null +++ b/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt @@ -0,0 +1,83 @@ +/* + Copyright 2024 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 + +import android.app.Application +import androidx.core.os.UserManagerCompat +import com.adobe.marketing.mobile.internal.eventhub.EventHub +import junit.framework.TestCase.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.assertFalse + +@RunWith(RobolectricTestRunner::class) +class MobileCoreRobolectricTests { + + @Before + fun setup() { + MobileCore.sdkInitializedWithContext = AtomicBoolean(false) + } + + @Test + @Config(sdk = [23]) + fun testA() { + val app = RuntimeEnvironment.application as Application + val mockedEventHub = Mockito.mock(EventHub::class.java) + Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> + mockedStaticUserManagerCompat.`when` { UserManagerCompat.isUserUnlocked(Mockito.any()) } + .thenReturn(false) + MobileCore.setApplication(app) + mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, never()) + } + verify(mockedEventHub, never()).executeInEventHubExecutor(any()) + assertTrue(MobileCore.sdkInitializedWithContext.get()) + } + + @Test + @Config(sdk = [24]) + fun testB() { + val app = RuntimeEnvironment.application as Application + val mockedEventHub = Mockito.mock(EventHub::class.java) + EventHub.shared = mockedEventHub + Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> + mockedStaticUserManagerCompat.`when` { UserManagerCompat.isUserUnlocked(Mockito.any()) }.thenReturn(true) + MobileCore.setApplication(app) + mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, times(1)) + } + verify(mockedEventHub, times(1)).executeInEventHubExecutor(any()) + assertTrue(MobileCore.sdkInitializedWithContext.get()) + } + + @Test + @Config(sdk = [24]) + fun testc() { + val app = RuntimeEnvironment.application as Application + val mockedEventHub = Mockito.mock(EventHub::class.java) + Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> + mockedStaticUserManagerCompat.`when` { UserManagerCompat.isUserUnlocked(Mockito.any()) }.thenReturn(false) + MobileCore.setApplication(app) + mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, times(1)) + } + verify(mockedEventHub, never()).executeInEventHubExecutor(any()) + assertFalse(MobileCore.sdkInitializedWithContext.get()) + } +} diff --git a/code/testapp/src/main/AndroidManifest.xml b/code/testapp/src/main/AndroidManifest.xml index fecce1c22..7b9a659b3 100644 --- a/code/testapp/src/main/AndroidManifest.xml +++ b/code/testapp/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + - + + + + + + + + + + \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/core/testapp/BootBroadcastReceiver.java b/code/testapp/src/main/java/com/adobe/marketing/mobile/core/testapp/BootBroadcastReceiver.java new file mode 100644 index 000000000..b06074874 --- /dev/null +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/core/testapp/BootBroadcastReceiver.java @@ -0,0 +1,32 @@ +/* + Copyright 2024 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.core.testapp; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.util.Log; +import androidx.core.os.BuildCompat; +import androidx.core.os.UserManagerCompat; + +public class BootBroadcastReceiver extends BroadcastReceiver { + + private static final String TAG = "BootBroadcastReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.i(TAG, "Received action: " + action + ", isUserUnlocked(): " + UserManagerCompat + .isUserUnlocked(context)); + } +} \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/core/testapp/MyApp.kt b/code/testapp/src/main/java/com/adobe/marketing/mobile/core/testapp/MyApp.kt index e4cc185bc..c7ff579c2 100644 --- a/code/testapp/src/main/java/com/adobe/marketing/mobile/core/testapp/MyApp.kt +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/core/testapp/MyApp.kt @@ -11,10 +11,12 @@ package com.adobe.marketing.mobile.core.testapp import android.app.Application -import com.adobe.marketing.mobile.MobileCore +import android.util.Log +import androidx.core.os.UserManagerCompat import com.adobe.marketing.mobile.Identity import com.adobe.marketing.mobile.Lifecycle import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore import com.adobe.marketing.mobile.Signal import com.adobe.marketing.mobile.core.testapp.extension.PerfExtension @@ -22,6 +24,7 @@ class MyApp : Application() { override fun onCreate() { super.onCreate() + Log.i("MyApp", "Application.onCreate() - start to initialize Adobe SDK. UserManagerCompat.isUserUnlocked(): ${UserManagerCompat.isUserUnlocked(this)}") MobileCore.setApplication(this) MobileCore.setLogLevel(LoggingMode.VERBOSE) From b23ef03cb2570e865b562c3e9824777ea0c23501 Mon Sep 17 00:00:00 2001 From: yansong Date: Tue, 18 Jun 2024 11:34:59 -0500 Subject: [PATCH 04/22] update tests --- .../adobe/marketing/mobile/MobileCoreRobolectricTests.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt b/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt index 6fa6a4c64..6149b4d6f 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt @@ -39,7 +39,8 @@ class MobileCoreRobolectricTests { @Test @Config(sdk = [23]) - fun testA() { + fun `test setApplication when the device doesn't support direct boot mode`() { + // Android supports direct boot mode on API level 24 and above val app = RuntimeEnvironment.application as Application val mockedEventHub = Mockito.mock(EventHub::class.java) Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> @@ -54,11 +55,12 @@ class MobileCoreRobolectricTests { @Test @Config(sdk = [24]) - fun testB() { + fun `test setApplication when the app is not configured to run in direct boot mode`() { val app = RuntimeEnvironment.application as Application val mockedEventHub = Mockito.mock(EventHub::class.java) EventHub.shared = mockedEventHub Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> + // when initializing SDK, the app is not in direct boot mode (device is unlocked) mockedStaticUserManagerCompat.`when` { UserManagerCompat.isUserUnlocked(Mockito.any()) }.thenReturn(true) MobileCore.setApplication(app) mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, times(1)) @@ -69,10 +71,11 @@ class MobileCoreRobolectricTests { @Test @Config(sdk = [24]) - fun testc() { + fun `test setApplication when the app is launched in direct boot mode`() { val app = RuntimeEnvironment.application as Application val mockedEventHub = Mockito.mock(EventHub::class.java) Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> + // when initializing SDK, the app is in direct boot mode (device is still locked) mockedStaticUserManagerCompat.`when` { UserManagerCompat.isUserUnlocked(Mockito.any()) }.thenReturn(false) MobileCore.setApplication(app) mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, times(1)) From 13249d26a42c0df00ed9b36dcab818170d25aa56 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 12:20:25 -0700 Subject: [PATCH 05/22] Add support for debug events The SDKs often needs to change its standard business logic to enable debugging/testing workflows. We need an event structure that external systems can follow to generate an event that communicates to the listening SDK extension that it should operate in a debug mode - a mode outside of expected production behavior if one exists. This commit adds an eventSource "com.adobe.eventSource.debug" to identify such events. Further, it adds utility methods to identify the eventType and eventSource provided within the `data.debug` content of such events. --- code/core/api/core.api | 6 + .../adobe/marketing/mobile/EventSource.java | 2 + .../adobe/marketing/mobile/util/EventUtils.kt | 62 +++++++ .../marketing/mobile/util/EventUtilsTest.kt | 175 ++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt create mode 100644 code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt diff --git a/code/core/api/core.api b/code/core/api/core.api index 839104048..b26f1cf47 100644 --- a/code/core/api/core.api +++ b/code/core/api/core.api @@ -62,6 +62,7 @@ public final class com/adobe/marketing/mobile/EventSource { public static final field CONSENT_PREFERENCE Ljava/lang/String; public static final field CONTENT_COMPLETE Ljava/lang/String; public static final field CREATE_TRACKER Ljava/lang/String; + public static final field DEBUG Ljava/lang/String; public static final field ERROR_RESPONSE_CONTENT Ljava/lang/String; public static final field NONE Ljava/lang/String; public static final field OS Ljava/lang/String; @@ -1088,6 +1089,11 @@ public class com/adobe/marketing/mobile/util/EventDataUtils { public static fun immutableClone (Ljava/util/Map;)Ljava/util/Map; } +public final class com/adobe/marketing/mobile/util/EventUtils { + public static final fun getDebugEventSource (Lcom/adobe/marketing/mobile/Event;)Ljava/lang/String; + public static final fun getDebugEventType (Lcom/adobe/marketing/mobile/Event;)Ljava/lang/String; +} + public final class com/adobe/marketing/mobile/util/JSONUtils { public static fun isNullOrEmpty (Lorg/json/JSONArray;)Z public static fun isNullOrEmpty (Lorg/json/JSONObject;)Z diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/EventSource.java b/code/core/src/main/java/com/adobe/marketing/mobile/EventSource.java index a99bb85e2..b99aadefd 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/EventSource.java +++ b/code/core/src/main/java/com/adobe/marketing/mobile/EventSource.java @@ -46,4 +46,6 @@ private EventSource() {} public static final String CREATE_TRACKER = "com.adobe.eventSource.createTracker"; public static final String TRACK_MEDIA = "com.adobe.eventSource.trackMedia"; public static final String CONTENT_COMPLETE = "com.adobe.eventSource.contentComplete"; + + public static final String DEBUG = "com.adobe.eventSource.debug"; } diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt new file mode 100644 index 000000000..fc3f76ba0 --- /dev/null +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -0,0 +1,62 @@ +/* + Copyright 2024 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. +*/ + +@file:JvmName("EventUtils") // Allows Java callers to use EventUtils.<> instead of EventUtilsKt.<> + +package com.adobe.marketing.mobile.util + +import com.adobe.marketing.mobile.Event + +private const val KEY_EVENT_DATA_DEBUG = "debug" +private const val KEY_DEBUG_EVENT_TYPE = "eventType" +private const val KEY_DEBUG_EVENT_SOURCE = "eventSource" + +/** + * Returns the debug event type (identified by data.debug.eventType) from the event data if present, otherwise null. + * @return the debug event type if present, otherwise null + */ +fun Event.getDebugEventType(): String? { + if (eventData == null) { + return null + } + + val debugData = DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) + ?: return null + + val debugEventType = debugData[KEY_DEBUG_EVENT_TYPE] + + if (debugEventType !is String) { + return null + } + + return debugEventType +} + +/** + * Returns the debug event source (identified by data.debug.eventSource) from the event data if present, otherwise null. + * @return the debug event source if present, otherwise null + */ +fun Event.getDebugEventSource(): String? { + if (eventData == null) { + return null + } + + val debugData = DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) + ?: return null + + val debugEventSource = debugData[KEY_DEBUG_EVENT_SOURCE] + + if (debugEventSource !is String) { + return null + } + + return debugEventSource +} diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt new file mode 100644 index 000000000..f21b939ae --- /dev/null +++ b/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt @@ -0,0 +1,175 @@ +/* + Copyright 2024 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.util + +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.EventSource +import com.adobe.marketing.mobile.EventType +import org.junit.Assert.assertNull +import org.junit.Test +import kotlin.test.assertEquals + +class EventUtilsTest { + companion object { + private const val TEST_EVENT_NAME = "testName" + private const val TEST_EVENT_TYPE = "testType" + private const val TEST_EVENT_SOURCE = "testSource" + } + + @Test + fun `Test getDebugEventType returns null on no eventData`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE).build() + + assertNull(event.getDebugEventType()) + } + + @Test + fun `Test getDebugEventType returns null on no debug data`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData(mapOf("key" to "value")).build() + + assertNull(event.getDebugEventType()) + } + + @Test + fun `Test getDebugEventType returns null on invalid debug key`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData( + mapOf( + "_debug" to mapOf( + "eventType" to EventType.RULES_ENGINE, + "eventSource" to EventSource.RESET_COMPLETE + ) + ) + ).build() + + assertNull(event.getDebugEventType()) + } + + @Test + fun `Test getDebugEventType returns null when key is absent`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData( + mapOf( + "debug" to mapOf( + "eventSource" to EventSource.RESET_COMPLETE + ) + ) + ).build() + + assertNull(event.getDebugEventType()) + } + + @Test + fun `Test getDebugEventType returns debug event type`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData( + mapOf( + "debug" to mapOf( + "eventType" to EventType.RULES_ENGINE, + "eventSource" to EventSource.RESET_COMPLETE + ) + ) + ).build() + + assertEquals(EventType.RULES_ENGINE, event.getDebugEventType()) + } + + @Test + fun `Test getDebugEventType returns null when debugEvent type is not a string`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData( + mapOf( + "debug" to mapOf( + "eventType" to mapOf("1" to "Hi"), + "eventSource" to "testEventSource" + ) + ) + ).build() + + assertNull(event.getDebugEventType()) + } + + @Test + fun `Test getDebugEventSource returns null on no eventData`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE).build() + + assertNull(event.getDebugEventSource()) + } + + @Test + fun `Test getDebugEventSource returns null on no debug data`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData(mapOf("key" to "value")).build() + + assertNull(event.getDebugEventSource()) + } + + @Test + fun `Test getDebugEventSource returns null on invalid debug key`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData( + mapOf( + "_debug" to mapOf( + "eventType" to EventType.RULES_ENGINE, + "eventSource" to EventSource.RESET_COMPLETE + ) + ) + ).build() + + assertNull(event.getDebugEventSource()) + } + + @Test + fun `Test getDebugEventSource returns debug event source`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData( + mapOf( + "debug" to mapOf( + "eventType" to EventType.RULES_ENGINE, + "eventSource" to EventSource.RESET_COMPLETE + ) + ) + ).build() + + assertEquals(EventSource.RESET_COMPLETE, event.getDebugEventSource()) + } + + @Test + fun `Test getDebugEventSource returns null when debugEvent source is not a string`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData( + mapOf( + "debug" to mapOf( + "eventType" to "testEventType", + "eventSource" to mapOf("1" to "Hi") + ) + ) + ).build() + + assertNull(event.getDebugEventSource()) + } + + @Test + fun `Test getDebugEventSource returns null when key is absent`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData( + mapOf( + "debug" to mapOf( + "eventType" to EventType.RULES_ENGINE + ) + ) + ).build() + + assertNull(event.getDebugEventSource()) + } +} From 94ed6f6446b91eea2de6c24013ac298d7cfc2697 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 12:39:26 -0700 Subject: [PATCH 06/22] Fix unwanted line break --- .../src/main/java/com/adobe/marketing/mobile/EventSource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/EventSource.java b/code/core/src/main/java/com/adobe/marketing/mobile/EventSource.java index b99aadefd..f80be89d2 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/EventSource.java +++ b/code/core/src/main/java/com/adobe/marketing/mobile/EventSource.java @@ -46,6 +46,5 @@ private EventSource() {} public static final String CREATE_TRACKER = "com.adobe.eventSource.createTracker"; public static final String TRACK_MEDIA = "com.adobe.eventSource.trackMedia"; public static final String CONTENT_COMPLETE = "com.adobe.eventSource.contentComplete"; - public static final String DEBUG = "com.adobe.eventSource.debug"; } From 021b33647a6d37342c235719ab6f2d860cc9e074 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 12:55:22 -0700 Subject: [PATCH 07/22] Restrict Event extensions to debug event type+source --- .../adobe/marketing/mobile/util/EventUtils.kt | 22 +++++++++---------- .../marketing/mobile/util/EventUtilsTest.kt | 18 +++++++++++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt index fc3f76ba0..342d240ca 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -14,6 +14,8 @@ package com.adobe.marketing.mobile.util import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.EventSource +import com.adobe.marketing.mobile.EventType private const val KEY_EVENT_DATA_DEBUG = "debug" private const val KEY_DEBUG_EVENT_TYPE = "eventType" @@ -24,18 +26,16 @@ private const val KEY_DEBUG_EVENT_SOURCE = "eventSource" * @return the debug event type if present, otherwise null */ fun Event.getDebugEventType(): String? { - if (eventData == null) { - return null - } + if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null + + if (eventData == null) return null val debugData = DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) ?: return null val debugEventType = debugData[KEY_DEBUG_EVENT_TYPE] - if (debugEventType !is String) { - return null - } + if (debugEventType !is String) return null return debugEventType } @@ -45,18 +45,16 @@ fun Event.getDebugEventType(): String? { * @return the debug event source if present, otherwise null */ fun Event.getDebugEventSource(): String? { - if (eventData == null) { - return null - } + if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null + + if (eventData == null) return null val debugData = DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) ?: return null val debugEventSource = debugData[KEY_DEBUG_EVENT_SOURCE] - if (debugEventSource !is String) { - return null - } + if (debugEventSource !is String) return null return debugEventSource } diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt index f21b939ae..ee814f0a8 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt @@ -21,8 +21,15 @@ import kotlin.test.assertEquals class EventUtilsTest { companion object { private const val TEST_EVENT_NAME = "testName" - private const val TEST_EVENT_TYPE = "testType" - private const val TEST_EVENT_SOURCE = "testSource" + private const val TEST_EVENT_TYPE = EventType.SYSTEM + private const val TEST_EVENT_SOURCE = EventSource.DEBUG + } + + @Test + fun `Test getDebugEventType returns null on non debug event`() { + val event = Event.Builder(TEST_EVENT_NAME, EventType.HUB, EventSource.REQUEST_CONTENT).build() + + assertNull(event.getDebugEventType()) } @Test @@ -99,6 +106,13 @@ class EventUtilsTest { assertNull(event.getDebugEventType()) } + @Test + fun `Test getDebugEventSource returns null on non debug event`() { + val event = Event.Builder(TEST_EVENT_NAME, EventType.HUB, EventSource.REQUEST_CONTENT).build() + + assertNull(event.getDebugEventSource()) + } + @Test fun `Test getDebugEventSource returns null on no eventData`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE).build() From 961d94bb5799b765f9b5b7f9d69f4bc256cdc123 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 13:02:48 -0700 Subject: [PATCH 08/22] Minor code rearrangement Moved event data extraction logic to a common place. --- .../adobe/marketing/mobile/util/EventUtils.kt | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt index 342d240ca..b16c31b5f 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -26,15 +26,8 @@ private const val KEY_DEBUG_EVENT_SOURCE = "eventSource" * @return the debug event type if present, otherwise null */ fun Event.getDebugEventType(): String? { - if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null - - if (eventData == null) return null - - val debugData = DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) - ?: return null - + val debugData = getDebugEventData() ?: return null val debugEventType = debugData[KEY_DEBUG_EVENT_TYPE] - if (debugEventType !is String) return null return debugEventType @@ -45,16 +38,21 @@ fun Event.getDebugEventType(): String? { * @return the debug event source if present, otherwise null */ fun Event.getDebugEventSource(): String? { - if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null - - if (eventData == null) return null - - val debugData = DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) - ?: return null - + val debugData = getDebugEventData() ?: return null val debugEventSource = debugData[KEY_DEBUG_EVENT_SOURCE] - if (debugEventSource !is String) return null return debugEventSource } + +/** + * Returns the debug event data (identified by data.debug) from the event if present, otherwise null. + * returns null if the event is not a debug event or if the event data is not present. + */ +private fun Event.getDebugEventData(): Map? { + if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null + + if (eventData == null) return null + + return DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) ?: null +} From db93f8abff42adb316e1e9c5ffd36410dd8cd507 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 13:24:16 -0700 Subject: [PATCH 09/22] Fix Kdocs --- .../main/java/com/adobe/marketing/mobile/util/EventUtils.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt index b16c31b5f..93e00a1a8 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -47,7 +47,8 @@ fun Event.getDebugEventSource(): String? { /** * Returns the debug event data (identified by data.debug) from the event if present, otherwise null. - * returns null if the event is not a debug event or if the event data is not present. + * @return the content of "debug" key withing "Event.data" if present, + * null if the event is not a debug event or if the debug data does not exist */ private fun Event.getDebugEventData(): Map? { if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null From bf2ec1289838475966615c3cbad5be9faf1912f6 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 13:25:11 -0700 Subject: [PATCH 10/22] Fix typo --- .../src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt index 93e00a1a8..66ef2d4e5 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -47,7 +47,7 @@ fun Event.getDebugEventSource(): String? { /** * Returns the debug event data (identified by data.debug) from the event if present, otherwise null. - * @return the content of "debug" key withing "Event.data" if present, + * @return the content of "debug" key within "Event.data" if present, * null if the event is not a debug event or if the debug data does not exist */ private fun Event.getDebugEventData(): Map? { From d135c3dbb94e34f11247d5249d2f29a0a66cf586 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 14:58:24 -0700 Subject: [PATCH 11/22] Add tests where debug is not a map --- .../adobe/marketing/mobile/util/EventUtils.kt | 4 ++-- .../marketing/mobile/util/EventUtilsTest.kt | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt index 66ef2d4e5..272194076 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -22,7 +22,7 @@ private const val KEY_DEBUG_EVENT_TYPE = "eventType" private const val KEY_DEBUG_EVENT_SOURCE = "eventSource" /** - * Returns the debug event type (identified by data.debug.eventType) from the event data if present, otherwise null. + * Returns the debug event type (identified by debug.eventType) from the event data if present, otherwise null. * @return the debug event type if present, otherwise null */ fun Event.getDebugEventType(): String? { @@ -34,7 +34,7 @@ fun Event.getDebugEventType(): String? { } /** - * Returns the debug event source (identified by data.debug.eventSource) from the event data if present, otherwise null. + * Returns the debug event source (identified by debug.eventSource) from the event data if present, otherwise null. * @return the debug event source if present, otherwise null */ fun Event.getDebugEventSource(): String? { diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt index ee814f0a8..45a68995a 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt @@ -47,6 +47,14 @@ class EventUtilsTest { assertNull(event.getDebugEventType()) } + @Test + fun `Test getDebugEventType returns null when debug is not a map`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData(mapOf("debug" to "value")).build() + + assertNull(event.getDebugEventType()) + } + @Test fun `Test getDebugEventType returns null on invalid debug key`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) @@ -120,6 +128,14 @@ class EventUtilsTest { assertNull(event.getDebugEventSource()) } + @Test + fun `Test getDebugEventSource returns null when debug is not a map`() { + val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) + .setEventData(mapOf("debug" to "value")).build() + + assertNull(event.getDebugEventSource()) + } + @Test fun `Test getDebugEventSource returns null on no debug data`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) From ac37d1615de14a03707bbdb9d46a7398f92cb048 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 15:05:32 -0700 Subject: [PATCH 12/22] Add test for same activity resuming case --- .../services/ui/common/AEPPresentableTest.kt | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt index 1d2971457..a2d87b215 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt @@ -900,6 +900,87 @@ internal class AEPPresentableTest { } } + @Test + fun `Test that presentable not detached and re-attached when the same Activity instance resumes`() { + // setup + val presentationStateManager = PresentationStateManager() + val aepPresentableWithGatedDisplay = TestAEPPresentableGatedDisplay( + mockPresentation, + mockPresentationUtilityProvider, + mockPresentationDelegate, + mockAppLifecycleProvider, + presentationStateManager, + mockActivityCompatLifecycleUtils, + mockMainScope, + mockPresentationObserver, + conflictLogic = { visible -> + visible.any { it is Alert } + } + ) + + // simulate initial detached state + // `when`(mockPresentationStateManager.presentableState).thenReturn(mutableStateOf(Presentable.State.DETACHED)) + // simulate that the presentation delegate allows the presentation to be shown + `when`(mockPresentationDelegate.canShow(aepPresentableWithGatedDisplay)).thenReturn(true) + // simulate a valid activity being present + `when`(mockPresentationUtilityProvider.getCurrentActivity()).thenReturn(mockActivity) + // simulate no existing ComposeView being present + `when`(mockActivity.findViewById(aepPresentableWithGatedDisplay.contentIdentifier)).thenReturn( + null + ) + `when`(mockActivity.findViewById(eq(android.R.id.content))).thenReturn( + mockViewGroup + ) + + `when`(mockPresentationObserver.getVisiblePresentations()).thenReturn( + mutableListOf(mock(FloatingButton::class.java)) + ) + + runTest { + // Part-1: Simulate presentable shown for the first time + aepPresentableWithGatedDisplay.show() + + // verify that the presentation delegate is called because display is gated + verify(mockPresentationDelegate).canShow(aepPresentableWithGatedDisplay) + + // verify that the lifecycle provider is called to register the listener + verify(mockAppLifecycleProvider).registerListener(aepPresentableWithGatedDisplay) + + // Verify that the compose view is added to the viewgroup + val composeViewCaptor: KArgumentCaptor = argumentCaptor() + verify(mockViewGroup, times(1)).addView(composeViewCaptor.capture()) + + // verify that the listener, delegate, and state manager are notified of content show + verify(mockPresentationDelegate).onShow(aepPresentableWithGatedDisplay) + verify(mockPresentationListener).onShow(aepPresentableWithGatedDisplay) + assertTrue { presentationStateManager.presentableState.value == Presentable.State.VISIBLE } + + // verify that the presentation observer is notified of the new presentation + verify(mockPresentationObserver).onPresentationVisible(aepPresentableWithGatedDisplay.getPresentation()) + + // Part-2: Simulate same activity resuming when presentable is already shown, like + // going to background and coming back to foreground or a notification drawer being closed + + // We already asserted above the presentable has been attached. So return the captured ComposeView + `when`(mockActivity.findViewById(aepPresentableWithGatedDisplay.contentIdentifier)).thenReturn( + composeViewCaptor.firstValue + ) + + // Test the same activity instance resuming + aepPresentableWithGatedDisplay.onActivityResumed(mockActivity) + + // verify that the compose view is not removed from the viewgroup since it's the same activity + verify(mockViewGroup, never()).removeView(any()) + // verify that no changes are made to the viewgroup + verifyNoMoreInteractions(mockViewGroup) + + // verify that the listener, delegate are not re-notified because this is implicit + verifyNoMoreInteractions(mockPresentationDelegate) + verifyNoMoreInteractions(mockPresentationListener) + assertTrue { presentationStateManager.presentableState.value == Presentable.State.VISIBLE } + } + } + @Test fun `Test that AEPPresentable#onActivityResumed bails when presentable is HIDDEN`() { // setup From e9d071fd7061868cc5e0927d34e32517ae58af61 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 15:13:02 -0700 Subject: [PATCH 13/22] Remove commendted out line in test --- .../marketing/mobile/services/ui/common/AEPPresentableTest.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt index a2d87b215..70fb71d2c 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentableTest.kt @@ -832,8 +832,6 @@ internal class AEPPresentableTest { } ) - // simulate initial detached state - // `when`(mockPresentationStateManager.presentableState).thenReturn(mutableStateOf(Presentable.State.DETACHED)) // simulate that the presentation delegate allows the presentation to be shown `when`(mockPresentationDelegate.canShow(aepPresentableWithGatedDisplay)).thenReturn(true) // simulate a valid activity being present @@ -918,8 +916,6 @@ internal class AEPPresentableTest { } ) - // simulate initial detached state - // `when`(mockPresentationStateManager.presentableState).thenReturn(mutableStateOf(Presentable.State.DETACHED)) // simulate that the presentation delegate allows the presentation to be shown `when`(mockPresentationDelegate.canShow(aepPresentableWithGatedDisplay)).thenReturn(true) // simulate a valid activity being present From 946e6231489f9e0d71cee8e9046dc11af6fe2de5 Mon Sep 17 00:00:00 2001 From: yansong Date: Tue, 18 Jun 2024 17:54:49 -0500 Subject: [PATCH 14/22] address review comments - update logs --- .../java/com/adobe/marketing/mobile/MobileCore.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 51e78f441..0e2840256 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -105,12 +105,16 @@ public static void setApplication(@NonNull final Application application) { // Direct boot mode is supported on Android N and above if (VERSION.SDK_INT >= VERSION_CODES.N) { if (UserManagerCompat.isUserUnlocked(application)) { - Log.debug(CoreConstants.LOG_TAG, LOG_TAG, "Device is not in direct boot mode."); + Log.debug( + CoreConstants.LOG_TAG, + LOG_TAG, + "setApplication failed - device is unlocked."); } else { Log.error( CoreConstants.LOG_TAG, LOG_TAG, - "Device is in direct boot mode, SDK will not be initialized."); + "setApplication failed - device is in direct boot mode, SDK will not be" + + " initialized."); return; } } @@ -119,7 +123,7 @@ public static void setApplication(@NonNull final Application application) { Log.debug( CoreConstants.LOG_TAG, LOG_TAG, - "Ignoring as setApplication was already called."); + "setApplication failed - ignoring as setApplication was already called."); return; } From 109d945e788668802e982e1d8c4b4164ec9f3f9e Mon Sep 17 00:00:00 2001 From: yansong Date: Tue, 18 Jun 2024 18:19:19 -0500 Subject: [PATCH 15/22] update logs --- .../src/phone/java/com/adobe/marketing/mobile/MobileCore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 0e2840256..3bc9433b3 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -108,7 +108,7 @@ public static void setApplication(@NonNull final Application application) { Log.debug( CoreConstants.LOG_TAG, LOG_TAG, - "setApplication failed - device is unlocked."); + "setApplication - device is unlocked, initializing the SDK."); } else { Log.error( CoreConstants.LOG_TAG, From 326ee78331cd8f963ec1e4dfe7db0308c7509dd9 Mon Sep 17 00:00:00 2001 From: yansong Date: Tue, 18 Jun 2024 18:25:32 -0500 Subject: [PATCH 16/22] update logs --- .../src/phone/java/com/adobe/marketing/mobile/MobileCore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 3bc9433b3..6a7a8e784 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -108,7 +108,7 @@ public static void setApplication(@NonNull final Application application) { Log.debug( CoreConstants.LOG_TAG, LOG_TAG, - "setApplication - device is unlocked, initializing the SDK."); + "setApplication - device is unlocked and not in direct boot mode, initializing the SDK."); } else { Log.error( CoreConstants.LOG_TAG, From acdb82f9969c91e5fd421d6f43be882e162f26c2 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 18 Jun 2024 16:33:09 -0700 Subject: [PATCH 17/22] Trigger CircleCI From 5534eb7236792adc297e586399911d78723a1b9d Mon Sep 17 00:00:00 2001 From: yansong Date: Tue, 18 Jun 2024 20:20:04 -0500 Subject: [PATCH 18/22] fix format --- .../src/phone/java/com/adobe/marketing/mobile/MobileCore.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 6a7a8e784..33d06f471 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -108,7 +108,8 @@ public static void setApplication(@NonNull final Application application) { Log.debug( CoreConstants.LOG_TAG, LOG_TAG, - "setApplication - device is unlocked and not in direct boot mode, initializing the SDK."); + "setApplication - device is unlocked and not in direct boot mode," + + " initializing the SDK."); } else { Log.error( CoreConstants.LOG_TAG, From 4ff70dc6765d3cec087712cdc17c2abbf4cc1585 Mon Sep 17 00:00:00 2001 From: prudrabhat Date: Wed, 19 Jun 2024 18:12:38 +0000 Subject: [PATCH 19/22] Update versions [Core-3.1.0] --- .../java/com/adobe/marketing/mobile/internal/CoreConstants.kt | 2 +- code/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 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 9db2f03af..18ac47ce8 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 = "3.0.2" + const val VERSION = "3.1.0" object EventDataKeys { /** diff --git a/code/gradle.properties b/code/gradle.properties index 2804dfe45..46dc34c44 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -5,7 +5,7 @@ android.useAndroidX=true #Maven artifacts #Core extension -coreExtensionVersion=3.0.2 +coreExtensionVersion=3.1.0 coreExtensionName=core coreMavenRepoName=AdobeMobileCoreSdk coreMavenRepoDescription=Android Core Extension for Adobe Mobile Marketing From ef82218e0b997ee4f793f78ddef0108adb53c9cc Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Thu, 20 Jun 2024 11:15:55 -0700 Subject: [PATCH 20/22] Switch Event extension functions to properties --- .../adobe/marketing/mobile/util/EventUtils.kt | 34 +++++----- .../marketing/mobile/util/EventUtilsTest.kt | 64 +++++++++---------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt index 272194076..b4f424ba9 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -22,28 +22,28 @@ private const val KEY_DEBUG_EVENT_TYPE = "eventType" private const val KEY_DEBUG_EVENT_SOURCE = "eventSource" /** - * Returns the debug event type (identified by debug.eventType) from the event data if present, otherwise null. - * @return the debug event type if present, otherwise null + * The debug event type (identified by debug.eventType) from the event data if present, otherwise null */ -fun Event.getDebugEventType(): String? { - val debugData = getDebugEventData() ?: return null - val debugEventType = debugData[KEY_DEBUG_EVENT_TYPE] - if (debugEventType !is String) return null +val Event.debugEventType: String? + get() { + val debugData = getDebugEventData() ?: return null + val eventType = debugData[KEY_DEBUG_EVENT_TYPE] + if (eventType !is String) return null - return debugEventType -} + return eventType + } /** - * Returns the debug event source (identified by debug.eventSource) from the event data if present, otherwise null. - * @return the debug event source if present, otherwise null + * The debug event source (identified by debug.eventSource) from the event data if present, otherwise null. */ -fun Event.getDebugEventSource(): String? { - val debugData = getDebugEventData() ?: return null - val debugEventSource = debugData[KEY_DEBUG_EVENT_SOURCE] - if (debugEventSource !is String) return null - - return debugEventSource -} +val Event.debugEventSource: String? + get() { + val debugData = getDebugEventData() ?: return null + val eventSource = debugData[KEY_DEBUG_EVENT_SOURCE] + if (eventSource !is String) return null + + return eventSource + } /** * Returns the debug event data (identified by data.debug) from the event if present, otherwise null. diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt index 45a68995a..dff2739c1 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/util/EventUtilsTest.kt @@ -26,37 +26,37 @@ class EventUtilsTest { } @Test - fun `Test getDebugEventType returns null on non debug event`() { + fun `Test debugEventType returns null on non debug event`() { val event = Event.Builder(TEST_EVENT_NAME, EventType.HUB, EventSource.REQUEST_CONTENT).build() - assertNull(event.getDebugEventType()) + assertNull(event.debugEventType) } @Test - fun `Test getDebugEventType returns null on no eventData`() { + fun `Test debugEventType returns null on no eventData`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE).build() - assertNull(event.getDebugEventType()) + assertNull(event.debugEventType) } @Test - fun `Test getDebugEventType returns null on no debug data`() { + fun `Test debugEventType returns null on no debug data`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData(mapOf("key" to "value")).build() - assertNull(event.getDebugEventType()) + assertNull(event.debugEventType) } @Test - fun `Test getDebugEventType returns null when debug is not a map`() { + fun `Test debugEventType returns null when debug is not a map`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData(mapOf("debug" to "value")).build() - assertNull(event.getDebugEventType()) + assertNull(event.debugEventType) } @Test - fun `Test getDebugEventType returns null on invalid debug key`() { + fun `Test debugEventType returns null on invalid debug key`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData( mapOf( @@ -67,11 +67,11 @@ class EventUtilsTest { ) ).build() - assertNull(event.getDebugEventType()) + assertNull(event.debugEventType) } @Test - fun `Test getDebugEventType returns null when key is absent`() { + fun `Test debugEventType returns null when key is absent`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData( mapOf( @@ -81,11 +81,11 @@ class EventUtilsTest { ) ).build() - assertNull(event.getDebugEventType()) + assertNull(event.debugEventType) } @Test - fun `Test getDebugEventType returns debug event type`() { + fun `Test debugEventType returns debug event type`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData( mapOf( @@ -96,11 +96,11 @@ class EventUtilsTest { ) ).build() - assertEquals(EventType.RULES_ENGINE, event.getDebugEventType()) + assertEquals(EventType.RULES_ENGINE, event.debugEventType) } @Test - fun `Test getDebugEventType returns null when debugEvent type is not a string`() { + fun `Test debugEventType returns null when debugEvent type is not a string`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData( mapOf( @@ -111,41 +111,41 @@ class EventUtilsTest { ) ).build() - assertNull(event.getDebugEventType()) + assertNull(event.debugEventType) } @Test - fun `Test getDebugEventSource returns null on non debug event`() { + fun `Test debugEventSource returns null on non debug event`() { val event = Event.Builder(TEST_EVENT_NAME, EventType.HUB, EventSource.REQUEST_CONTENT).build() - assertNull(event.getDebugEventSource()) + assertNull(event.debugEventSource) } @Test - fun `Test getDebugEventSource returns null on no eventData`() { + fun `Test debugEventSource returns null on no eventData`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE).build() - assertNull(event.getDebugEventSource()) + assertNull(event.debugEventSource) } @Test - fun `Test getDebugEventSource returns null when debug is not a map`() { + fun `Test debugEventSource returns null when debug is not a map`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData(mapOf("debug" to "value")).build() - assertNull(event.getDebugEventSource()) + assertNull(event.debugEventSource) } @Test - fun `Test getDebugEventSource returns null on no debug data`() { + fun `Test debugEventSource returns null on no debug data`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData(mapOf("key" to "value")).build() - assertNull(event.getDebugEventSource()) + assertNull(event.debugEventSource) } @Test - fun `Test getDebugEventSource returns null on invalid debug key`() { + fun `Test debugEventSource returns null on invalid debug key`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData( mapOf( @@ -156,11 +156,11 @@ class EventUtilsTest { ) ).build() - assertNull(event.getDebugEventSource()) + assertNull(event.debugEventSource) } @Test - fun `Test getDebugEventSource returns debug event source`() { + fun `Test debugEventSource returns debug event source`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData( mapOf( @@ -171,11 +171,11 @@ class EventUtilsTest { ) ).build() - assertEquals(EventSource.RESET_COMPLETE, event.getDebugEventSource()) + assertEquals(EventSource.RESET_COMPLETE, event.debugEventSource) } @Test - fun `Test getDebugEventSource returns null when debugEvent source is not a string`() { + fun `Test debugEventSource returns null when debugEvent source is not a string`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData( mapOf( @@ -186,11 +186,11 @@ class EventUtilsTest { ) ).build() - assertNull(event.getDebugEventSource()) + assertNull(event.debugEventSource) } @Test - fun `Test getDebugEventSource returns null when key is absent`() { + fun `Test debugEventSource returns null when key is absent`() { val event = Event.Builder(TEST_EVENT_NAME, TEST_EVENT_TYPE, TEST_EVENT_SOURCE) .setEventData( mapOf( @@ -200,6 +200,6 @@ class EventUtilsTest { ) ).build() - assertNull(event.getDebugEventSource()) + assertNull(event.debugEventSource) } } From ae60e51749b5b214be4d93f5badf48bb7b640027 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Thu, 20 Jun 2024 11:31:17 -0700 Subject: [PATCH 21/22] Make property evaludation simpler --- .../adobe/marketing/mobile/util/EventUtils.kt | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt index b4f424ba9..f8729ae77 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -25,35 +25,24 @@ private const val KEY_DEBUG_EVENT_SOURCE = "eventSource" * The debug event type (identified by debug.eventType) from the event data if present, otherwise null */ val Event.debugEventType: String? - get() { - val debugData = getDebugEventData() ?: return null - val eventType = debugData[KEY_DEBUG_EVENT_TYPE] - if (eventType !is String) return null - - return eventType - } + get() = debugEventData?.get(KEY_DEBUG_EVENT_TYPE) as? String /** * The debug event source (identified by debug.eventSource) from the event data if present, otherwise null. */ val Event.debugEventSource: String? - get() { - val debugData = getDebugEventData() ?: return null - val eventSource = debugData[KEY_DEBUG_EVENT_SOURCE] - if (eventSource !is String) return null - - return eventSource - } + get() = debugEventData?.get(KEY_DEBUG_EVENT_SOURCE) as? String /** * Returns the debug event data (identified by data.debug) from the event if present, otherwise null. * @return the content of "debug" key within "Event.data" if present, * null if the event is not a debug event or if the debug data does not exist */ -private fun Event.getDebugEventData(): Map? { - if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null +val Event.debugEventData: Map? + get() { + if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null - if (eventData == null) return null + if (eventData == null) return null - return DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) ?: null -} + return DataReader.optTypedMap(Any::class.java, eventData, KEY_EVENT_DATA_DEBUG, null) ?: null + } From 3efc840b4a32e90413598fdf48dcdd3dca08d10f Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Thu, 20 Jun 2024 12:01:25 -0700 Subject: [PATCH 22/22] Make debugEventData private --- .../src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt index f8729ae77..b3d2bdd07 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/util/EventUtils.kt @@ -38,7 +38,7 @@ val Event.debugEventSource: String? * @return the content of "debug" key within "Event.data" if present, * null if the event is not a debug event or if the debug data does not exist */ -val Event.debugEventData: Map? +private val Event.debugEventData: Map? get() { if (type != EventType.SYSTEM || source != EventSource.DEBUG) return null