From c02afb6714e7648ced933fce3826fc37f8ee03ff Mon Sep 17 00:00:00 2001 From: David Perez Date: Wed, 4 Sep 2024 14:12:09 -0500 Subject: [PATCH] PM-11643: Add LauncherPackageNameManager for tracking launcher apps (#3861) --- app/src/main/AndroidManifest.xml | 4 ++ .../accessibility/di/AccessibilityModule.kt | 15 ++++ .../manager/LauncherPackageNameManager.kt | 11 +++ .../manager/LauncherPackageNameManagerImpl.kt | 41 +++++++++++ .../manager/LauncherPackageNameManagerTest.kt | 70 +++++++++++++++++++ 5 files changed, 141 insertions(+) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManager.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManagerImpl.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManagerTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cf18a3fe95..a87391c028 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -245,6 +245,10 @@ + + + + diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/di/AccessibilityModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/di/AccessibilityModule.kt index 7dacb5fe0d..df7f7a1845 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/di/AccessibilityModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/di/AccessibilityModule.kt @@ -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, + ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManager.kt new file mode 100644 index 0000000000..9fdf659aff --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManager.kt @@ -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 +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManagerImpl.kt new file mode 100644 index 0000000000..bc8f43d147 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManagerImpl.kt @@ -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? = null + + override val launcherPackages: List + 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() + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManagerTest.kt new file mode 100644 index 0000000000..c964909c9a --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/LauncherPackageNameManagerTest.kt @@ -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()) } returns packages + + val firstResult = launcherPackageNameManager.launcherPackages + + assertEquals(listOf(testPackageName), firstResult) + verify(exactly = 1) { + packageManager.queryIntentActivities(any(), any()) + } + clearMocks(packageManager) + + val secondResult = launcherPackageNameManager.launcherPackages + + assertEquals(listOf(testPackageName), secondResult) + verify(exactly = 0) { + packageManager.queryIntentActivities(any(), any()) + } + clearMocks(packageManager) + + every { packageManager.queryIntentActivities(any(), any()) } returns emptyList() + mutableClock = Clock.offset(FIXED_CLOCK, Duration.ofMinutes(61)) + val thirdResult = launcherPackageNameManager.launcherPackages + + assertEquals(emptyList(), thirdResult) + verify(exactly = 1) { + packageManager.queryIntentActivities(any(), any()) + } + } +} + +private val FIXED_CLOCK: Clock = Clock.fixed( + Instant.parse("2023-10-27T12:00:00Z"), + ZoneOffset.UTC, +)