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

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

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