Create reusable isAccessibilityServiceEnabled extension (#3929)

This commit is contained in:
David Perez 2024-09-17 11:53:37 -05:00 committed by GitHub
parent 76cc9c8579
commit 503c966177
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 124 additions and 46 deletions

View file

@ -1,9 +1,8 @@
package com.x8bit.bitwarden.data.autofill.accessibility.manager package com.x8bit.bitwarden.data.autofill.accessibility.manager
import android.content.Context import android.content.Context
import android.provider.Settings
import androidx.lifecycle.LifecycleCoroutineScope 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 com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -17,28 +16,12 @@ class AccessibilityActivityManagerImpl(
appForegroundManager: AppForegroundManager, appForegroundManager: AppForegroundManager,
lifecycleScope: LifecycleCoroutineScope, lifecycleScope: LifecycleCoroutineScope,
) : AccessibilityActivityManager { ) : 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 { init {
appForegroundManager appForegroundManager
.appForegroundStateFlow .appForegroundStateFlow
.onEach { .onEach {
accessibilityEnabledManager.isAccessibilityEnabled = isAccessibilityServiceEnabled accessibilityEnabledManager.isAccessibilityEnabled =
context.isAccessibilityServiceEnabled
} }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
} }

View file

@ -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
}

View file

@ -1,9 +1,9 @@
package com.x8bit.bitwarden.data.autofill.accessibility.manager package com.x8bit.bitwarden.data.autofill.accessibility.manager
import android.content.Context import android.content.Context
import android.provider.Settings
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import app.cash.turbine.test 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.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
import io.mockk.every import io.mockk.every
@ -22,11 +22,7 @@ import org.junit.jupiter.api.Test
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class AccessibilityActivityManagerTest { class AccessibilityActivityManagerTest {
private val context: Context = mockk { private val context: Context = mockk()
every { applicationContext } returns this
every { packageName } returns "com.x8bit.bitwarden"
every { contentResolver } returns mockk()
}
private val accessibilityEnabledManager: AccessibilityEnabledManager = private val accessibilityEnabledManager: AccessibilityEnabledManager =
AccessibilityEnabledManagerImpl() AccessibilityEnabledManagerImpl()
private val mutableAppForegroundStateFlow = MutableStateFlow(AppForegroundState.BACKGROUNDED) 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 // We will construct an instance here just to hook the various dependencies together internally
@Suppress("unused") private lateinit var autofillActivityManager: AccessibilityActivityManager
private val autofillActivityManager: AccessibilityActivityManager =
AccessibilityActivityManagerImpl( @BeforeEach
fun setup() {
mockkStatic(Context::isAccessibilityServiceEnabled)
every { context.isAccessibilityServiceEnabled } returns false
autofillActivityManager = AccessibilityActivityManagerImpl(
context = context, context = context,
accessibilityEnabledManager = accessibilityEnabledManager, accessibilityEnabledManager = accessibilityEnabledManager,
appForegroundManager = appForegroundManager, appForegroundManager = appForegroundManager,
lifecycleScope = lifecycleScope, lifecycleScope = lifecycleScope,
) )
@BeforeEach
fun setup() {
mockkStatic(Settings.Secure::getString)
mockkSettingsSecureGetString(value = null)
} }
@AfterEach @AfterEach
fun tearDown() { fun tearDown() {
unmockkStatic(Settings.Secure::getString) unmockkStatic(Context::isAccessibilityServiceEnabled)
} }
@Test @Test
@ -66,10 +61,7 @@ class AccessibilityActivityManagerTest {
// An update is received when both the accessibility state and foreground state // An update is received when both the accessibility state and foreground state
// change // change
@Suppress("MaxLineLength") every { context.isAccessibilityServiceEnabled } returns true
mockkSettingsSecureGetString(
value = "com.x8bit.bitwarden/com.x8bit.bitwarden.Accessibility.AccessibilityService",
)
mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
assertTrue(awaitItem()) assertTrue(awaitItem())
@ -78,7 +70,7 @@ class AccessibilityActivityManagerTest {
expectNoEvents() expectNoEvents()
// An update is not received when only the accessibility state changes // An update is not received when only the accessibility state changes
mockkSettingsSecureGetString(value = "com.x8bit.bitwarden/AccessibilityService") every { context.isAccessibilityServiceEnabled } returns false
expectNoEvents() expectNoEvents()
// An update is received after both states have changed // An update is received after both states have changed
@ -86,10 +78,4 @@ class AccessibilityActivityManagerTest {
assertFalse(awaitItem()) assertFalse(awaitItem())
} }
} }
private fun mockkSettingsSecureGetString(value: String?) {
every {
Settings.Secure.getString(any(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
} returns value
}
} }

View file

@ -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
}
}