mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
PM-11643: Add LauncherPackageNameManager for tracking launcher apps (#3861)
This commit is contained in:
parent
b672418c45
commit
c02afb6714
5 changed files with 141 additions and 0 deletions
|
@ -245,6 +245,10 @@
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||||
</intent>
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.HOME" />
|
||||||
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.di
|
package com.x8bit.bitwarden.data.autofill.accessibility.di
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManager
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManagerImpl
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import java.time.Clock
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,4 +22,15 @@ object AccessibilityModule {
|
||||||
@Provides
|
@Provides
|
||||||
fun providesAccessibilityInvokeManager(): AccessibilityAutofillManager =
|
fun providesAccessibilityInvokeManager(): AccessibilityAutofillManager =
|
||||||
AccessibilityAutofillManagerImpl()
|
AccessibilityAutofillManagerImpl()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun providesLauncherPackageNameManager(
|
||||||
|
clock: Clock,
|
||||||
|
packageManager: PackageManager,
|
||||||
|
): LauncherPackageNameManager =
|
||||||
|
LauncherPackageNameManagerImpl(
|
||||||
|
clockProvider = { clock },
|
||||||
|
packageManager = packageManager,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A manager for getting the launcher packages from the operating system.
|
||||||
|
*/
|
||||||
|
interface LauncherPackageNameManager {
|
||||||
|
/**
|
||||||
|
* A list of launcher packages from the operating system.
|
||||||
|
*/
|
||||||
|
val launcherPackages: List<String>
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import java.time.Clock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How frequently the cached launcher list should be refreshed.
|
||||||
|
*/
|
||||||
|
private const val REFRESH_CACHE_MS: Long = 1L * 60L * 60L * 1000L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of the [LauncherPackageNameManager].
|
||||||
|
*/
|
||||||
|
class LauncherPackageNameManagerImpl(
|
||||||
|
private val clockProvider: () -> Clock,
|
||||||
|
private val packageManager: PackageManager,
|
||||||
|
) : LauncherPackageNameManager {
|
||||||
|
private var lastLauncherFetchMs: Long = 0L
|
||||||
|
private var cachedLauncherPackages: List<String>? = null
|
||||||
|
|
||||||
|
override val launcherPackages: List<String>
|
||||||
|
get() {
|
||||||
|
if (cachedLauncherPackages == null ||
|
||||||
|
clockProvider().millis() - lastLauncherFetchMs > REFRESH_CACHE_MS
|
||||||
|
) {
|
||||||
|
updateCachedLauncherPackages()
|
||||||
|
}
|
||||||
|
return cachedLauncherPackages.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCachedLauncherPackages() {
|
||||||
|
cachedLauncherPackages = packageManager
|
||||||
|
.queryIntentActivities(
|
||||||
|
Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.map { it.activityInfo.packageName }
|
||||||
|
lastLauncherFetchMs = clockProvider().millis()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.ResolveInfo
|
||||||
|
import io.mockk.clearMocks
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.Clock
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
|
class LauncherPackageNameManagerTest {
|
||||||
|
private val packageManager: PackageManager = mockk()
|
||||||
|
private var mutableClock: Clock = FIXED_CLOCK
|
||||||
|
|
||||||
|
private val launcherPackageNameManager: LauncherPackageNameManager =
|
||||||
|
LauncherPackageNameManagerImpl(
|
||||||
|
clockProvider = { mutableClock },
|
||||||
|
packageManager = packageManager,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `launcherPackages should populate cache on first attempt and use cached value for second attempt and repopulate the cache after an hour`() {
|
||||||
|
val testPackageName = "testPackageName"
|
||||||
|
val testActivityInfo = ActivityInfo().apply {
|
||||||
|
packageName = testPackageName
|
||||||
|
}
|
||||||
|
val resolveInfo = ResolveInfo().apply {
|
||||||
|
activityInfo = testActivityInfo
|
||||||
|
}
|
||||||
|
val packages = listOf(resolveInfo)
|
||||||
|
every { packageManager.queryIntentActivities(any(), any<Int>()) } returns packages
|
||||||
|
|
||||||
|
val firstResult = launcherPackageNameManager.launcherPackages
|
||||||
|
|
||||||
|
assertEquals(listOf(testPackageName), firstResult)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
packageManager.queryIntentActivities(any(), any<Int>())
|
||||||
|
}
|
||||||
|
clearMocks(packageManager)
|
||||||
|
|
||||||
|
val secondResult = launcherPackageNameManager.launcherPackages
|
||||||
|
|
||||||
|
assertEquals(listOf(testPackageName), secondResult)
|
||||||
|
verify(exactly = 0) {
|
||||||
|
packageManager.queryIntentActivities(any(), any<Int>())
|
||||||
|
}
|
||||||
|
clearMocks(packageManager)
|
||||||
|
|
||||||
|
every { packageManager.queryIntentActivities(any(), any<Int>()) } returns emptyList()
|
||||||
|
mutableClock = Clock.offset(FIXED_CLOCK, Duration.ofMinutes(61))
|
||||||
|
val thirdResult = launcherPackageNameManager.launcherPackages
|
||||||
|
|
||||||
|
assertEquals(emptyList<String>(), thirdResult)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
packageManager.queryIntentActivities(any(), any<Int>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||||
|
Instant.parse("2023-10-27T12:00:00Z"),
|
||||||
|
ZoneOffset.UTC,
|
||||||
|
)
|
Loading…
Reference in a new issue