From 0e4a6720ac0079930751763bc09a92da35694004 Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Wed, 5 Aug 2026 22:07:14 -0300 Subject: [PATCH] Show non-Zapstore installer package ID in app info. Expose Android install-source package names so users can see when an app was installed from Play Store or another channel. --- .../app/plugins/AndroidPackageManagerPlugin.kt | 14 ++++++++++++++ .../android_package_manager.dart | 4 ++++ .../background_package_manager.dart | 1 + .../installed_packages_snapshot.dart | 2 ++ .../package_manager/package_manager.dart | 6 ++++++ lib/widgets/app_info_table.dart | 17 ++++++++++++++++- 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/kotlin/dev/zapstore/app/plugins/AndroidPackageManagerPlugin.kt b/android/app/src/main/kotlin/dev/zapstore/app/plugins/AndroidPackageManagerPlugin.kt index aa0c8b6..e055461 100644 --- a/android/app/src/main/kotlin/dev/zapstore/app/plugins/AndroidPackageManagerPlugin.kt +++ b/android/app/src/main/kotlin/dev/zapstore/app/plugins/AndroidPackageManagerPlugin.kt @@ -1854,11 +1854,25 @@ class AndroidPackageManagerPlugin : "versionName" to pkg.versionName, "versionCode" to versionCode, "signatureHashes" to getSignatureHashes(pkg.packageName), + "installerPackageName" to getInstallerPackageName(pkg.packageName), "canInstallSilently" to canInstallSilently(pkg.packageName) ) } } + private fun getInstallerPackageName(packageName: String): String? { + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + context.packageManager.getInstallSourceInfo(packageName).installingPackageName + } else { + @Suppress("DEPRECATION") + context.packageManager.getInstallerPackageName(packageName) + } + } catch (_: Exception) { + null + } + } + private fun getSignatureHashes(packageName: String): List { return try { val pm = context.packageManager diff --git a/lib/services/package_manager/android_package_manager.dart b/lib/services/package_manager/android_package_manager.dart index 3754122..3fa06d3 100644 --- a/lib/services/package_manager/android_package_manager.dart +++ b/lib/services/package_manager/android_package_manager.dart @@ -512,6 +512,8 @@ final class AndroidPackageManager extends PackageManager { versionCode: target.versionCode, signatureHashes: existingPkg?.signatureHashes ?? const [], installTime: DateTime.now(), + installerPackageName: + existingPkg?.installerPackageName ?? kZapstoreAppIdentifier, canInstallSilently: existingPkg?.canInstallSilently ?? false, ); state = state.copyWith(installed: {...state.installed, appId: newPkg}); @@ -794,6 +796,7 @@ final class AndroidPackageManager extends PackageManager { ? rawHashes.cast().toList() : []; final canInstallSilently = app['canInstallSilently'] as bool? ?? false; + final installerPackageName = app['installerPackageName'] as String?; if (canInstallSilently) anyCanInstallSilently = true; @@ -805,6 +808,7 @@ final class AndroidPackageManager extends PackageManager { versionCode: versionCode, signatureHashes: signatureHashes, installTime: null, + installerPackageName: installerPackageName, canInstallSilently: canInstallSilently, ); } diff --git a/lib/services/package_manager/background_package_manager.dart b/lib/services/package_manager/background_package_manager.dart index ed06c73..534bc29 100644 --- a/lib/services/package_manager/background_package_manager.dart +++ b/lib/services/package_manager/background_package_manager.dart @@ -77,6 +77,7 @@ final class BackgroundPackageManager extends PackageManager { ? rawHashes.cast().toList() : const [], installTime: null, + installerPackageName: app['installerPackageName'] as String?, canInstallSilently: app['canInstallSilently'] as bool? ?? false, ); } diff --git a/lib/services/package_manager/installed_packages_snapshot.dart b/lib/services/package_manager/installed_packages_snapshot.dart index 43674f2..39ab03c 100644 --- a/lib/services/package_manager/installed_packages_snapshot.dart +++ b/lib/services/package_manager/installed_packages_snapshot.dart @@ -26,6 +26,7 @@ class InstalledPackagesSnapshot { 'version': p.version, 'versionCode': p.versionCode, 'signatureHashes': p.signatureHashes, + 'installerPackageName': p.installerPackageName, 'canInstallSilently': p.canInstallSilently, }, ) @@ -87,6 +88,7 @@ class InstalledPackagesSnapshot { versionCode: map['versionCode'] as int?, signatureHashes: signatureHashes, installTime: null, + installerPackageName: map['installerPackageName'] as String?, canInstallSilently: (map['canInstallSilently'] as bool?) ?? false, ); } diff --git a/lib/services/package_manager/package_manager.dart b/lib/services/package_manager/package_manager.dart index 5b603e0..8e9ccae 100644 --- a/lib/services/package_manager/package_manager.dart +++ b/lib/services/package_manager/package_manager.dart @@ -26,6 +26,7 @@ class PackageInfo extends Equatable { this.signatureHashes = const [], this.name, this.installTime, + this.installerPackageName, this.canInstallSilently = false, }); @@ -35,6 +36,10 @@ class PackageInfo extends Equatable { final int? versionCode; final List signatureHashes; final DateTime? installTime; + + /// Package ID of the app that installed this package, if known. + /// Examples: `dev.zapstore.app`, `com.android.vending` (Play Store). + final String? installerPackageName; final bool canInstallSilently; @override @@ -45,6 +50,7 @@ class PackageInfo extends Equatable { versionCode, signatureHashes, installTime, + installerPackageName, canInstallSilently, ]; } diff --git a/lib/widgets/app_info_table.dart b/lib/widgets/app_info_table.dart index 09e5375..52cc1a7 100644 --- a/lib/widgets/app_info_table.dart +++ b/lib/widgets/app_info_table.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:models/models.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:zapstore/services/package_manager/package_manager.dart'; import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/widgets/app_detail_widgets.dart'; import 'package:zapstore/widgets/author_container.dart'; @@ -86,6 +87,21 @@ class AppInfoTable extends HookConsumerWidget { rows.add(_InfoRow(label: 'App ID', value: app.identifier)); + final installedPackage = ref.watch( + installedPackageProvider(app.identifier), + ); + final installerPackageName = installedPackage?.installerPackageName; + if (installerPackageName != null && + installerPackageName != kZapstoreAppIdentifier) { + rows.add( + _InfoRow( + label: 'Installer', + value: installerPackageName, + copyValue: installerPackageName, + ), + ); + } + if (identityProof != null) { final npub = Utils.encodeShareableFromString( identityProof!.pubkey, @@ -154,7 +170,6 @@ class AppInfoTable extends HookConsumerWidget { } if (fileMetadata!.versionCode != null) { - final installedPackage = app.installedPackage; final installedVersionCode = installedPackage?.versionCode; final availableVersionCode = fileMetadata!.versionCode!;