Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Merge staging into main for Core v3.1.0 #686

Merged
merged 30 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b4d0c20
Set webview container background to be transparent
prudrabhat Jun 10, 2024
d500c8c
Merge pull request #680 from prudrabhat/webview_bg_color
praveek Jun 11, 2024
658223c
Detach presentables before re-attaching to top Activity
prudrabhat Jun 17, 2024
413233a
Not to initialize SDK in Android direct boot mode
yangyansong-adbe Jun 18, 2024
b23ef03
update tests
yangyansong-adbe Jun 18, 2024
13249d2
Add support for debug events
prudrabhat Jun 18, 2024
94ed6f6
Fix unwanted line break
prudrabhat Jun 18, 2024
021b336
Restrict Event extensions to debug event type+source
prudrabhat Jun 18, 2024
961d94b
Minor code rearrangement
prudrabhat Jun 18, 2024
db93f8a
Fix Kdocs
prudrabhat Jun 18, 2024
bf2ec12
Fix typo
prudrabhat Jun 18, 2024
d135c3d
Add tests where debug is not a map
prudrabhat Jun 18, 2024
ac37d16
Add test for same activity resuming case
prudrabhat Jun 18, 2024
e9d071f
Remove commendted out line in test
prudrabhat Jun 18, 2024
946e623
address review comments - update logs
yangyansong-adbe Jun 18, 2024
109d945
update logs
yangyansong-adbe Jun 18, 2024
326ee78
update logs
yangyansong-adbe Jun 18, 2024
acdb82f
Trigger CircleCI
prudrabhat Jun 18, 2024
d044b40
Merge pull request #681 from prudrabhat/iam_attachment
prudrabhat Jun 19, 2024
5534eb7
fix format
yangyansong-adbe Jun 19, 2024
90638b4
Merge pull request #682 from yangyansong-adbe/direct_boot
yangyansong-adbe Jun 19, 2024
ee20486
Merge pull request #683 from prudrabhat/debug_event_support
prudrabhat Jun 19, 2024
4ff70dc
Update versions [Core-3.1.0]
prudrabhat Jun 19, 2024
d080bb8
Merge pull request #684 from adobe/create-pull-request/patch
prudrabhat Jun 19, 2024
135dd43
Merge pull request #685 from adobe/dev
prudrabhat Jun 19, 2024
ef82218
Switch Event extension functions to properties
prudrabhat Jun 20, 2024
ae60e51
Make property evaludation simpler
prudrabhat Jun 20, 2024
3efc840
Make debugEventData private
prudrabhat Jun 20, 2024
83f5734
Merge pull request #687 from prudrabhat/debug_event_prop
prudrabhat Jun 20, 2024
449a92b
Merge pull request #688 from adobe/dev
prudrabhat Jun 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions code/core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions code/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
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
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"
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() = 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() = 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 val Event.debugEventData: Map<String, Any?>?
get() {
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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -82,31 +86,49 @@ 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.
*
* <p>NOTE: This method should be called right after the app starts, so it gives the SDK all the
* contexts it needed.
*
* @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,
"setApplication - device is unlocked and not in direct boot mode,"
+ " initializing the SDK.");
} else {
Log.error(
CoreConstants.LOG_TAG,
LOG_TAG,
"setApplication failed - device is in direct boot mode, SDK will not be"
+ " initialized.");
return;
}
}

if (sdkInitializedWithContext.getAndSet(true)) {
Log.debug(
CoreConstants.LOG_TAG,
LOG_TAG,
"Ignoring as setApplication was already called.");
"setApplication failed - ignoring as setApplication was already called.");
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ internal abstract class AEPPresentable<T : Presentation<T>> :

@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<Activity?> = 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]
Expand Down Expand Up @@ -241,6 +249,22 @@ internal abstract class AEPPresentable<T : Presentation<T>> :
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)
}
}
Expand Down Expand Up @@ -327,6 +351,10 @@ internal abstract class AEPPresentable<T : Presentation<T>> :
composeView.id = contentIdentifier
val rootViewGroup = activityToAttach.findViewById<ViewGroup>(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,
Expand Down Expand Up @@ -373,6 +401,20 @@ internal abstract class AEPPresentable<T : Presentation<T>> :
}
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.")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
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 `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 ->
mockedStaticUserManagerCompat.`when`<Any> { 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 `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`<Any> { 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 `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`<Any> { 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())
}
}
Loading
Loading