diff --git a/code/core/api/core.api b/code/core/api/core.api index 5ab422d6d..1067b479b 100644 --- a/code/core/api/core.api +++ b/code/core/api/core.api @@ -910,6 +910,7 @@ public class com/adobe/marketing/mobile/services/ui/MessageFragment : android/ap public fun onActivityCreated (Landroid/os/Bundle;)V public fun onAttach (Landroid/content/Context;)V public fun onCreate (Landroid/os/Bundle;)V + public fun onCreateDialog (Landroid/os/Bundle;)Landroid/app/Dialog; public fun onDestroyView ()V public fun onDetach ()V public fun onTouch (Landroid/view/View;Landroid/view/MotionEvent;)Z 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 af0cb70f0..f85bfddd9 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.5.1" + const val VERSION = "2.6.0" object EventDataKeys { /** diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/services/ui/MessageSettings.java b/code/core/src/main/java/com/adobe/marketing/mobile/services/ui/MessageSettings.java index 410b9d28c..4103875d3 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/services/ui/MessageSettings.java +++ b/code/core/src/main/java/com/adobe/marketing/mobile/services/ui/MessageSettings.java @@ -65,7 +65,7 @@ public enum MessageGesture { SWIPE_DOWN("swipeDown"), SWIPE_LEFT("swipeLeft"), SWIPE_RIGHT("swipeRight"), - BACKGROUND_TAP("backgroundTap"); + BACKGROUND_TAP("tapBackground"); private String name; private static final Map gestureStringToGestureEnumMap; 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 817dc45a4..9ead536af 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 @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.services.ui; +import android.app.Activity; import android.app.Dialog; import android.app.FragmentTransaction; import android.content.Context; @@ -25,6 +26,7 @@ import android.view.ViewGroup; import android.webkit.WebView; import android.widget.FrameLayout; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.cardview.widget.CardView; import androidx.fragment.app.DialogFragment; @@ -172,7 +174,66 @@ public void onCreate(final Bundle savedInstanceState) { .getApplicationContext(), webViewGestureListener); - setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Translucent_NoTitleBar); + setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Translucent_NoTitleBar); + } + + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final Activity parentActivity = getActivity(); + final int fragmentTheme = getTheme(); + final Dialog defaultDialog = super.onCreateDialog(savedInstanceState); + + if (parentActivity == null || message == null) { + Log.trace( + ServiceConstants.LOG_TAG, + TAG, + "%s (%s), returning a default Dialog object.", + parentActivity == null ? "Parent Activity" : "Message", + UNEXPECTED_NULL_VALUE); + return defaultDialog; + } + + final MessageSettings messageSettings = message.getMessageSettings(); + if (messageSettings == null) { + Log.trace( + ServiceConstants.LOG_TAG, + TAG, + "%s (MessageSettings), returning a default Dialog object.", + UNEXPECTED_NULL_VALUE); + return defaultDialog; + } + + final boolean uiTakeoverEnabled = messageSettings.getUITakeover(); + return new Dialog(parentActivity, fragmentTheme) { + @Override + public boolean onTouchEvent(@NonNull final MotionEvent motionEvent) { + if (!uiTakeoverEnabled) { + // only log action up or down events as this logging can get quite + // spammy + if (motionEvent.getAction() == MotionEvent.ACTION_UP) { + Log.trace( + ServiceConstants.LOG_TAG, + TAG, + "UI takeover is false, passing the touch event to the parent" + + " activity."); + } + return parentActivity.dispatchTouchEvent(motionEvent); + } else { + // load any behavior url strings on action up only as a touch consists of + // two motion events: an action down and an action up event + if (motionEvent.getAction() == MotionEvent.ACTION_UP) { + Log.trace( + ServiceConstants.LOG_TAG, + TAG, + "UI takeover is true, parent activity UI is inaccessible." + + " Processing defined background tap behaviors."); + webViewGestureListener.handleGesture(MessageGesture.BACKGROUND_TAP); + return true; + } + } + return super.onTouchEvent(motionEvent); + } + }; } @Override @@ -234,13 +295,14 @@ public void onDetach() { @Override public boolean onTouch(final View view, final MotionEvent motionEvent) { + final String viewName = view.getClass().getSimpleName(); if (message == null) { Log.debug( ServiceConstants.LOG_TAG, TAG, "%s (AEPMessage), unable to handle the touch event on %s.", UNEXPECTED_NULL_VALUE, - view.getClass().getSimpleName()); + viewName); return true; } @@ -251,7 +313,7 @@ public boolean onTouch(final View view, final MotionEvent motionEvent) { TAG, "%s (WebView), unable to handle the touch event on %s.", UNEXPECTED_NULL_VALUE, - view.getClass().getSimpleName()); + viewName); return true; } @@ -262,37 +324,7 @@ public boolean onTouch(final View view, final MotionEvent motionEvent) { TAG, "%s (MessageSettings), unable to handle the touch event on %s.", UNEXPECTED_NULL_VALUE, - view.getClass().getSimpleName()); - return true; - } - - final int motionEventAction = motionEvent.getAction(); - - // determine if the tap occurred outside the webview - if ((motionEventAction == MotionEvent.ACTION_DOWN - || motionEventAction == MotionEvent.ACTION_BUTTON_PRESS) - && view.getId() != webView.getId()) { - Log.trace( - ServiceConstants.LOG_TAG, - TAG, - "Detected tap on %s", - view.getClass().getSimpleName()); - - final boolean uiTakeoverEnabled = messageSettings.getUITakeover(); - - // if ui takeover is false, dismiss the message - if (!uiTakeoverEnabled) { - Log.trace( - ServiceConstants.LOG_TAG, - TAG, - "UI takeover is false, dismissing the message."); - webViewGestureListener.handleGesture(MessageGesture.BACKGROUND_TAP); - // perform the tap to allow interaction with ui elements outside the webview - return view.onTouchEvent(motionEvent); - } - - // ui takeover is true, consume the tap and ignore it - Log.trace(ServiceConstants.LOG_TAG, TAG, "UI takeover is true, ignoring the tap."); + viewName); return true; } @@ -343,10 +375,6 @@ private void addListeners() { final Dialog dialog = getDialog(); if (dialog != null) { - // set this fragment onTouchListener to dismiss the IAM if a touch occurs on the decor - // view - dialog.getWindow().getDecorView().setOnTouchListener(this); - // handle on back pressed to dismiss the message dialog.setOnKeyListener( (dialogInterface, keyCode, event) -> { @@ -368,7 +396,6 @@ private void removeListeners() { final Dialog dialog = getDialog(); if (dialog != null) { - dialog.getWindow().getDecorView().setOnTouchListener(null); dialog.setOnKeyListener(null); } } @@ -395,6 +422,16 @@ private void applyBackdropColor() { return; } + final boolean uiTakeoverEnabled = messageSettings.getUITakeover(); + // we don't want to dim the background if ui takeover is disabled + if (!uiTakeoverEnabled) { + Log.trace( + ServiceConstants.LOG_TAG, + TAG, + "Not applying background alpha, ui takeover is disabled."); + return; + } + final Dialog dialog = getDialog(); if (dialog != null) { diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/WebViewGestureListener.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/WebViewGestureListener.java index 6cdce77a8..ad74350f2 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/WebViewGestureListener.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/WebViewGestureListener.java @@ -19,7 +19,6 @@ import androidx.cardview.widget.CardView; import com.adobe.marketing.mobile.services.Log; import com.adobe.marketing.mobile.services.ServiceConstants; -import com.adobe.marketing.mobile.services.ui.MessageSettings.MessageAnimation; import com.adobe.marketing.mobile.services.ui.MessageSettings.MessageGesture; import com.adobe.marketing.mobile.util.StringUtils; @@ -114,24 +113,15 @@ public boolean onFling( } /** - * Generates a dismiss animation using the {@link ObjectAnimator}. The {@link + * Generates a dismiss animation if needed using the {@link ObjectAnimator}. The {@link * androidx.cardview.widget.CardView} frame will be dismissed at the direction of the detected - * swipe {@link MessageGesture}. If the in-app message was dismissed via a {@code - * MessageGesture.BACKGROUND_TAP} then the {@code CardView} will be dismissed using the - * dismissal {@link MessageAnimation} specified in the {@link MessageSettings}. + * swipe {@link MessageGesture}. Background tap gestures will not dismiss the message but any + * associated behavior will be * * @param gesture The detected swipe {@code MessageGesture} that occurred. */ public void handleGesture(final MessageGesture gesture) { - if (gesture.equals(MessageSettings.MessageGesture.BACKGROUND_TAP)) { - // we are handling a background tap. message will be dismissed via the dismiss animation - // specified in the MessageSettings. - dismissMessage(gesture, false); - return; - } - final AEPMessage message = parentFragment.getAEPMessage(); - if (message == null) { Log.debug( ServiceConstants.LOG_TAG, @@ -163,7 +153,21 @@ public void handleGesture(final MessageGesture gesture) { webViewFrame.getTop(), -message.parentViewHeight); break; - default: // default, dismiss to bottom if not a background tap + case BACKGROUND_TAP: + animation = null; + // we are handling a background tap. handle the background tap interaction behavior + // specified (if any). + // if we have no defined background tap behavior then we do not want to dismiss the + // message. + final String behavior = + parentFragment.gestures == null + ? null + : parentFragment.gestures.get(gesture); + if (!StringUtils.isNullOrEmpty(behavior)) { + message.listener.overrideUrlLoad(message, behavior); + } + break; + default: // default, dismiss to bottom animation = ObjectAnimator.ofFloat( webViewFrame, "y", webViewFrame.getTop(), message.parentViewHeight); 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 ff017ed56..29433c050 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.5.1" + private var EXTENSION_VERSION = "2.6.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 d86714b13..8cfe145a2 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.5.1" + private var EXTENSION_VERSION = "2.6.0" @Mock private lateinit var mockServiceProvider: ServiceProvider diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/MessageFragmentTests.java b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/MessageFragmentTests.java index bd6f71f0d..b8bdbaff6 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/MessageFragmentTests.java +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/MessageFragmentTests.java @@ -148,47 +148,4 @@ public void testOnTouchListener_MessageIsNull_ThenTouchEventIsIgnored() { ArgumentMatchers.any(AEPMessage.class), ArgumentMatchers.anyString()); Assert.assertTrue(eventProcessed); } - - @Test - public void - testOnTouchListener_TouchOccurredOutsideWebview_And_UITakeoverFalse_ThenMessageDismissed() { - // setup - messageFragment.webViewGestureListener = mockWebViewGestureListener; - messageFragment.gestureDetector = mockGestureDetector; - Mockito.when(mockAEPMessageSettings.getUITakeover()).thenReturn(false); - Mockito.when(mockWebView.getId()).thenReturn(12345); - Mockito.when(mockViewGroup.getId()).thenReturn(67890); - Mockito.when(mockAEPMessage.getWebView()).thenReturn(mockWebView); - // call onCreate to setup the gestures - messageFragment.onCreate(mockSavedInstanceState); - // test - boolean eventProcessed = messageFragment.onTouch(mockViewGroup, mockMotionEvent); - // verify - Mockito.verify(mockFullscreenMessageDelegate, Mockito.times(1)) - .overrideUrlLoad( - ArgumentMatchers.any(AEPMessage.class), ArgumentMatchers.anyString()); - // expect false because the touch event was handled by the rootview - Assert.assertFalse(eventProcessed); - } - - @Test - public void - testOnTouchListener_TouchOccurredOutsideWebview_And_UITakeoverTrue_ThenMessageNotDismissed() { - // setup - messageFragment.webViewGestureListener = mockWebViewGestureListener; - messageFragment.gestureDetector = mockGestureDetector; - Mockito.when(mockAEPMessageSettings.getUITakeover()).thenReturn(true); - Mockito.when(mockWebView.getId()).thenReturn(12345); - Mockito.when(mockViewGroup.getId()).thenReturn(67890); - Mockito.when(mockAEPMessage.getWebView()).thenReturn(mockWebView); - // call onCreate to setup the gestures - messageFragment.onCreate(mockSavedInstanceState); - // test - boolean eventProcessed = messageFragment.onTouch(mockViewGroup, mockMotionEvent); - // verify - Mockito.verify(mockFullscreenMessageDelegate, Mockito.times(0)) - .overrideUrlLoad( - ArgumentMatchers.any(AEPMessage.class), ArgumentMatchers.anyString()); - Assert.assertTrue(eventProcessed); - } } diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/WebViewGestureListenerTests.java b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/WebViewGestureListenerTests.java index 856e29c37..6e9a52030 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/WebViewGestureListenerTests.java +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/WebViewGestureListenerTests.java @@ -248,7 +248,8 @@ public void testOnFling_VerticalSwipeDown_And_FlingVelocityOverThreshold_Then_Me } @Test - public void testHandleGesture_BackgroundTap_Then_MessageDismissed() { + public void + testHandleGesture_BackgroundTap_BackgroundTapBehaviorDefined_Then_MessageDismissed() { // setup Mockito.when(mockMotionEvent.getX()).thenReturn(0f); Mockito.when(mockMotionEvent2.getX()).thenReturn(0f); @@ -262,4 +263,23 @@ public void testHandleGesture_BackgroundTap_Then_MessageDismissed() { .overrideUrlLoad( ArgumentMatchers.any(AEPMessage.class), ArgumentMatchers.anyString()); } + + @Test + public void + testHandleGesture_BackgroundTap_BackgroundTapBehaviorNotDefined_Then_MessageNotDismissed() { + // setup + gestureMap.remove(MessageGesture.BACKGROUND_TAP); + mockMessageFragment.gestures = gestureMap; + Mockito.when(mockMotionEvent.getX()).thenReturn(0f); + Mockito.when(mockMotionEvent2.getX()).thenReturn(0f); + Mockito.when(mockMotionEvent.getY()).thenReturn(0f); + Mockito.when(mockMotionEvent2.getY()).thenReturn(-300.0f); + // test + gestureListener.handleGesture(MessageGesture.BACKGROUND_TAP); + // verify + Assert.assertFalse(mockMessageFragment.isDismissedWithGesture()); + Mockito.verify(mockFullscreenMessageDelegate, Mockito.times(0)) + .overrideUrlLoad( + ArgumentMatchers.any(AEPMessage.class), ArgumentMatchers.anyString()); + } } diff --git a/code/gradle.properties b/code/gradle.properties index 090ed3cf9..ab8f6d40f 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -6,7 +6,7 @@ android.useAndroidX=true # #Maven artifacts #Core extension -coreExtensionVersion=2.5.1 +coreExtensionVersion=2.6.0 coreExtensionName=core coreExtensionAARName=core-phone-release.aar coreMavenRepoName=AdobeMobileCoreSdk