diff --git a/lib/services/package_manager/background_package_manager.dart b/lib/services/package_manager/background_package_manager.dart new file mode 100644 index 0000000..e72e275 --- /dev/null +++ b/lib/services/package_manager/background_package_manager.dart @@ -0,0 +1,53 @@ +import 'package:models/models.dart'; +import 'package:zapstore/services/package_manager/installed_packages_snapshot.dart'; +import 'package:zapstore/services/package_manager/package_manager.dart'; + +/// Background-safe PackageManager that avoids EventChannel usage. +final class BackgroundPackageManager extends PackageManager { + BackgroundPackageManager(super.ref); + + // Zapstore currently targets arm64 APKs for background checks. + @override + String get platform => 'android-arm64-v8a'; + + @override + String get packageExtension => '.apk'; + + @override + bool get supportsSilentInstall => false; + + @override + Future install( + String appId, + String filePath, { + required String expectedHash, + required int expectedSize, + required FileMetadata target, + }) { + throw UnsupportedError('Install not supported in background'); + } + + @override + Future uninstall(String appId) { + throw UnsupportedError('Uninstall not supported in background'); + } + + @override + Future launchApp(String appId) { + throw UnsupportedError('Launch not supported in background'); + } + + @override + Future requestPermission() { + throw UnsupportedError('Permission not supported in background'); + } + + @override + Future hasPermission() async => false; + + @override + Future syncInstalledPackages() async { + final installed = await InstalledPackagesSnapshot.load(); + state = state.copyWith(installed: installed); + } +} diff --git a/lib/services/package_manager/installed_packages_snapshot.dart b/lib/services/package_manager/installed_packages_snapshot.dart new file mode 100644 index 0000000..043af6f --- /dev/null +++ b/lib/services/package_manager/installed_packages_snapshot.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; +import 'package:zapstore/services/package_manager/package_manager.dart'; + +class InstalledPackagesSnapshot { + static const _fileName = 'installed_packages_snapshot.json'; + + static Future _file() async { + final dir = await getApplicationSupportDirectory(); + return File(path.join(dir.path, _fileName)); + } + + static Future save(Map installed) async { + try { + final file = await _file(); + final tmp = File('${file.path}.tmp'); + final list = installed.values + .map( + (p) => { + 'appId': p.appId, + 'name': p.name, + 'version': p.version, + 'versionCode': p.versionCode, + 'signatureHash': p.signatureHash, + 'canInstallSilently': p.canInstallSilently, + }, + ) + .toList(growable: false); + final payload = jsonEncode({ + 'v': 1, + 'savedAt': DateTime.now().millisecondsSinceEpoch, + 'installed': list, + }); + await tmp.writeAsString(payload, flush: true); + if (await file.exists()) { + await file.delete(); + } + await tmp.rename(file.path); + } catch (e) { + // Best-effort snapshot only. + if (kDebugMode) { + debugPrint('[InstalledPackagesSnapshot] Save failed: $e'); + } + } + } + + static Future> load() async { + try { + final file = await _file(); + if (!await file.exists()) return {}; + + final raw = await file.readAsString(); + final decoded = jsonDecode(raw); + if (decoded is! Map) return {}; + final version = decoded['v']; + if (version != null && version is! int && kDebugMode) { + debugPrint('[InstalledPackagesSnapshot] Unknown schema: $version'); + } + final installed = decoded['installed']; + if (installed is! List) return {}; + + final result = {}; + for (final item in installed) { + if (item is! Map) continue; + final map = Map.from(item); + final appId = map['appId'] as String?; + if (appId == null || appId.isEmpty) continue; + result[appId] = PackageInfo( + appId: appId, + name: map['name'] as String?, + version: (map['version'] as String?) ?? '0.0.0', + versionCode: map['versionCode'] as int?, + signatureHash: (map['signatureHash'] as String?) ?? '', + installTime: null, + canInstallSilently: (map['canInstallSilently'] as bool?) ?? false, + ); + } + return result; + } catch (e) { + if (kDebugMode) { + debugPrint('[InstalledPackagesSnapshot] Load failed: $e'); + } + return {}; + } + } +}