From 5c71930ec112d5159761d4e48b149e299a1ae43f Mon Sep 17 00:00:00 2001 From: Alberto Ponces Date: Mon, 15 Aug 2022 03:31:36 +0100 Subject: [PATCH] fix: root installation and foreground task and improve installer a bit --- android/app/src/main/AndroidManifest.xml | 8 +- assets/i18n/en.json | 9 +- lib/services/github_api.dart | 5 +- lib/services/patcher_api.dart | 20 +- lib/services/root_api.dart | 124 ++++++----- lib/ui/views/installer/installer_view.dart | 192 +++++++----------- .../views/installer/installer_viewmodel.dart | 44 +++- .../patches_selector_viewmodel.dart | 12 +- lib/ui/widgets/patch_selector_card.dart | 22 +- pubspec.yaml | 2 +- 10 files changed, 231 insertions(+), 207 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index abd718e1..2ca2fb11 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,10 +4,11 @@ - - - + + + + - > getContributors(String org, repoName) async { try { - var contributors = await github.repositories.listContributors( + var contributors = github.repositories.listContributors( RepositorySlug(org, repoName), ); return contributors.toList(); } on Exception { - print(Exception); - return []; + return List.empty(); } } } diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index cd6e11ea..d201bc88 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -32,11 +32,8 @@ class PatcherAPI { Future handlePlatformChannelMethods() async { platform.setMethodCallHandler((call) async { - switch (call.method) { - case 'updateInstallerLog': - var message = call.arguments('message'); - locator().addLog(message); - return 'OK'; + if (call.method == 'updateInstallerLog' && call.arguments != null) { + locator().addLog(call.arguments); } }); } @@ -298,4 +295,17 @@ class PatcherAPI { return false; } } + + Future checkOldPatch(PatchedApplication patchedApp) async { + if (patchedApp.isRooted) { + return await rootAPI.checkApp(patchedApp.packageName); + } + return false; + } + + Future deleteOldPatch(PatchedApplication patchedApp) async { + if (patchedApp.isRooted) { + await rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath); + } + } } diff --git a/lib/services/root_api.dart b/lib/services/root_api.dart index 36a8bca7..d7a4eed3 100644 --- a/lib/services/root_api.dart +++ b/lib/services/root_api.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:injectable/injectable.dart'; import 'package:root/root.dart'; @@ -9,17 +7,35 @@ class RootAPI { final String postFsDataDirPath = "/data/adb/post-fs-data.d"; final String serviceDDirPath = "/data/adb/service.d"; - bool deleteApp(String packageName) { + Future checkApp(String packageName) async { try { - File('$managerDirPath/$packageName.apk').deleteSync(); - File('$serviceDDirPath/$packageName.sh').deleteSync(); - File('$postFsDataDirPath/$packageName.sh').deleteSync(); - return true; + String? res = await Root.exec( + cmd: 'ls -la "$managerDirPath/$packageName"', + ); + return res != null && res.isNotEmpty; } on Exception { return false; } } + Future deleteApp(String packageName, String originalFilePath) async { + await Root.exec( + cmd: 'am force-stop "$packageName"', + ); + await Root.exec( + cmd: 'su -mm -c "umount -l $originalFilePath"', + ); + await Root.exec( + cmd: 'rm -rf "$managerDirPath/$packageName"', + ); + await Root.exec( + cmd: 'rm -rf "$serviceDDirPath/$packageName.sh"', + ); + await Root.exec( + cmd: 'rm -rf "$postFsDataDirPath/$packageName.sh"', + ); + } + Future installApp( String packageName, String originalFilePath, @@ -27,66 +43,76 @@ class RootAPI { ) async { try { await Root.exec( - cmd: 'mkdir "$managerDirPath"', - ); - String newPatchedFilePath = '$managerDirPath/$packageName.apk'; - installServiceDScript( - packageName, - originalFilePath, - newPatchedFilePath, - ); - installPostFsDataScript( - packageName, - originalFilePath, - newPatchedFilePath, - ); - await Root.exec( - cmd: 'cp $patchedFilePath $newPatchedFilePath', - ); - await Root.exec( - cmd: 'chmod 644 "$newPatchedFilePath"', - ); - await Root.exec( - cmd: 'chown system:system "$newPatchedFilePath"', - ); - await Root.exec( - cmd: 'chcon u:object_r:apk_data_file:s0 "$newPatchedFilePath"', + cmd: 'mkdir -p "$managerDirPath/$packageName"', ); + installServiceDScript(packageName); + installPostFsDataScript(packageName); + installApk(packageName, patchedFilePath); + mountApk(packageName, originalFilePath, patchedFilePath); return true; } on Exception { return false; } } - Future installServiceDScript( - String packageName, - String originalFilePath, - String patchedFilePath, - ) async { + Future installServiceDScript(String packageName) async { String content = '#!/system/bin/sh\n' - 'while [ "\$(getprop sys.boot_completed | tr -d \'\r\')" != "1" ]; do sleep 1; done\n' - 'sleep 1\n' - 'chcon u:object_r:apk_data_file:s0 $patchedFilePath\n' - 'mount -o bind $patchedFilePath $originalFilePath'; + 'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 1; done\n' + 'base_path=$managerDirPath/$packageName/base.apk\n' + 'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n' + '[ ! -z \$stock_path ] && mount -o bind \$base_path \$stock_path'; String scriptFilePath = '$serviceDDirPath/$packageName.sh'; await Root.exec( - cmd: 'echo "$content" > "$scriptFilePath"', + cmd: 'echo \'$content\' > "$scriptFilePath"', + ); + await Root.exec( + cmd: 'chmod 744 "$scriptFilePath"', ); - await Root.exec(cmd: 'chmod 744 "$scriptFilePath"'); } - Future installPostFsDataScript( + Future installPostFsDataScript(String packageName) async { + String content = '#!/system/bin/sh\n' + 'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n' + '[ ! -z \$stock_path ] && umount -l \$stock_path'; + String scriptFilePath = '$postFsDataDirPath/$packageName.sh'; + await Root.exec( + cmd: 'echo \'$content\' > "$scriptFilePath"', + ); + await Root.exec( + cmd: 'chmod 744 "$scriptFilePath"', + ); + } + + Future installApk(String packageName, String patchedFilePath) async { + String newPatchedFilePath = '$managerDirPath/$packageName/base.apk'; + await Root.exec( + cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"', + ); + await Root.exec( + cmd: 'chmod 644 "$newPatchedFilePath"', + ); + await Root.exec( + cmd: 'chown system:system "$newPatchedFilePath"', + ); + await Root.exec( + cmd: 'chcon u:object_r:apk_data_file:s0 "$newPatchedFilePath"', + ); + } + + Future mountApk( String packageName, String originalFilePath, String patchedFilePath, ) async { - String content = '#!/system/bin/sh\n' - 'while read line; do echo \$line | grep $originalFilePath | ' - 'awk \'{print \$2}\' | xargs umount -l; done< /proc/mounts'; - String scriptFilePath = '$postFsDataDirPath/$packageName.sh'; + String newPatchedFilePath = '$managerDirPath/$packageName/base.apk'; await Root.exec( - cmd: 'echo "$content" > "$scriptFilePath"', + cmd: 'am force-stop "$packageName"', + ); + await Root.exec( + cmd: 'su -mm -c "umount -l $originalFilePath"', + ); + await Root.exec( + cmd: 'su -mm -c "mount -o bind $newPatchedFilePath $originalFilePath"', ); - await Root.exec(cmd: 'chmod 744 $scriptFilePath'); } } diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart index a90c7058..f915a568 100644 --- a/lib/ui/views/installer/installer_view.dart +++ b/lib/ui/views/installer/installer_view.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart'; @@ -18,130 +17,89 @@ class InstallerView extends StatelessWidget { disposeViewModel: false, onModelReady: (model) => model.initialize(), viewModelBuilder: () => locator(), - builder: (context, model, child) => WillStartForegroundTask( - onWillStart: () async => model.isPatching, - androidNotificationOptions: AndroidNotificationOptions( - channelId: 'revanced-patcher-patching', - channelName: 'Patching', - channelDescription: 'This notification appears when the patching ' - 'foreground service is running.', - channelImportance: NotificationChannelImportance.LOW, - priority: NotificationPriority.LOW, - ), - notificationTitle: 'Patching', - notificationText: 'ReVanced Manager is patching', - callback: () => {}, - child: WillPopScope( - child: Scaffold( - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) => SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 12), - controller: _controller, - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: constraints.maxWidth, - minHeight: constraints.maxHeight, + builder: (context, model, child) => WillPopScope( + child: Scaffold( + floatingActionButton: Visibility( + visible: model.showButtons, + child: FloatingActionButton.extended( + onPressed: () => + model.isInstalled ? model.openApp() : model.installResult(), + label: I18nText(model.isInstalled + ? 'installerView.fabOpenButton' + : 'installerView.fabInstallButton'), + icon: model.isInstalled + ? const Icon(Icons.open_in_new) + : const Icon(Icons.install_mobile), + backgroundColor: Theme.of(context).colorScheme.secondary, + foregroundColor: Colors.white, + ), + ), + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 12), + controller: _controller, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + I18nText( + 'installerView.widgetTitle', + child: Text( + '', + style: Theme.of(context).textTheme.headline5, + ), + ), + Visibility( + visible: model.showButtons, + child: IconButton( + icon: const Icon(Icons.share), + onPressed: () => model.shareResult(), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0, + horizontal: 4.0, ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - I18nText( - 'installerView.widgetTitle', - child: Text( - '', - style: Theme.of(context).textTheme.headline5, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 4.0, - ), - child: LinearProgressIndicator( - color: Theme.of(context).colorScheme.secondary, - backgroundColor: Colors.white, - value: model.progress, - ), - ), - Container( - padding: const EdgeInsets.all(12.0), - width: double.infinity, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: BorderRadius.circular(8), - ), - child: SelectableText( - model.logs, - style: const TextStyle( - fontFamily: 'monospace', - fontSize: 15, - height: 1.5, - ), - ), - ), - const Spacer(), - Visibility( - visible: model.showButtons, - child: Row( - children: [ - Expanded( - child: MaterialButton( - textColor: Colors.white, - color: - Theme.of(context).colorScheme.secondary, - padding: const EdgeInsets.symmetric( - vertical: 12, - horizontal: 8, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - onPressed: () => model.installResult(), - child: I18nText( - 'installerView.installButton', - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: MaterialButton( - textColor: Colors.white, - color: - Theme.of(context).colorScheme.secondary, - padding: const EdgeInsets.symmetric( - vertical: 12, - horizontal: 8, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - onPressed: () => model.shareResult(), - child: I18nText( - 'installerView.shareButton', - ), - ), - ), - ], - ), - ), - ], + child: LinearProgressIndicator( + color: Theme.of(context).colorScheme.secondary, + backgroundColor: Colors.white, + value: model.progress, + ), + ), + Container( + padding: const EdgeInsets.all(12.0), + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.circular(8), + ), + child: SelectableText( + model.logs, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 15, + height: 1.5, ), ), ), - ), + ], ), ), ), - onWillPop: () async { - if (!model.isPatching) { - model.cleanWorkplace(); - Navigator.of(context).pop(); - } - return false; - }, ), + onWillPop: () async { + if (!model.isPatching) { + model.cleanWorkplace(); + Navigator.of(context).pop(); + } + return false; + }, ), ); } diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index d2203988..c9e5ab28 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -1,3 +1,5 @@ +import 'package:device_apps/device_apps.dart'; +import 'package:flutter_background/flutter_background.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patched_application.dart'; @@ -11,9 +13,22 @@ class InstallerViewModel extends BaseViewModel { double? progress = 0.2; String logs = ''; bool isPatching = false; + bool isInstalled = false; bool showButtons = false; Future initialize() async { + await FlutterBackground.initialize( + androidConfig: const FlutterBackgroundAndroidConfig( + notificationTitle: 'Patching', + notificationText: 'ReVanced Manager is patching', + notificationImportance: AndroidNotificationImportance.Default, + notificationIcon: AndroidResource( + name: 'ic_launcher_foreground', + defType: 'drawable', + ), + ), + ); + await FlutterBackground.enableBackgroundExecution(); await locator().handlePlatformChannelMethods(); runPatcher(); } @@ -28,6 +43,7 @@ class InstallerViewModel extends BaseViewModel { void updateProgress(double value) { progress = value; + isInstalled = false; isPatching = progress == 1.0 ? false : true; showButtons = progress == 1.0 ? true : false; if (progress == 0.0) { @@ -46,6 +62,18 @@ class InstallerViewModel extends BaseViewModel { locator().selectedPatches; if (selectedPatches.isNotEmpty) { addLog('Initializing installer...'); + if (selectedApp.isRooted) { + addLog('Checking if an old patched version exists...'); + bool oldExists = + await locator().checkOldPatch(selectedApp); + addLog('Done'); + if (oldExists) { + addLog('Deleting old patched version...'); + await locator().deleteOldPatch(selectedApp); + addLog('Done'); + } + } + addLog('Creating working directory...'); bool? isSuccess = await locator().initPatcher(); if (isSuccess != null && isSuccess) { addLog('Done'); @@ -108,6 +136,7 @@ class InstallerViewModel extends BaseViewModel { } else { addLog('No app selected! Aborting...'); } + await FlutterBackground.disableBackgroundExecution(); isPatching = false; } @@ -118,9 +147,8 @@ class InstallerViewModel extends BaseViewModel { addLog(selectedApp.isRooted ? 'Installing patched file using root method...' : 'Installing patched file using nonroot method...'); - bool isSucess = - await locator().installPatchedFile(selectedApp); - if (isSucess) { + isInstalled = await locator().installPatchedFile(selectedApp); + if (isInstalled) { addLog('Done'); } else { addLog('An error occurred! Aborting...'); @@ -139,10 +167,18 @@ class InstallerViewModel extends BaseViewModel { } } - void cleanWorkplace() { + Future cleanWorkplace() async { locator().cleanPatcher(); locator().selectedApp = null; locator().selectedPatches.clear(); locator().notifyListeners(); } + + void openApp() { + PatchedApplication? selectedApp = + locator().selectedApp; + if (selectedApp != null) { + DeviceApps.openApp(selectedApp.packageName); + } + } } diff --git a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index df1bf875..1bb79e68 100644 --- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart +++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart @@ -25,11 +25,13 @@ class PatchesSelectorViewModel extends BaseViewModel { void selectPatches(List patchItems) { selectedPatches.clear(); if (patches != null) { - for (PatchItem patch in patchItems) { - if (patch.isSelected) { - selectedPatches.add( - patches!.firstWhere((element) => element.name == patch.name), - ); + for (PatchItem item in patchItems) { + if (item.isSelected) { + Patch patch = + patches!.firstWhere((element) => element.name == item.name); + if (!selectedPatches.contains(patch)) { + selectedPatches.add(patch); + } } } } diff --git a/lib/ui/widgets/patch_selector_card.dart b/lib/ui/widgets/patch_selector_card.dart index 5d3b7dcb..98eb0635 100644 --- a/lib/ui/widgets/patch_selector_card.dart +++ b/lib/ui/widgets/patch_selector_card.dart @@ -43,7 +43,7 @@ class PatchSelectorCard extends StatelessWidget { const SizedBox(height: 10), locator().selectedApp == null ? I18nText( - 'patchSelectorCard.widgetFirstSubtitle', + 'patchSelectorCard.widgetSubtitle', child: Text( '', style: robotoTextStyle, @@ -51,24 +51,18 @@ class PatchSelectorCard extends StatelessWidget { ) : locator().selectedPatches.isEmpty ? I18nText( - 'patchSelectorCard.widgetSecondSubtitle', + 'patchSelectorCard.widgetEmptySubtitle', child: Text( '', style: robotoTextStyle, ), ) - : I18nText( - 'patchSelectorCard.widgetThirdSubtitle', - translationParams: { - 'selected': locator() - .selectedPatches - .length - .toString() - }, - child: Text( - '', - style: robotoTextStyle, - ), + : Text( + locator() + .selectedPatches + .map((e) => e.simpleName) + .toList() + .join('\n'), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 86a98033..bb504833 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,8 +17,8 @@ dependencies: file_picker: ^5.0.1 flutter: sdk: flutter + flutter_background: ^1.1.0 flutter_cache_manager: ^3.3.0 - flutter_foreground_task: ^3.8.1 flutter_i18n: ^0.32.4 flutter_svg: ^1.1.1+1 fluttertoast: ^8.0.9