From af72ec74f2ab7f790174283d8c68c9bd006b4e84 Mon Sep 17 00:00:00 2001 From: Giannis Lagodimos <62063308+lagodimos@users.noreply.github.com> Date: Sat, 1 Nov 2025 18:53:13 +0200 Subject: [PATCH 1/4] feat: add certificate hashes to AppInfo --- .../com/sharmadhiraj/installed_apps/Util.kt | 27 ++++++++++++++++++- lib/app_info.dart | 3 +++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt b/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt index d04ea26..b7a28e5 100644 --- a/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt +++ b/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt @@ -4,10 +4,13 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager +import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.P +import android.util.Base64 import android.util.Log import java.io.File +import java.security.MessageDigest class Util { companion object { @@ -38,6 +41,7 @@ class Util { map["is_system_app"] = isSystemApp(packageManager, packageInfo.packageName) map["is_launchable_app"] = isLaunchableApp(packageManager, packageInfo.packageName) + map["certificate_hashes"] = getCertificateHashes(packageManager, packageInfo.packageName) } } else { map["name"] = "Unknown" @@ -75,5 +79,26 @@ class Util { false } } + + fun getCertificateHashes(packageManager: PackageManager, packageName: String): List { + val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) + val signingInfo = packageInfo.signingInfo + val signatures = if (signingInfo.hasMultipleSigners()) { + signingInfo.apkContentsSigners + } else { + signingInfo.signingCertificateHistory + } + + val hashes = signatures.map { + signature -> MessageDigest + .getInstance("SHA-256") + .digest(signature.toByteArray()) + .joinToString(":") { + "%02X".format(it) + } + } + + return hashes + } } -} \ No newline at end of file +} diff --git a/lib/app_info.dart b/lib/app_info.dart index 79e127e..3bee77b 100644 --- a/lib/app_info.dart +++ b/lib/app_info.dart @@ -10,6 +10,7 @@ class AppInfo { final int installedTimestamp; final bool isSystemApp; final bool isLaunchableApp; + final List certificateHashes; const AppInfo({ required this.name, @@ -21,6 +22,7 @@ class AppInfo { required this.installedTimestamp, required this.isSystemApp, required this.isLaunchableApp, + required this.certificateHashes, }); factory AppInfo.create(dynamic data) { @@ -34,6 +36,7 @@ class AppInfo { installedTimestamp: data["installed_timestamp"] ?? 0, isSystemApp: data["is_system_app"] ?? false, isLaunchableApp: data["is_launchable_app"] ?? true, + certificateHashes: data["certificate_hashes"].cast() ?? [], ); } From 551f6e62c204f453a4138c170fe6d61eeb453517 Mon Sep 17 00:00:00 2001 From: Giannis Lagodimos <62063308+lagodimos@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:22:46 +0200 Subject: [PATCH 2/4] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6947772..70c0269 100644 --- a/README.md +++ b/README.md @@ -49,16 +49,16 @@ Example project: [GitHub](https://github.com/sharmadhiraj/installed_apps/tree/ma List apps = await InstalledApps.getInstalledApps( // Optional: whether to exclude system apps from the list. Default is true. excludeSystemApps: true, - + // Optional: whether to exclude apps that cannot be launched (no launch intent). Default is true. excludeNonLaunchableApps: true, - + // Optional: whether to include app icons in the result. Default is false. withIcon: false, - + // Optional: filter apps whose package names start with this prefix. Default is null (no filtering). packageNamePrefix: "com.example", - + // Optional: filter apps by platform type (Flutter, React Native, etc.). Default is null (no filtering). platformType: PlatformType.flutter, ); @@ -83,6 +83,7 @@ class AppInfo { int installedTimestamp; bool isSystemApp; bool isLaunchableApp; + List certificateHashes; } ``` From 65b2cd61ed9041f5e17370baef7b8c3eeeeaa03b Mon Sep 17 00:00:00 2001 From: Giannis Lagodimos <62063308+lagodimos@users.noreply.github.com> Date: Sun, 2 Nov 2025 10:47:38 +0200 Subject: [PATCH 3/4] add hasMultipleSigners --- README.md | 1 + .../main/kotlin/com/sharmadhiraj/installed_apps/Util.kt | 8 ++++++++ lib/app_info.dart | 3 +++ 3 files changed, 12 insertions(+) diff --git a/README.md b/README.md index 70c0269..3bee4d9 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ class AppInfo { int installedTimestamp; bool isSystemApp; bool isLaunchableApp; + bool hasMultipleSigners; List certificateHashes; } ``` diff --git a/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt b/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt index b7a28e5..6f800c4 100644 --- a/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt +++ b/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt @@ -41,6 +41,7 @@ class Util { map["is_system_app"] = isSystemApp(packageManager, packageInfo.packageName) map["is_launchable_app"] = isLaunchableApp(packageManager, packageInfo.packageName) + map["has_multiple_signers"] = hasMultipleSigners(packageManager, packageInfo.packageName) map["certificate_hashes"] = getCertificateHashes(packageManager, packageInfo.packageName) } } else { @@ -80,6 +81,13 @@ class Util { } } + fun hasMultipleSigners(packageManager: PackageManager, packageName: String): Boolean { + return packageManager + .getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) + .signingInfo + .hasMultipleSigners() + } + fun getCertificateHashes(packageManager: PackageManager, packageName: String): List { val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) val signingInfo = packageInfo.signingInfo diff --git a/lib/app_info.dart b/lib/app_info.dart index 3bee77b..49ff3c7 100644 --- a/lib/app_info.dart +++ b/lib/app_info.dart @@ -10,6 +10,7 @@ class AppInfo { final int installedTimestamp; final bool isSystemApp; final bool isLaunchableApp; + final bool hasMultipleSigners; final List certificateHashes; const AppInfo({ @@ -22,6 +23,7 @@ class AppInfo { required this.installedTimestamp, required this.isSystemApp, required this.isLaunchableApp, + required this.hasMultipleSigners, required this.certificateHashes, }); @@ -36,6 +38,7 @@ class AppInfo { installedTimestamp: data["installed_timestamp"] ?? 0, isSystemApp: data["is_system_app"] ?? false, isLaunchableApp: data["is_launchable_app"] ?? true, + hasMultipleSigners: data["has_multiple_signers"] ?? false, certificateHashes: data["certificate_hashes"].cast() ?? [], ); } From 12d84963360bf7caf7219c4a9f75ce82f1f20282 Mon Sep 17 00:00:00 2001 From: Giannis Lagodimos <62063308+lagodimos@users.noreply.github.com> Date: Sun, 2 Nov 2025 10:50:28 +0200 Subject: [PATCH 4/4] add documentation url --- android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt b/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt index 6f800c4..52b4dc2 100644 --- a/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt +++ b/android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt @@ -91,6 +91,8 @@ class Util { fun getCertificateHashes(packageManager: PackageManager, packageName: String): List { val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) val signingInfo = packageInfo.signingInfo + + // https://developer.android.com/reference/android/content/pm/SigningInfo#getApkContentsSigners() val signatures = if (signingInfo.hasMultipleSigners()) { signingInfo.apkContentsSigners } else {