mirror of
https://github.com/bitwarden/android.git
synced 2024-12-18 15:21:53 +03:00
Create reusable isAccessibilityServiceEnabled extension (#3929)
This commit is contained in:
parent
76cc9c8579
commit
503c966177
4 changed files with 124 additions and 46 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue