mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 04:39:19 -06:00
[PM-14435] Accessibility enabled settings changes to address older and custom Android phone versions (#4756)
This commit is contained in:
parent
ec030f2c2e
commit
ac6ff98041
@ -1,8 +1,10 @@
|
|||||||
package com.x8bit.bitwarden.data.autofill.accessibility
|
package com.x8bit.bitwarden.data.autofill.accessibility
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityService
|
import android.accessibilityservice.AccessibilityService
|
||||||
|
import android.content.Intent
|
||||||
import android.view.accessibility.AccessibilityEvent
|
import android.view.accessibility.AccessibilityEvent
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityEnabledManager
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.processor.BitwardenAccessibilityProcessor
|
import com.x8bit.bitwarden.data.autofill.accessibility.processor.BitwardenAccessibilityProcessor
|
||||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||||
import com.x8bit.bitwarden.data.tiles.BitwardenAutofillTileService
|
import com.x8bit.bitwarden.data.tiles.BitwardenAutofillTileService
|
||||||
@ -21,9 +23,23 @@ class BitwardenAccessibilityService : AccessibilityService() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var processor: BitwardenAccessibilityProcessor
|
lateinit var processor: BitwardenAccessibilityProcessor
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var accessibilityEnabledManager: AccessibilityEnabledManager
|
||||||
|
|
||||||
override fun onAccessibilityEvent(event: AccessibilityEvent) {
|
override fun onAccessibilityEvent(event: AccessibilityEvent) {
|
||||||
processor.processAccessibilityEvent(event = event) { rootInActiveWindow }
|
processor.processAccessibilityEvent(event = event) { rootInActiveWindow }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInterrupt() = Unit
|
override fun onInterrupt() = Unit
|
||||||
|
|
||||||
|
override fun onUnbind(intent: Intent?): Boolean {
|
||||||
|
return super
|
||||||
|
.onUnbind(intent)
|
||||||
|
.also { accessibilityEnabledManager.refreshAccessibilityEnabledFromSettings() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected() {
|
||||||
|
super.onServiceConnected()
|
||||||
|
accessibilityEnabledManager.refreshAccessibilityEnabledFromSettings()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,10 +57,10 @@ object AccessibilityModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun providesAccessibilityEnabledManager(
|
fun providesAccessibilityEnabledManager(
|
||||||
accessibilityManager: AccessibilityManager,
|
@ApplicationContext context: Context,
|
||||||
): AccessibilityEnabledManager =
|
): AccessibilityEnabledManager =
|
||||||
AccessibilityEnabledManagerImpl(
|
AccessibilityEnabledManagerImpl(
|
||||||
accessibilityManager = accessibilityManager,
|
context = context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|||||||
@ -10,4 +10,9 @@ interface AccessibilityEnabledManager {
|
|||||||
* Emits updates that track whether the accessibility autofill service is enabled..
|
* Emits updates that track whether the accessibility autofill service is enabled..
|
||||||
*/
|
*/
|
||||||
val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the accessibility enabled state from the system settings.
|
||||||
|
*/
|
||||||
|
fun refreshAccessibilityEnabledFromSettings()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
import android.view.accessibility.AccessibilityManager
|
import android.content.Context
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@ -9,20 +10,20 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
* The default implementation of [AccessibilityEnabledManager].
|
* The default implementation of [AccessibilityEnabledManager].
|
||||||
*/
|
*/
|
||||||
class AccessibilityEnabledManagerImpl(
|
class AccessibilityEnabledManagerImpl(
|
||||||
accessibilityManager: AccessibilityManager,
|
private val context: Context,
|
||||||
) : AccessibilityEnabledManager {
|
) : AccessibilityEnabledManager {
|
||||||
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(
|
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(
|
||||||
value = accessibilityManager.isEnabled,
|
value = context.isAccessibilityServiceEnabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
accessibilityManager.addAccessibilityStateChangeListener(
|
mutableIsAccessibilityEnabledStateFlow.value = context.isAccessibilityServiceEnabled
|
||||||
AccessibilityManager.AccessibilityStateChangeListener { isEnabled ->
|
|
||||||
mutableIsAccessibilityEnabledStateFlow.value = isEnabled
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
override val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
||||||
get() = mutableIsAccessibilityEnabledStateFlow.asStateFlow()
|
get() = mutableIsAccessibilityEnabledStateFlow.asStateFlow()
|
||||||
|
|
||||||
|
override fun refreshAccessibilityEnabledFromSettings() {
|
||||||
|
mutableIsAccessibilityEnabledStateFlow.value = context.isAccessibilityServiceEnabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,46 +1,50 @@
|
|||||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
import android.view.accessibility.AccessibilityManager
|
import android.content.Context
|
||||||
import app.cash.turbine.test
|
import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.slot
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkAll
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class AccessibilityEnabledManagerTest {
|
class AccessibilityEnabledManagerTest {
|
||||||
|
private val context: Context = mockk()
|
||||||
|
|
||||||
private val accessibilityStateChangeListener =
|
private lateinit var accessibilityEnabledManager: AccessibilityEnabledManager
|
||||||
slot<AccessibilityManager.AccessibilityStateChangeListener>()
|
|
||||||
private val accessibilityManager = mockk<AccessibilityManager> {
|
@BeforeEach
|
||||||
every { isEnabled } returns false
|
fun setUp() {
|
||||||
every {
|
mockkStatic(Context::isAccessibilityServiceEnabled)
|
||||||
addAccessibilityStateChangeListener(capture(accessibilityStateChangeListener))
|
every { context.isAccessibilityServiceEnabled } returns false
|
||||||
} returns true
|
accessibilityEnabledManager = AccessibilityEnabledManagerImpl(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val accessibilityEnabledManager: AccessibilityEnabledManager =
|
@AfterEach
|
||||||
AccessibilityEnabledManagerImpl(
|
fun tearDown() {
|
||||||
accessibilityManager = accessibilityManager,
|
unmockkAll()
|
||||||
)
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
@Test
|
||||||
fun `isAccessibilityEnabledStateFlow should emit whenever accessibilityStateChangeListener emits a unique value`() =
|
fun `isAccessibilityEnabled returns false when setting does not contain our service`() =
|
||||||
runTest {
|
runTest {
|
||||||
accessibilityEnabledManager.isAccessibilityEnabledStateFlow.test {
|
every { context.isAccessibilityServiceEnabled } returns false
|
||||||
assertFalse(awaitItem())
|
accessibilityEnabledManager.refreshAccessibilityEnabledFromSettings()
|
||||||
|
val result = accessibilityEnabledManager.isAccessibilityEnabledStateFlow.value
|
||||||
accessibilityStateChangeListener.captured.onAccessibilityStateChanged(true)
|
assertFalse(result)
|
||||||
assertTrue(awaitItem())
|
|
||||||
|
|
||||||
accessibilityStateChangeListener.captured.onAccessibilityStateChanged(true)
|
|
||||||
expectNoEvents()
|
|
||||||
|
|
||||||
accessibilityStateChangeListener.captured.onAccessibilityStateChanged(false)
|
|
||||||
assertFalse(awaitItem())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `isAccessibilityEnabled returns true when setting contains the defined service`() =
|
||||||
|
runTest {
|
||||||
|
every { context.isAccessibilityServiceEnabled } returns true
|
||||||
|
accessibilityEnabledManager.refreshAccessibilityEnabledFromSettings()
|
||||||
|
val result = accessibilityEnabledManager.isAccessibilityEnabledStateFlow.value
|
||||||
|
assertTrue(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,10 @@ class FakeAccessibilityEnabledManager : AccessibilityEnabledManager {
|
|||||||
override val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
override val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
||||||
get() = mutableIsAccessibilityEnabledStateFlow.asStateFlow()
|
get() = mutableIsAccessibilityEnabledStateFlow.asStateFlow()
|
||||||
|
|
||||||
|
override fun refreshAccessibilityEnabledFromSettings() {
|
||||||
|
mutableIsAccessibilityEnabledStateFlow.value = isAccessibilityEnabled
|
||||||
|
}
|
||||||
|
|
||||||
var isAccessibilityEnabled: Boolean
|
var isAccessibilityEnabled: Boolean
|
||||||
get() = mutableIsAccessibilityEnabledStateFlow.value
|
get() = mutableIsAccessibilityEnabledStateFlow.value
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import org.junit.jupiter.api.Assertions.assertTrue
|
|||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class ReviewPromptManagerTest {
|
class ReviewPromptManagerTest {
|
||||||
|
|
||||||
private val autofillEnabledManager: AutofillEnabledManager = AutofillEnabledManagerImpl()
|
private val autofillEnabledManager: AutofillEnabledManager = AutofillEnabledManagerImpl()
|
||||||
private val fakeAccessibilityEnabledManager = FakeAccessibilityEnabledManager()
|
private val fakeAccessibilityEnabledManager = FakeAccessibilityEnabledManager()
|
||||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user