JJ_MKVMaker/lib/core/app_manager.dart

91 lines
3.1 KiB
Dart

import 'dart:io';
import 'package:flutter/services.dart';
import 'package:path/path.dart' as p;
/// X-plore 비교 5-2 앱 관리자 (사용자 결정 10/21, Android). 관리자 판단: 모든 앱 목록 권한 (QUERY_ALL_PACKAGES) 은 쓰지 않고
/// 매니페스트 `<queries>` 의 LAUNCHER 로 "실행할 수 있는 앱" (런처에 보이는 앱) 만 본다.
class InstalledApp {
final String package, label, version, apk;
final DateTime installed, updated;
final int size, splits;
final bool system;
const InstalledApp({
required this.package,
required this.label,
required this.version,
required this.apk,
required this.installed,
required this.updated,
required this.size,
this.splits = 0,
this.system = false,
});
factory InstalledApp.fromMap(Map<Object?, Object?> m) {
int n(String k) => (m[k] as num?)?.toInt() ?? 0;
return InstalledApp(
package: '${m['package'] ?? ''}',
label: '${m['label'] ?? m['package'] ?? ''}',
version: '${m['version'] ?? ''}',
apk: '${m['apk'] ?? ''}',
installed: DateTime.fromMillisecondsSinceEpoch(n('installed')),
updated: DateTime.fromMillisecondsSinceEpoch(n('updated')),
size: n('size'),
splits: n('splits'),
system: m['system'] == true,
);
}
}
/// 기기와 이야기하는 쪽 (시험에서 바꿔 끼움)
abstract class AppManagerBackend {
Future<List<InstalledApp>> list();
Future<Uint8List?> icon(String package);
Future<bool> details(String package);
Future<bool> uninstall(String package);
/// APK 백업 폴더 (Download/JJ_MKVMaker/apk)
Future<String> backupDir();
}
class AndroidAppManager implements AppManagerBackend {
static const _ch = MethodChannel('jj_mkvmaker/android');
@override
Future<List<InstalledApp>> list() async {
final l = await _ch.invokeMethod<List<Object?>>('launcherApps') ?? const [];
return [for (final m in l) if (m is Map) InstalledApp.fromMap(m)];
}
@override
Future<Uint8List?> icon(String package) => _ch.invokeMethod<Uint8List>('appIcon', {'package': package, 'size': 96});
@override
Future<bool> details(String package) async => await _ch.invokeMethod<bool>('appDetails', {'package': package}) ?? false;
@override
Future<bool> uninstall(String package) async => await _ch.invokeMethod<bool>('uninstallApp', {'package': package}) ?? false;
@override
Future<String> backupDir() async {
final root = await _ch.invokeMethod<String>('storageRoot') ?? '/storage/emulated/0';
return p.join(root, 'Download', 'JJ_MKVMaker', 'apk');
}
}
/// 파일 이름에 못 쓰는 글자를 _ 로
String _safe(String s) => s.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim();
/// APK 를 [dir] 에 복사한다 ("이름_판.apk", 이미 있으면 " (2)" …). 만든 파일 경로
Future<String> backupApk(InstalledApp app, String dir) async {
await Directory(dir).create(recursive: true);
final base = '${_safe(app.label)}_${_safe(app.version.isEmpty ? app.package : app.version)}';
var out = p.join(dir, '$base.apk');
for (var i = 2; await File(out).exists(); i++) {
out = p.join(dir, '$base ($i).apk');
}
await File(app.apk).copy(out);
return out;
}