diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerImpl.kt index 9c0128d5a..62670454b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerImpl.kt @@ -1,9 +1,8 @@ package com.x8bit.bitwarden.data.autofill.accessibility.manager import android.content.Context -import android.provider.Settings import androidx.lifecycle.LifecycleCoroutineScope -import com.x8bit.bitwarden.LEGACY_ACCESSIBILITY_SERVICE_NAME +import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -17,28 +16,12 @@ class AccessibilityActivityManagerImpl( appForegroundManager: AppForegroundManager, lifecycleScope: LifecycleCoroutineScope, ) : AccessibilityActivityManager { - private val isAccessibilityServiceEnabled: Boolean - get() { - val appContext = context.applicationContext - val accessibilityService = appContext - .packageName - ?.let { "$it/$LEGACY_ACCESSIBILITY_SERVICE_NAME" } - ?: return false - return Settings - .Secure - .getString( - appContext.contentResolver, - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - ) - ?.contains(accessibilityService) - ?: false - } - init { appForegroundManager .appForegroundStateFlow .onEach { - accessibilityEnabledManager.isAccessibilityEnabled = isAccessibilityServiceEnabled + accessibilityEnabledManager.isAccessibilityEnabled = + context.isAccessibilityServiceEnabled } .launchIn(lifecycleScope) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/util/ContextExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/util/ContextExtensions.kt new file mode 100644 index 000000000..58c1e6615 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/util/ContextExtensions.kt @@ -0,0 +1,26 @@ +package com.x8bit.bitwarden.data.autofill.accessibility.util + +import android.content.Context +import android.provider.Settings +import com.x8bit.bitwarden.LEGACY_ACCESSIBILITY_SERVICE_NAME +import com.x8bit.bitwarden.data.autofill.accessibility.BitwardenAccessibilityService + +/** + * Helper method to determine if the [BitwardenAccessibilityService] is enabled. + */ +val Context.isAccessibilityServiceEnabled: Boolean + get() { + val appContext = this.applicationContext + val accessibilityServiceName = appContext + .packageName + ?.let { "$it/$LEGACY_ACCESSIBILITY_SERVICE_NAME" } + ?: return false + return Settings + .Secure + .getString( + appContext.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + ) + ?.contains(accessibilityServiceName) + ?: false + } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerTest.kt index 520ede728..e328fc834 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerTest.kt @@ -1,9 +1,9 @@ package com.x8bit.bitwarden.data.autofill.accessibility.manager import android.content.Context -import android.provider.Settings import androidx.lifecycle.LifecycleCoroutineScope import app.cash.turbine.test +import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState import io.mockk.every @@ -22,11 +22,7 @@ import org.junit.jupiter.api.Test @OptIn(ExperimentalCoroutinesApi::class) class AccessibilityActivityManagerTest { - private val context: Context = mockk { - every { applicationContext } returns this - every { packageName } returns "com.x8bit.bitwarden" - every { contentResolver } returns mockk() - } + private val context: Context = mockk() private val accessibilityEnabledManager: AccessibilityEnabledManager = AccessibilityEnabledManagerImpl() private val mutableAppForegroundStateFlow = MutableStateFlow(AppForegroundState.BACKGROUNDED) @@ -38,24 +34,23 @@ class AccessibilityActivityManagerTest { } // We will construct an instance here just to hook the various dependencies together internally - @Suppress("unused") - private val autofillActivityManager: AccessibilityActivityManager = - AccessibilityActivityManagerImpl( + private lateinit var autofillActivityManager: AccessibilityActivityManager + + @BeforeEach + fun setup() { + mockkStatic(Context::isAccessibilityServiceEnabled) + every { context.isAccessibilityServiceEnabled } returns false + autofillActivityManager = AccessibilityActivityManagerImpl( context = context, accessibilityEnabledManager = accessibilityEnabledManager, appForegroundManager = appForegroundManager, lifecycleScope = lifecycleScope, ) - - @BeforeEach - fun setup() { - mockkStatic(Settings.Secure::getString) - mockkSettingsSecureGetString(value = null) } @AfterEach fun tearDown() { - unmockkStatic(Settings.Secure::getString) + unmockkStatic(Context::isAccessibilityServiceEnabled) } @Test @@ -66,10 +61,7 @@ class AccessibilityActivityManagerTest { // An update is received when both the accessibility state and foreground state // change - @Suppress("MaxLineLength") - mockkSettingsSecureGetString( - value = "com.x8bit.bitwarden/com.x8bit.bitwarden.Accessibility.AccessibilityService", - ) + every { context.isAccessibilityServiceEnabled } returns true mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED assertTrue(awaitItem()) @@ -78,7 +70,7 @@ class AccessibilityActivityManagerTest { expectNoEvents() // An update is not received when only the accessibility state changes - mockkSettingsSecureGetString(value = "com.x8bit.bitwarden/AccessibilityService") + every { context.isAccessibilityServiceEnabled } returns false expectNoEvents() // An update is received after both states have changed @@ -86,10 +78,4 @@ class AccessibilityActivityManagerTest { assertFalse(awaitItem()) } } - - private fun mockkSettingsSecureGetString(value: String?) { - every { - Settings.Secure.getString(any(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) - } returns value - } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/util/ContextExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/util/ContextExtensionsTest.kt new file mode 100644 index 000000000..204998dbc --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/util/ContextExtensionsTest.kt @@ -0,0 +1,83 @@ +package com.x8bit.bitwarden.data.autofill.accessibility.util + +import android.content.Context +import android.provider.Settings +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class ContextExtensionsTest { + + @BeforeEach + fun setup() { + mockkStatic(Settings.Secure::getString) + } + + @AfterEach + fun tearDown() { + unmockkStatic(Settings.Secure::getString) + } + + @Test + fun `isAccessibilityServiceEnabled with null package name returns false`() { + val context: Context = mockk { + every { applicationContext } returns this + every { packageName } returns null + } + + assertFalse(context.isAccessibilityServiceEnabled) + } + + @Test + fun `isAccessibilityServiceEnabled with null secure string returns false`() { + val context: Context = mockk { + every { applicationContext } returns this + every { packageName } returns "com.x8bit.bitwarden" + every { contentResolver } returns mockk() + } + mockkSettingsSecureGetString(value = null) + + assertFalse(context.isAccessibilityServiceEnabled) + } + + @Test + fun `isAccessibilityServiceEnabled with incorrect secure string returns false`() { + val context: Context = mockk { + every { applicationContext } returns this + every { packageName } returns "com.x8bit.bitwarden" + every { contentResolver } returns mockk() + } + @Suppress("MaxLineLength") + mockkSettingsSecureGetString( + value = "com.x8bit.bitwarden.dev/com.x8bit.bitwarden.Accessibility.AccessibilityService", + ) + + assertFalse(context.isAccessibilityServiceEnabled) + } + + @Test + fun `isAccessibilityServiceEnabled with correct secure string returns true`() { + val context: Context = mockk { + every { applicationContext } returns this + every { packageName } returns "com.x8bit.bitwarden" + every { contentResolver } returns mockk() + } + mockkSettingsSecureGetString( + value = "com.x8bit.bitwarden/com.x8bit.bitwarden.Accessibility.AccessibilityService", + ) + + assertTrue(context.isAccessibilityServiceEnabled) + } + + private fun mockkSettingsSecureGetString(value: String?) { + every { + Settings.Secure.getString(any(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) + } returns value + } +}