[PM-22665] Add BitwardenPackageManager abstraction (#5360)

This commit is contained in:
Patrick Honkonen
2025-06-13 12:01:56 -04:00
committed by GitHub
parent 5d32fe9caf
commit 265014fd64
4 changed files with 184 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
package com.bitwarden.data.manager
/**
* Abstraction for interacting with Android package manager.
*/
interface BitwardenPackageManager {
/**
* Checks if the package is installed.
*/
fun isPackageInstalled(packageName: String): Boolean
/**
* Gets the app name from the package name or null if the package name is not found.
*/
fun getAppLabelForPackageOrNull(packageName: String): String?
}

View File

@@ -0,0 +1,33 @@
package com.bitwarden.data.manager
import android.content.Context
import android.content.pm.PackageManager
/**
* Primary implementation of [BitwardenPackageManager].
*/
class BitwardenPackageManagerImpl(
context: Context,
) : BitwardenPackageManager {
private val nativePackageManager = context.packageManager
override fun isPackageInstalled(packageName: String): Boolean {
return try {
nativePackageManager.getApplicationInfo(packageName, 0)
true
} catch (_: PackageManager.NameNotFoundException) {
false
}
}
override fun getAppLabelForPackageOrNull(packageName: String): String? {
return try {
val appInfo = nativePackageManager.getApplicationInfo(packageName, 0)
nativePackageManager
.getApplicationLabel(appInfo)
.toString()
} catch (_: PackageManager.NameNotFoundException) {
null
}
}
}

View File

@@ -0,0 +1,25 @@
package com.bitwarden.data.manager.di
import android.content.Context
import com.bitwarden.data.manager.BitwardenPackageManager
import com.bitwarden.data.manager.BitwardenPackageManagerImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides managers in the data module.
*/
@Module
@InstallIn(SingletonComponent::class)
object DataManagerModule {
@Provides
@Singleton
fun provideBitwardenPackageManager(
@ApplicationContext context: Context,
): BitwardenPackageManager = BitwardenPackageManagerImpl(context = context)
}

View File

@@ -0,0 +1,110 @@
package com.bitwarden.data.manager
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
class BitwardenPackageManagerTest {
private val mockPackageManager: PackageManager = mockk()
private val context: Context = mockk {
every { packageManager } returns mockPackageManager
}
private val bitwardenPackageManager = BitwardenPackageManagerImpl(context)
@Test
fun `isPackageInstalled returns true for installed package`() {
val packageName = "com.example.installed"
every { mockPackageManager.getApplicationInfo(packageName, 0) } returns ApplicationInfo()
val result = bitwardenPackageManager.isPackageInstalled(packageName)
assertTrue(result)
}
@Test
fun `isPackageInstalled returns false for non existent package`() {
val packageName = "com.example.nonexistent"
every {
mockPackageManager.getApplicationInfo(packageName, 0)
} throws PackageManager.NameNotFoundException()
val result = bitwardenPackageManager.isPackageInstalled(packageName)
assertFalse(result)
}
@Test
fun `isPackageInstalled handles empty package name`() {
val packageName = ""
every {
mockPackageManager.getApplicationInfo(packageName, 0)
} throws PackageManager.NameNotFoundException()
val result = bitwardenPackageManager.isPackageInstalled(packageName)
assertFalse(result)
}
@Test
fun `isPackageInstalled handles package name with special characters`() {
val packageName = "com.example.invalid name!"
every {
mockPackageManager.getApplicationInfo(packageName, 0)
} throws PackageManager.NameNotFoundException()
val result = bitwardenPackageManager.isPackageInstalled(packageName)
assertFalse(result)
}
@Test
fun `getAppLabelForPackageOrNull returns correct label for installed package`() {
val packageName = "com.example.installed"
val appLabel = "Example App"
val applicationInfo = ApplicationInfo()
every { mockPackageManager.getApplicationInfo(packageName, 0) } returns applicationInfo
every { mockPackageManager.getApplicationLabel(applicationInfo) } returns appLabel
val result = bitwardenPackageManager.getAppLabelForPackageOrNull(packageName)
assertEquals(appLabel, result)
}
@Test
fun `getAppLabelForPackageOrNull returns null for non existent package`() {
val packageName = "com.example.nonexistent"
every {
mockPackageManager.getApplicationInfo(packageName, 0)
} throws PackageManager.NameNotFoundException()
val result = bitwardenPackageManager.getAppLabelForPackageOrNull(packageName)
assertNull(result)
}
@Test
fun `getAppLabelForPackageOrNull handles package with no label`() {
val packageName = "com.example.nolabel"
val applicationInfo = ApplicationInfo()
every { mockPackageManager.getApplicationInfo(packageName, 0) } returns applicationInfo
every { mockPackageManager.getApplicationLabel(applicationInfo) } returns ""
val result = bitwardenPackageManager.getAppLabelForPackageOrNull(packageName)
assertEquals("", result)
}
@Test
fun `getAppLabelForPackageOrNull handles empty package name`() {
val packageName = ""
every {
mockPackageManager.getApplicationInfo(packageName, 0)
} throws PackageManager.NameNotFoundException()
val result = bitwardenPackageManager.getAppLabelForPackageOrNull(packageName)
assertNull(result)
}
@Test
fun `getAppLabelForPackageOrNull handles package name with special characters`() {
val packageName = "com.example.invalid name!"
every {
mockPackageManager.getApplicationInfo(packageName, 0)
} throws PackageManager.NameNotFoundException()
val result = bitwardenPackageManager.getAppLabelForPackageOrNull(packageName)
assertNull(result)
}
}