From 06fac7981e48c5103badc8ce6ae42715cf59bd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Herculano?= <228650+andresilveirah@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:39:50 +0100 Subject: [PATCH 1/6] skip signing for publishToMavenLocal task --- cmplibrary/build.gradle.kts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmplibrary/build.gradle.kts b/cmplibrary/build.gradle.kts index 14e6ac455..f00e61e9e 100644 --- a/cmplibrary/build.gradle.kts +++ b/cmplibrary/build.gradle.kts @@ -99,7 +99,10 @@ apply(from = "${project.rootDir.path}/gradleutils/ktlint_utils.gradle") mavenPublishing { coordinates(group.toString(), "cmplibrary", versionLib) publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true) - signAllPublications() + // skip signing for publishToMavenLocal task + if (!gradle.startParameter.taskNames.any { it.contains("publishToMavenLocal") }) { + signAllPublications() + } pom { name = "Sourcepoint Android CMP" description = "The internal Network & Data layers used by our mobile SDKs" From e2615fab352d31d529e3933600715e7763175ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Herculano?= <228650+andresilveirah@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:10:24 +0100 Subject: [PATCH 2/6] give more accessibility focus to message webview --- .../cmplibrary/SpConsentLibMobileCore.kt | 29 ++++++++++++++----- .../mobile_core/SPConsentWebView.kt | 7 +++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt index d135e8d1f..56385606d 100644 --- a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt +++ b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt @@ -6,8 +6,11 @@ import android.content.Context import android.os.Handler import android.os.Looper import android.view.View +import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityManager import com.sourcepoint.cmplibrary.consent.CustomConsentClient import com.sourcepoint.cmplibrary.data.network.connection.ConnectionManager import com.sourcepoint.cmplibrary.data.network.util.CampaignType @@ -47,9 +50,10 @@ import com.sourcepoint.mobile_core.network.json import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString import org.json.JSONObject import java.lang.ref.WeakReference +import java.util.WeakHashMap +import kotlin.getValue fun launch(task: suspend () -> Unit) { CoroutineScope(Dispatchers.Default).launch { task() } @@ -76,6 +80,10 @@ class SpConsentLibMobileCore( private val userData: SPUserData get() = coordinator.userData private val spConsents: SPConsents get() = SPConsents(userData) + private val accessibilityManater by lazy { + context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager + } + private var messageUI: SPMessageUI? = null fun getOrCreateMessageUI(): SPMessageUI { messageUI = messageUI ?: SPConsentWebView.create( @@ -284,15 +292,22 @@ class SpConsentLibMobileCore( } override fun showView(view: View) { - view.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) - view.bringToFront() - view.requestLayout() - view.requestFocus() - mainView?.addView(view) + mainView?.let { parentView -> + view.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) + parentView.addView(view) + view.bringToFront() + view.requestLayout() + view.requestFocus() + if (accessibilityManater.isEnabled && accessibilityManater.isTouchExplorationEnabled) { + view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) + } + } } override fun removeView(view: View) { - mainView?.removeView(view) + mainView?.let { parentView -> + parentView.removeView(view) + } } override fun dismissMessage() { diff --git a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/mobile_core/SPConsentWebView.kt b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/mobile_core/SPConsentWebView.kt index 1c193070b..d10c1c602 100644 --- a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/mobile_core/SPConsentWebView.kt +++ b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/mobile_core/SPConsentWebView.kt @@ -3,6 +3,7 @@ package com.sourcepoint.cmplibrary.mobile_core import android.annotation.SuppressLint import android.content.Context import android.graphics.Color +import android.os.Build import android.os.Looper import android.os.Message import android.view.KeyEvent @@ -142,6 +143,12 @@ class SPConsentWebView( setWebContentsDebuggingEnabled(true) settings.setSupportMultipleWindows(true) addJavascriptInterface(this, "JSReceiver") + importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_YES + isFocusable = true + isFocusableInTouchMode = true + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + isScreenReaderFocusable = true + } webChromeClient = object : WebChromeClient() { override fun onCreateWindow( view: WebView, From ff2206df2f27a7cee5f75a77626e50e29549c0c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Herculano?= <228650+andresilveirah@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:11:08 +0100 Subject: [PATCH 3/6] hide ui components from accessibility to give webview emphasis --- .../cmplibrary/SpConsentLibMobileCore.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt index 56385606d..337b00337 100644 --- a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt +++ b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt @@ -84,6 +84,9 @@ class SpConsentLibMobileCore( context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager } + // used to store accessibility state of main view's children + private var a11ySnapshot: WeakHashMap = WeakHashMap() + private var messageUI: SPMessageUI? = null fun getOrCreateMessageUI(): SPMessageUI { messageUI = messageUI ?: SPConsentWebView.create( @@ -291,9 +294,31 @@ class SpConsentLibMobileCore( ) } + // This is an aggressive way to "hide" ui elements from accessibility services in order to give + // the message full attention. We store a weak reference to each child, so we can later restore + // their original accessibility state. + private fun hideMainViewChildrenFromAccessibility(parent: ViewGroup?) { + val parent = parent ?: return + if(!a11ySnapshot.isEmpty()) return + + for (i in 0 until parent.childCount) { + val child = parent.getChildAt(i) + a11ySnapshot[child] = child.importantForAccessibility + child.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + } + } + + private fun restoreChildrenA11y() { + for ((view, accessibilityState) in a11ySnapshot.entries.toList()) { + view.importantForAccessibility = accessibilityState + } + a11ySnapshot.clear() + } + override fun showView(view: View) { mainView?.let { parentView -> view.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) + hideMainViewChildrenFromAccessibility(parentView) parentView.addView(view) view.bringToFront() view.requestLayout() @@ -307,6 +332,7 @@ class SpConsentLibMobileCore( override fun removeView(view: View) { mainView?.let { parentView -> parentView.removeView(view) + restoreChildrenA11y() } } @@ -384,6 +410,7 @@ class SpConsentLibMobileCore( override fun onError(error: ConsentLibExceptionK) { pendingActions = 0 messagesToDisplay = ArrayDeque(emptyList()) + restoreChildrenA11y() if (cleanUserDataOnError) { clearLocalData() From 9edb1da7ae93f8823cd7ce6f79ea133bd293dc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Herculano?= <228650+andresilveirah@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:11:21 +0100 Subject: [PATCH 4/6] set version name to 7.15.10-beta.1 --- cmplibrary/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmplibrary/gradle.properties b/cmplibrary/gradle.properties index a029a66a3..e0cc9dacb 100644 --- a/cmplibrary/gradle.properties +++ b/cmplibrary/gradle.properties @@ -1 +1 @@ -VERSION_NAME = 7.15.9 +VERSION_NAME = 7.15.10-beta.1 From 6b48c9d3d0a0b825f94876d4b84dfcd02d6b9978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Herculano?= <228650+andresilveirah@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:12:25 +0100 Subject: [PATCH 5/6] fix linting issue --- .../java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt index 337b00337..e4d21dcf4 100644 --- a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt +++ b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt @@ -299,7 +299,7 @@ class SpConsentLibMobileCore( // their original accessibility state. private fun hideMainViewChildrenFromAccessibility(parent: ViewGroup?) { val parent = parent ?: return - if(!a11ySnapshot.isEmpty()) return + if (!a11ySnapshot.isEmpty()) return for (i in 0 until parent.childCount) { val child = parent.getChildAt(i) From 5aaeb1609d3564105832a272d963ad5d29656197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Herculano?= <228650+andresilveirah@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:43:37 +0100 Subject: [PATCH 6/6] add flag for aggressive accessibility --- .../main/java/com/sourcepoint/cmplibrary/SpConsentLib.kt | 6 ++++++ .../com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt | 7 +++++-- .../java/com/sourcepoint/cmplibrary/creation/Factory.kt | 4 +++- .../com/sourcepoint/cmplibrary/creation/SpCmpBuilder.kt | 4 +++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLib.kt b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLib.kt index ea9abd66a..dd685df26 100644 --- a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLib.kt +++ b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLib.kt @@ -16,6 +16,12 @@ interface SpConsentLib { */ val dismissMessageOnBackPress: Boolean + /** + * Instructs the SDK to hide the host app's views from accessibility services + * (e.g., screen readers) while the message is displayed. It's false by default. + */ + val hideAppsViewsFromAccessibilityWhileMessageIsDisplayed: Boolean + var cleanUserDataOnError: Boolean /** diff --git a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt index e4d21dcf4..03cd2682b 100644 --- a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt +++ b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/SpConsentLibMobileCore.kt @@ -71,8 +71,9 @@ class SpConsentLibMobileCore( private val coordinator: ICoordinator, private val connectionManager: ConnectionManager, private val spClient: SpClient, + override val hideAppsViewsFromAccessibilityWhileMessageIsDisplayed: Boolean = false, override val dismissMessageOnBackPress: Boolean = true, - override var cleanUserDataOnError: Boolean = false + override var cleanUserDataOnError: Boolean = false, ) : SpConsentLib, SPMessageUIClient { private var pendingActions: Int = 0 private var messagesToDisplay: ArrayDeque = ArrayDeque(emptyList()) @@ -318,7 +319,9 @@ class SpConsentLibMobileCore( override fun showView(view: View) { mainView?.let { parentView -> view.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) - hideMainViewChildrenFromAccessibility(parentView) + if (hideAppsViewsFromAccessibilityWhileMessageIsDisplayed) { + hideMainViewChildrenFromAccessibility(parentView) + } parentView.addView(view) view.bringToFront() view.requestLayout() diff --git a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/creation/Factory.kt b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/creation/Factory.kt index 4af391afc..b30094b01 100644 --- a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/creation/Factory.kt +++ b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/creation/Factory.kt @@ -38,6 +38,7 @@ fun makeConsentLib( activity: Activity, spClient: SpClient, dismissMessageOnBackPress: Boolean = true, + hideAppsViewsFromAccessibilityWhileMessageIsDisplayed: Boolean = false, connectionManager: ConnectionManager = ConnectionManagerImpl(activity.applicationContext), ): SpConsentLib = SpConsentLibMobileCore( coordinator = Coordinator( @@ -58,7 +59,8 @@ fun makeConsentLib( context = activity.applicationContext, activity = WeakReference(activity), connectionManager = connectionManager, - dismissMessageOnBackPress = dismissMessageOnBackPress + dismissMessageOnBackPress = dismissMessageOnBackPress, + hideAppsViewsFromAccessibilityWhileMessageIsDisplayed = hideAppsViewsFromAccessibilityWhileMessageIsDisplayed ) fun List.toCore(spConfig: SpConfig) = SPCampaigns( diff --git a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/creation/SpCmpBuilder.kt b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/creation/SpCmpBuilder.kt index f7bbc60a5..3cb5d4679 100644 --- a/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/creation/SpCmpBuilder.kt +++ b/cmplibrary/src/main/java/com/sourcepoint/cmplibrary/creation/SpCmpBuilder.kt @@ -15,6 +15,7 @@ class SpCmpBuilder { lateinit var spClient: SpClient var connectionManager: ConnectionManager? = null var dismissMessageOnBackPress = true + var hideAppsViewsFromAccessibilityWhileMessageIsDisplayed = false fun config(dsl: SpConfigDataBuilder.() -> Unit) { spConfig = SpConfigDataBuilder().apply(dsl).build() @@ -29,7 +30,8 @@ class SpCmpBuilder { activity = activity, spClient = spClient, connectionManager = connectionManager ?: ConnectionManagerImpl(activity.applicationContext), - dismissMessageOnBackPress = dismissMessageOnBackPress + dismissMessageOnBackPress = dismissMessageOnBackPress, + hideAppsViewsFromAccessibilityWhileMessageIsDisplayed = hideAppsViewsFromAccessibilityWhileMessageIsDisplayed ) } }