PM-11643: Add LauncherPackageNameManager for tracking launcher apps (#3861)

This commit is contained in:
David Perez 2024-09-04 14:12:09 -05:00 committed by GitHub
parent b672418c45
commit c02afb6714
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 141 additions and 0 deletions

View file

@ -245,6 +245,10 @@
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
</intent>
</queries>
</manifest>

View file

@ -1,11 +1,15 @@
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.AccessibilityAutofillManagerImpl
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManager
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManagerImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import java.time.Clock
import javax.inject.Singleton
/**
@ -18,4 +22,15 @@ object AccessibilityModule {
@Provides
fun providesAccessibilityInvokeManager(): AccessibilityAutofillManager =
AccessibilityAutofillManagerImpl()
@Singleton
@Provides
fun providesLauncherPackageNameManager(
clock: Clock,
packageManager: PackageManager,
): LauncherPackageNameManager =
LauncherPackageNameManagerImpl(
clockProvider = { clock },
packageManager = packageManager,
)
}

View file

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

View file

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

View file

@ -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,
)