Skip to content

Commit

Permalink
feat: Adding Export APK support
Browse files Browse the repository at this point in the history
  • Loading branch information
nkitsaini committed Nov 1, 2022
1 parent 293f715 commit 10efbbd
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package app.revanced.manager.flutter

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
Expand All @@ -18,19 +21,32 @@ import dalvik.system.DexClassLoader
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.Result
import java.io.File
import java.io.OutputStream

private const val PATCHER_CHANNEL = "app.revanced.manager.flutter/patcher"
private const val INSTALLER_CHANNEL = "app.revanced.manager.flutter/installer"
private const val EXPORTER_CHANNEL = "app.revanced.manager.flutter/exporter"

private const val APK_MIME_TYPE = "application/vnd.android.package-archive"

class MainActivity : FlutterActivity() {
private val handler = Handler(Looper.getMainLooper())
private lateinit var installerChannel: MethodChannel
private lateinit var exporterChannel: MethodChannel


// Export APK
internal val export_request_code = 1
internal var export_result: Result? = null
internal var export_source_path: String? = null

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PATCHER_CHANNEL)
installerChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL)
exporterChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, EXPORTER_CHANNEL)
mainChannel.setMethodCallHandler { call, result ->
when (call.method) {
"runPatcher" -> {
Expand Down Expand Up @@ -75,6 +91,71 @@ class MainActivity : FlutterActivity() {
else -> result.notImplemented()
}
}

exporterChannel.setMethodCallHandler { call, result ->
// Referenced from https://gist.github.com/MSVCode/9ccedfa6692f8bc3b82fdc74fad65bc6
if (call.method == "exportApk") {
export_result = result;

export_source_path = call.argument<String>("source_path")!!;
var name = call.argument<String>("name")!!;
startFileExport(APK_MIME_TYPE, name);
} else {
result.notImplemented();
}
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

// Check which request we're responding to
if (requestCode == export_request_code) {
if (resultCode == Activity.RESULT_OK) {
if (data != null && data.getData() != null) {
exportToFile(data.getData() as Uri) // data.getData() is Uri
} else {
export_result?.error("NO DATA", "Did not get valid data (Uri) for export", null)
}
} else {
export_result?.error("CANCELED", "User cancelled", null)
}
}
}

private fun startFileExport(mimeType: String, fileName: String) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
// Filter to only show results that can be "opened", such as
// a file (as opposed to a list of contacts or timezones).
addCategory(Intent.CATEGORY_OPENABLE)

// Create a file with the requested MIME type.
type = mimeType
putExtra(Intent.EXTRA_TITLE, fileName)
}

startActivityForResult(intent, export_request_code)
}


private fun exportToFile(uri: Uri) {
val outputStream: OutputStream?
try {
outputStream = getContentResolver().openOutputStream(uri)
if (outputStream != null) {
File(export_source_path).inputStream().copyTo(outputStream)
export_result?.success("SUCCESS")
} else {
export_result?.error("ERROR", "Unable to open output Uri", null)
}
} catch (e: Exception) {

// log to console
print("Error exporting apk: ${e.message}")
e.printStackTrace()

export_result?.error("ERROR", "Unable to write", null)
}
}

private fun runPatcher(
Expand Down
1 change: 1 addition & 0 deletions assets/i18n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"notificationTitle": "ReVanced Manager is patching",
"notificationText": "Tap to return to the installer",
"shareApkMenuOption": "Share APK",
"exportApkMenuOption": "Export APK",
"shareLogMenuOption": "Share log",
"installErrorDialogTitle": "Error",
"installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.",
Expand Down
26 changes: 24 additions & 2 deletions lib/services/patcher_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,26 @@ class PatcherAPI {
return false;
}


void exportPatchedFile(String appName, String version) {
try {
if (_outFile != null) {
const platform = MethodChannel("app.revanced.manager.flutter/exporter");
String newName = _getFileName(appName, version);
platform.invokeMethod("exportApk", {
"source_path": _outFile!.path,
"name": newName
});
}
} on Exception catch (e, s) {
Sentry.captureException(e, stackTrace: s);
}
}

void sharePatchedFile(String appName, String version) {
try {
if (_outFile != null) {
String prefix = appName.toLowerCase().replaceAll(' ', '-');
String newName = '$prefix-revanced_v$version.apk';
String newName = _getFileName(appName, version);
int lastSeparator = _outFile!.path.lastIndexOf('/');
String newPath =
_outFile!.path.substring(0, lastSeparator + 1) + newName;
Expand All @@ -244,6 +259,13 @@ class PatcherAPI {
}
}

String _getFileName(String appName, String version) {
String prefix = appName.toLowerCase().replaceAll(' ', '-');
String newName = '$prefix-revanced_v$version.apk';
return newName;

}

Future<void> sharePatcherLog(String logs) async {
Directory appCache = await getTemporaryDirectory();
Directory logDir = Directory('${appCache.path}/logs');
Expand Down
11 changes: 10 additions & 1 deletion lib/ui/views/installer/installer_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,16 @@ class InstallerView extends StatelessWidget {
),
),
),
1: I18nText(
1: I18nText(
'installerView.exportApkMenuOption',
child: const Text(
'',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
2: I18nText(
'installerView.shareLogMenuOption',
child: const Text(
'',
Expand Down
11 changes: 11 additions & 0 deletions lib/ui/views/installer/installer_viewmodel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ class InstallerViewModel extends BaseViewModel {
}
}

void exportResult() {
try {
_patcherAPI.exportPatchedFile(_app.name, _app.version);
} on Exception catch (e, s) {
Sentry.captureException(e, stackTrace: s);
}
}

void shareResult() {
try {
_patcherAPI.sharePatchedFile(_app.name, _app.version);
Expand Down Expand Up @@ -250,6 +258,9 @@ class InstallerViewModel extends BaseViewModel {
shareResult();
break;
case 1:
exportResult();
break;
case 2:
shareLog();
break;
}
Expand Down

0 comments on commit 10efbbd

Please sign in to comment.