Files
zapstore/lib/services/background_native_installer.dart

147 lines
4.5 KiB
Dart

import 'package:flutter/services.dart';
import 'package:zapstore/services/log_service.dart';
/// Native install helpers usable from the WorkManager background isolate.
class BackgroundNativeInstaller {
static const _channel = MethodChannel('android_package_manager');
/// Verify APK hash and signing metadata without installing.
static Future<bool> verifyApk({
required String filePath,
required String expectedHash,
required List<String> expectedCertHashes,
Map<String, Object>? c1Proof,
}) async {
try {
final result = await _channel
.invokeMethod<Map<Object?, Object?>>('verifyApk', {
'filePath': filePath,
'expectedHash': expectedHash,
'expectedCertHashes': expectedCertHashes,
'c1Proof': c1Proof,
});
final map = Map<String, dynamic>.from(result ?? {});
return map['valid'] == true;
} catch (e, st) {
LogService.I.warn(
'background APK verify failed',
tag: 'background_updates',
fields: {'filePath': filePath},
err: e,
stack: st,
);
return false;
}
}
/// Install silently and block until a terminal result is known.
static Future<BackgroundInstallResult> installAndAwait({
required String appId,
required String filePath,
required String expectedHash,
required int expectedSize,
required List<String> expectedCertHashes,
Map<String, Object>? c1Proof,
}) async {
try {
final result = await _channel
.invokeMethod<Map<Object?, Object?>>('installAndAwait', {
'filePath': filePath,
'packageName': appId,
'expectedHash': expectedHash,
'expectedSize': expectedSize,
'expectedCertHashes': expectedCertHashes,
'c1Proof': c1Proof,
});
final map = Map<String, dynamic>.from(result ?? {});
return BackgroundInstallResult(
success: map['success'] == true,
cancelled: map['cancelled'] == true,
needsUserAction: map['needsUserAction'] == true,
error: map['error'] as String?,
);
} catch (e, st) {
LogService.I.warn(
'background installAndAwait failed',
tag: 'background_updates',
fields: {'appId': appId},
err: e,
stack: st,
);
return BackgroundInstallResult(success: false, error: e.toString());
}
}
/// Notify an attached foreground engine that silent background updates finished.
///
/// The foreground package manager will rescan installed packages and refresh
/// its local update categories. This does not forward per-install status
/// events, which belong only to the headless worker.
static Future<void> notifyBackgroundUpdatesCompleted(
List<String> updatedAppIds,
) async {
if (updatedAppIds.isEmpty) return;
try {
await _channel.invokeMethod<void>('notifyBackgroundUpdatesCompleted', {
'updatedAppIds': updatedAppIds,
});
} catch (e, st) {
LogService.I.warn(
'could not notify foreground background updates completed',
tag: 'background_updates',
fields: {'updatedAppIds': updatedAppIds.join(',')},
err: e,
stack: st,
);
}
}
/// Launch the Android install confirmation dialog for a prepared manual update.
static Future<bool> launchPreparedInstall({
required String appId,
required String filePath,
required String expectedHash,
required int expectedSize,
required List<String> expectedCertHashes,
Map<String, Object>? c1Proof,
}) async {
try {
final result = await _channel
.invokeMethod<Map<Object?, Object?>>('install', {
'filePath': filePath,
'packageName': appId,
'expectedHash': expectedHash,
'expectedSize': expectedSize,
'expectedCertHashes': expectedCertHashes,
'c1Proof': c1Proof,
});
final map = Map<String, dynamic>.from(result ?? {});
return map['started'] == true || map['alreadyInProgress'] == true;
} catch (e, st) {
LogService.I.warn(
'launch prepared install failed',
tag: 'background_updates',
fields: {'appId': appId},
err: e,
stack: st,
);
return false;
}
}
}
class BackgroundInstallResult {
const BackgroundInstallResult({
required this.success,
this.cancelled = false,
this.needsUserAction = false,
this.error,
});
final bool success;
final bool cancelled;
final bool needsUserAction;
final String? error;
}