fix: root installation and foreground task and improve installer a bit

This commit is contained in:
Alberto Ponces 2022-08-15 03:31:36 +01:00
parent 8fd942a808
commit 5c71930ec1
10 changed files with 231 additions and 207 deletions

View file

@ -4,10 +4,11 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:label="ReVanced Manager"
android:name="${applicationName}"
@ -34,7 +35,6 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<service android:name="com.pravera.flutter_foreground_task.service.ForegroundService" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"

View file

@ -38,9 +38,8 @@
},
"patchSelectorCard": {
"widgetTitle": "Select patches",
"widgetFirstSubtitle": "Select an application first.",
"widgetSecondSubtitle": "No patches selected.",
"widgetThirdSubtitle": "{selected} patch(es) selected."
"widgetSubtitle": "Select an application first.",
"widgetEmptySubtitle": "No patches selected."
},
"appSelectorView": {
"searchBarHint": "Search applications",
@ -53,8 +52,8 @@
},
"installerView": {
"widgetTitle": "Installer",
"installButton": "Install",
"shareButton": "Share"
"fabInstallButton": "Install",
"fabOpenButton": "Open"
},
"settingsView": {
"widgetTitle": "Settings",

View file

@ -40,13 +40,12 @@ class GithubAPI {
Future<List<Contributor>> 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();
}
}
}

View file

@ -32,11 +32,8 @@ class PatcherAPI {
Future<dynamic> handlePlatformChannelMethods() async {
platform.setMethodCallHandler((call) async {
switch (call.method) {
case 'updateInstallerLog':
var message = call.arguments<String>('message');
locator<InstallerViewModel>().addLog(message);
return 'OK';
if (call.method == 'updateInstallerLog' && call.arguments != null) {
locator<InstallerViewModel>().addLog(call.arguments);
}
});
}
@ -298,4 +295,17 @@ class PatcherAPI {
return false;
}
}
Future<bool> checkOldPatch(PatchedApplication patchedApp) async {
if (patchedApp.isRooted) {
return await rootAPI.checkApp(patchedApp.packageName);
}
return false;
}
Future<void> deleteOldPatch(PatchedApplication patchedApp) async {
if (patchedApp.isRooted) {
await rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath);
}
}
}

View file

@ -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<bool> 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<void> 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<bool> 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<void> installServiceDScript(
String packageName,
String originalFilePath,
String patchedFilePath,
) async {
Future<void> 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<void> installPostFsDataScript(
Future<void> 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<void> 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<void> 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');
}
}

View file

@ -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<InstallerViewModel>(),
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: <Widget>[
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: <Widget>[
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;
},
),
);
}

View file

@ -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<void> 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<PatcherAPI>().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<PatchesSelectorViewModel>().selectedPatches;
if (selectedPatches.isNotEmpty) {
addLog('Initializing installer...');
if (selectedApp.isRooted) {
addLog('Checking if an old patched version exists...');
bool oldExists =
await locator<PatcherAPI>().checkOldPatch(selectedApp);
addLog('Done');
if (oldExists) {
addLog('Deleting old patched version...');
await locator<PatcherAPI>().deleteOldPatch(selectedApp);
addLog('Done');
}
}
addLog('Creating working directory...');
bool? isSuccess = await locator<PatcherAPI>().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<PatcherAPI>().installPatchedFile(selectedApp);
if (isSucess) {
isInstalled = await locator<PatcherAPI>().installPatchedFile(selectedApp);
if (isInstalled) {
addLog('Done');
} else {
addLog('An error occurred! Aborting...');
@ -139,10 +167,18 @@ class InstallerViewModel extends BaseViewModel {
}
}
void cleanWorkplace() {
Future<void> cleanWorkplace() async {
locator<PatcherAPI>().cleanPatcher();
locator<AppSelectorViewModel>().selectedApp = null;
locator<PatchesSelectorViewModel>().selectedPatches.clear();
locator<PatcherViewModel>().notifyListeners();
}
void openApp() {
PatchedApplication? selectedApp =
locator<AppSelectorViewModel>().selectedApp;
if (selectedApp != null) {
DeviceApps.openApp(selectedApp.packageName);
}
}
}

View file

@ -25,11 +25,13 @@ class PatchesSelectorViewModel extends BaseViewModel {
void selectPatches(List<PatchItem> 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);
}
}
}
}

View file

@ -43,7 +43,7 @@ class PatchSelectorCard extends StatelessWidget {
const SizedBox(height: 10),
locator<AppSelectorViewModel>().selectedApp == null
? I18nText(
'patchSelectorCard.widgetFirstSubtitle',
'patchSelectorCard.widgetSubtitle',
child: Text(
'',
style: robotoTextStyle,
@ -51,24 +51,18 @@ class PatchSelectorCard extends StatelessWidget {
)
: locator<PatchesSelectorViewModel>().selectedPatches.isEmpty
? I18nText(
'patchSelectorCard.widgetSecondSubtitle',
'patchSelectorCard.widgetEmptySubtitle',
child: Text(
'',
style: robotoTextStyle,
),
)
: I18nText(
'patchSelectorCard.widgetThirdSubtitle',
translationParams: {
'selected': locator<PatchesSelectorViewModel>()
.selectedPatches
.length
.toString()
},
child: Text(
'',
style: robotoTextStyle,
),
: Text(
locator<PatchesSelectorViewModel>()
.selectedPatches
.map((e) => e.simpleName)
.toList()
.join('\n'),
),
],
),

View file

@ -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